Note that there are some explanatory texts on larger screens.

plurals
  1. POJar hell: how to use a classloader to replace one jar library version with another at runtime
    primarykey
    data
    text
    <p>I'm still relatively new to Java, so please bear with me.</p> <p>My issue is that my Java application depends on two libraries. Let's call them Library 1 and Library 2. Both of these libraries share a mutual dependency on Library 3. However:</p> <ul> <li>Library 1 requires exactly version 1 of Library 3.</li> <li>Library 2 requires exactly version 2 of Library 3.</li> </ul> <p>This is exactly the definition of <a href="http://en.wikipedia.org/wiki/Java_Classloader#JAR_hell" rel="noreferrer">JAR hell</a> (or at least one its variations). As stated in the link, I can't load both versions of the third library in the same classloader. Thus, I've been trying to figure out if I could create a new classloader within the application to solve this problem. I've been looking into <a href="http://download.oracle.com/javase/6/docs/api/java/net/URLClassLoader.html" rel="noreferrer">URLClassLoader</a>, but I've not been able to figure it out.</p> <p>Here's an example application structure that demonstrates the problem. The Main class (Main.java) of the application tries to instantiate both Library1 and Library2 and run some method defined in those libraries:</p> <p><strong>Main.java (original version, before any attempt at a solution):</strong></p> <pre><code>public class Main { public static void main(String[] args) { Library1 lib1 = new Library1(); lib1.foo(); Library2 lib2 = new Library2(); lib2.bar(); } } </code></pre> <p>Library1 and Library2 both share a mutual dependency on Library3, but Library1 requires exactly version 1, and Library2 requires exactly version 2. In the example, both of these libraries just print the version of Library3 that they see:</p> <p><strong>Library1.java:</strong></p> <pre><code>public class Library1 { public void foo() { Library3 lib3 = new Library3(); lib3.printVersion(); // Should print "This is version 1." } } </code></pre> <p><strong>Library2.java:</strong></p> <pre><code>public class Library2 { public void foo() { Library3 lib3 = new Library3(); lib3.printVersion(); // Should print "This is version 2." if the correct version of Library3 is loaded. } } </code></pre> <p>And then, of course, there are multiple versions of Library3. All they do is print their version numbers:</p> <p><strong>Version 1 of Library3 (required by Library1):</strong></p> <pre><code>public class Library3 { public void printVersion() { System.out.println("This is version 1."); } } </code></pre> <p><strong>Version 2 of Library3 (required by Library2):</strong></p> <pre><code>public class Library3 { public void printVersion() { System.out.println("This is version 2."); } } </code></pre> <p>When I launch the application, the classpath contains Library1 (lib1.jar), Library2 (lib2.jar), and version 1 of Library 3 (lib3-v1/lib3.jar). This works out fine for Library1, but it won't work for Library2.</p> <p>What I somehow need to do is replace the version of Library3 that appears on the classpath before instantiating Library2. I was under the impression that <a href="http://download.oracle.com/javase/6/docs/api/java/net/URLClassLoader.html" rel="noreferrer">URLClassLoader</a> could be used for this, so here is what I tried:</p> <p><strong>Main.java (new version, including my attempt at a solution):</strong></p> <pre><code>import java.net.*; import java.io.*; public class Main { public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException, FileNotFoundException { Library1 lib1 = new Library1(); lib1.foo(); // This causes "This is version 1." to print. // Original code: // Library2 lib2 = new Library2(); // lib2.bar(); // However, we need to replace Library 3 version 1, which is // on the classpath, with Library 3 version 2 before attempting // to instantiate Library2. // Create a new classloader that has the version 2 jar // of Library 3 in its list of jars. URL lib2_url = new URL("file:lib2/lib2.jar"); verifyValidPath(lib2_url); URL lib3_v2_url = new URL("file:lib3-v2/lib3.jar"); verifyValidPath(lib3_v2_url); URL[] urls = new URL[] {lib2_url, lib3_v2_url}; URLClassLoader c = new URLClassLoader(urls); // Try to instantiate Library2 with the new classloader Class&lt;?&gt; cls = Class.forName("Library2", true, c); Library2 lib2 = (Library2) cls.newInstance(); // If it worked, this should print "This is version 2." // However, it still prints that it's version 1. Why? lib2.bar(); } public static void verifyValidPath(URL url) throws FileNotFoundException { File filePath = new File(url.getFile()); if (!filePath.exists()) { throw new FileNotFoundException(filePath.getPath()); } } } </code></pre> <p>When I run this, <code>lib1.foo()</code> causes "This is version 1." to be printed. Since that's the version of Library3 that's on the classpath when the application starts, this is expected.</p> <p>However, I was expecting <code>lib2.bar()</code> to print "This is version 2.", reflecting that the new version of Library3 got loaded, but it still prints "This is version 1."</p> <p>Why is it that using the new classloader with the right jar version loaded still results in the old jar version being used? Am I doing something wrong? Or am I not understanding the concept behind classloaders? How can I switch jar versions of Library3 correctly at runtime?</p> <p>I would appreciate any help on this problem.</p>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload