Blog coding and discussion of coding about JavaScript, PHP, CGI, general web building etc.

Sunday, December 20, 2015

Jar hell: how to use a classloader to replace one jar library version with another at runtime

Jar hell: how to use a classloader to replace one jar library version with another at runtime


I'm still relatively new to Java, so please bear with me.

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:

  • Library 1 requires exactly version 1 of Library 3.
  • Library 2 requires exactly version 2 of Library 3.

This is exactly the definition of JAR hell (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 URLClassLoader, but I've not been able to figure it out.

I made a complete demo application of the problem and my attempt at a solution. It's available here:

If you're on an Unix-based OS, or if you have Cygwin on Windows, I've included a Makefile so you should be able to run make and ./run.sh to run the sample.

Here's how it's structured. The Main class (Main.java) of the application tries to instantiate both Library1 and Library2 and run some method defined in those libraries:

Main.java (original version, before any attempt at a solution):

public class Main {      public static void main(String[] args) {          Library1 lib1 = new Library1();          lib1.foo();            Library2 lib2 = new Library2();          lib2.bar();      }  }  

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:

Library1.java:

public class Library1 {    public void foo() {      Library3 lib3 = new Library3();      lib3.printVersion();    // Should print "This is version 1."    }  }  

Library2.java:

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.    }  }  

And then, of course, there are multiple versions of Library3. All they do is print their version numbers:

Version 1 of Library3 (required by Library1):

public class Library3 {    public void printVersion() {      System.out.println("This is version 1.");    }  }  

Version 2 of Library3 (required by Library2):

public class Library3 {    public void printVersion() {      System.out.println("This is version 2.");    }  }  

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.

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 URLClassLoader could be used for this, so here is what I tried:

Main.java (new version, including my attempt at a solution):

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 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());      }    }  }  

When I run this, lib1.foo() 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.

However, I was expecting lib2.bar() to print "This is version 2.", reflecting that the new version of Library3 got loaded, but it still prints "This is version 1."

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?

I would appreciate any help on this problem.

Answer by Vlad for Jar hell: how to use a classloader to replace one jar library version with another at runtime


Trying to get rid of classpath lib2 and invoke the bar() method by reflection:

try {      cls.getMethod("bar").invoke(cls.newInstance());  } catch (Exception e) {      e.printStackTrace();  }  

gives following output:

Exception in thread "main" java.lang.ClassNotFoundException: Library2      at java.net.URLClassLoader$1.run(URLClassLoader.java:202)      at java.security.AccessController.doPrivileged(Native Method)      at java.net.URLClassLoader.findClass(URLClassLoader.java:190)      at java.lang.ClassLoader.loadClass(ClassLoader.java:307)      at java.lang.ClassLoader.loadClass(ClassLoader.java:248)      at java.lang.Class.forName0(Native Method)      at java.lang.Class.forName(Class.java:247)      at Main.main(Main.java:36)  

This means you're in fact loading Library2 from classpath using default classloader, not your custom URLClassLoader.

Answer by wikier for Jar hell: how to use a classloader to replace one jar library version with another at runtime


classloader are something simple in concept, but actually quite complex

I recommend you not to use a custom solution

you have some partial open source solutions, such as DCEVM

but there are also very good commercial product, such as JRebel

Answer by Dave DiFranco for Jar hell: how to use a classloader to replace one jar library version with another at runtime


You need to load both Library1 and Library2 in separate URLClassloaders. (In your current code, Library2 is loaded in a URLClassloader whose parent is the main classloader - which has already loaded Library1.)

Change your example to something like this:

URL lib1_url = new URL("file:lib1/lib1.jar");        verifyValidPath(lib1_url);  URL lib3_v1_url = new URL("file:lib3-v1/lib3.jar");  verifyValidPath(lib3_v1_url);  URL[] urls1 = new URL[] {lib1_url, lib3_v21_url};  URLClassLoader c1 = new URLClassLoader(urls1);    Class cls1 = Class.forName("Library1", true, c);  Library1 lib1 = (Library1) cls1.newInstance();          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[] urls2 = new URL[] {lib2_url, lib3_v2_url};  URLClassLoader c2 = new URLClassLoader(url2s);      Class cls2 = Class.forName("Library2", true, c);  Library2 lib2 = (Library2) cls2.newInstance();  

Answer by Kamran for Jar hell: how to use a classloader to replace one jar library version with another at runtime


Use jar class loader which can be used to load classes from jar files on runtime.

Answer by janos for Jar hell: how to use a classloader to replace one jar library version with another at runtime


Your sample cannot work. I needed this too, but now I believe this is not possible, because: even if you instantiate class A using a custom class loader, other classes instantiated by class A will use the default class loader, not the custom one.

This becomes clear if you try to follow the suggestion in another answer, using separate custom class loaders to instantiate Library1 and Library2. The suggestion implies that run.sh can be changed from:

java -classpath .:lib1/lib1.jar:lib2/lib2.jar:lib3-v1/lib3.jar Main  

to:

java -classpath .:lib1/lib1.jar:lib2/lib2.jar Main  

because we want to load Library3 in only the separate class loaders, not in the default one.

Unfortunately this won't work. Even if you create Library1 using the custom class loader specifying the paths of lib1.jar:lib3-v1.jar, the moment Library1 tries to instantiate Library3 you get a ClassNotFoundException. This is because the default class loader will be used to instantiate Library3 instead of the custom one.

Answer by Jeewantha for Jar hell: how to use a classloader to replace one jar library version with another at runtime


You can use a ParentLastClassloader to solve Jar Hell. Please check this blog post out

Answer by mdzh for Jar hell: how to use a classloader to replace one jar library version with another at runtime


I can't believe that for more than 4 years no one has answered this question correctly.

https://docs.oracle.com/javase/8/docs/api/java/lang/ClassLoader.html

The ClassLoader class uses a delegation model to search for classes and resources. Each instance of ClassLoader has an associated parent class loader. When requested to find a class or resource, a ClassLoader instance will delegate the search for the class or resource to its parent class loader before attempting to find the class or resource itself. The virtual machine's built-in class loader, called the "bootstrap class loader", does not itself have a parent but may serve as the parent of a ClassLoader instance.

Sergei, the problem with your example was that Library 1,2 & 3 were on the default class path, so the Application classloader which was the parent of your URLClassloder was able to load the classes from Library 1,2 & 3.

If youremove the libraries from the classpath, the Application classloader won't be able to resolve classes from them so it will delegate resolvation to its child - the URLClassLoader. So that is what you need to do.


Fatal error: Call to a member function getElementsByTagName() on a non-object in D:\XAMPP INSTALLASTION\xampp\htdocs\endunpratama9i\www-stackoverflow-info-proses.php on line 71

0 comments:

Post a Comment

Popular Posts

Powered by Blogger.