简体   繁体   中英

classloader: how to load different version of jar

I have to use a 3rd party platform, but the platform has an older version of the jar libjar-1.0.0.jar that cannot be replaced. The platform let me run my own (flat file) packages on top of it. I put the new version of libjar-2.0.0.jar under my package /packages/package-name/external-jar. When I used URLClassLoader to load libjar-2.0.0.jar and then printing out all declaredmethods, I was able to see the method that is in 2.0.0 jar. However, when I invoke, I always get NoSuchMethodException . When I print out newobj.class.getProtectionDomain().getCodeSource().getLocation().toString() , it always shows libjar-1.0.0.jar . Could anyone help explaining what I did wrong and what I need to do to force using the classes in a particular jar during runtime?

Here is a snapshot of my code

File f = new File(path);

URL[] urls = new URL[1];
urls[0] = f.toURI().toURL();
ClassLoader cl = new URLClassLoader(urls);

Class<?> utilsClass = cl.loadClass("com.myclass");
Constructor<?> cons = utilsClass.getConstructor(First.class, Second.class);
Object utils = cons.newInstance(firstObj, new Second());
if (utilsClass.getProtectionDomain() != null) {
           LOGGER.info(utilsClass.getProtectionDomain().getCodeSource().getLocation().toString());
}
// this print out --- 1.0.0.jar instead of 2.0.0.jar

for (Method m : utilsClass.getDeclaredMethods()) {
     LOGGER.info("methods: " + m.getName());
}
// method shows the "methodILookFor"

Method m = utilsClass.getDeclaredMethod("methodILookFor", Target.class, String[].class, Object.class);
// always throws NoSuchMethodException

m.invoke(utils, target, string, obj);

How Class Loading Works

  • URLClassLoader is used for loading classes that are not already specified in the application classpath.
    • Class loading works on delegation principle . If a class is not loaded, the task of loading the class is delegated by the class loader to its parent class loader. If the class is not found by the parent class loader, it is passed to the child class loader for loading the class.
    • In your case, the URLClassLoader delegates the class loading to its parent, ie, the Application Class Loader .
    • The Application Class Loader finds the class in libjar-1.0.0.jar . So, the URLClassLoader ends up not loading the class from libjar-2.0.0.jar .

Custom Class Loader

Here is a simple example of a custom class loader which extends URLClassLoader . This class loader tries to load classes from its URLs before it delegates to its parent class loader. It should be able to load different versions of JARs that you need in your example. You will find a complete example here with a unit test.

PS Class loading has changed in Java 9. It is not tested with Java 9 and may not work.

public class MyClassLoader extends URLClassLoader {

    public MyClassLoader(URL[] urls, ClassLoader parent) {
        super(urls, parent);
    }

    @Override
    protected synchronized Class<?> loadClass(String name,
            boolean resolve) throws ClassNotFoundException {

        // 1. Check if the class has already been loaded
        Class<?> clazz = findLoadedClass(name);

        ClassLoader parentCL = getParent();

        // 2. If the class is not loaded and the class name starts
        // with 'java.' or 'javax.', delegate loading to parent
        if (clazz == null && parentCL != null && (name.startsWith(
                "java.") || name.startsWith(
                "javax."))) {
            clazz = parentCL.loadClass(name);

        }

        // 3. If the class is still null, try to load the class from the URL
        // (since we have already taken care of 'java.' and 'javax.'
        if (clazz == null) {
            try {
                clazz = super.findClass(name);
            } catch (ClassNotFoundException e) {
                //don't do anything
            }
        }

        // 4. If the class is still null, let the parent class loader load it.
        // Previously, we allowed 'java.' and 'javax.' classes to be loaded
        // from parent
        if (clazz == null && parentCL != null) {
            clazz = parentCL.loadClass(name);
        }

        // 5. If the class is still null, throw a class not found exception
        if (clazz == null) {
            throw new ClassNotFoundException(name);
        }

        if (resolve) {
            resolveClass(clazz);
        }

        return clazz;
    }
}

Instead of using URL class loader you can try using custom class loader to load class using its fully qualified name. Using this approach you should be able to bypass the class loading delegation to the parent class loader which is causing problem in your case. So your class loader should be able to load the class from libjar-2.0.0.jar.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM