Parent Last Classloader to solve Java Class path h

2020-07-24 06:51发布

问题:

I have a project which uses two versions of bouncyCastle jars bcprov-jdk15 and bcprov-jdk16. The jvm loads the older version but there is a feature I wrote which needs the newer version to run. I tried to solve this classpath hell by using a custom class loader. After some googling and with the help of some previous Stackoverflow answers[1] [2] and this blog, I wrote the following Parent Last Class loader to load the classes from the newer jar before delegating to the parent class loader.

public class ParentLastClassLoader extends ClassLoader {

    private String jarFile; //Path to the jar file
    private Hashtable classes = new Hashtable(); //used to cache already defined classes

    public ParentLastClassLoader(ClassLoader parent, String path)
    {
        super(parent);
        this.jarFile = path;
    }

    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException
    {
        System.out.println("Trying to find");
        throw new ClassNotFoundException();
    }

    @Override
    protected synchronized Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException
    {
        System.out.println("Trying to load");
        try
        {
            System.out.println("Loading class in Child : " + className);
            byte classByte[];
            Class result = null;

            //checks in cached classes
            result = (Class) classes.get(className);
            if (result != null) {
                return result;
            }

            try {
                JarFile jar = new JarFile(jarFile);
                JarEntry entry = jar.getJarEntry(className + ".class");
                InputStream is = jar.getInputStream(entry);
                ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
                int nextValue = is.read();
                while (-1 != nextValue) {
                    byteStream.write(nextValue);
                    nextValue = is.read();
                }

                classByte = byteStream.toByteArray();
                result = defineClass(className, classByte, 0, classByte.length, null);
                classes.put(className, result);
                return result;
            } catch (Exception e) {
                throw new ClassNotFoundException(className + "Not found", e);
            }
        }
        catch( ClassNotFoundException e ){

            System.out.println("Delegating to parent : " + className);
            // didn't find it, try the parent
            return super.loadClass(className, resolve);
        }
    }
}

I loaded the main class in the feature with this class loader but the BouncyCaslte classes used in the feature are not loaded by my custom classloader.

ClassLoader loader = new ParentLastClassLoader(Thread.currentThread().getContextClassLoader(), pathToJar);
Class myClass = loader.loadClass("MainClassOfTheFeature");
Method mainMethod = myClass.getMethod("MainMethod");
mainMethod.invoke(myClass.getConstructor().newInstance());

Jvm still uses the classes it loaded from the older version. How can I make the JVM to load the classes from my class loader when running the feature and use the already loaded older classes in the older jar when the feature is not running?

Edit: The problem remains even after setting the custom classloader as the Thread context classloader in the MainMethod of the feature Main class.

Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());

回答1:

I managed to solve this problem. Modified the code of the ParentLastClassLoader to get an array of all the Jarfile paths which are needed by the feature. So when a class is loaded, all the jarfiles needed by the feature will be searched for the .class files. If a class file cannot be found, it will be delegated to the parent.

    private  class ParentLastClassLoader extends ClassLoader {

    private String[] jarFiles; //Paths to the jar files
    private Hashtable classes = new Hashtable(); //used to cache already defined classes

    public ParentLastClassLoader(ClassLoader parent, String[] paths)
    {
        super(parent);
        this.jarFiles = paths;
    }

    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException
    {
        System.out.println("Trying to find");
        throw new ClassNotFoundException();
    }

    @Override
    protected synchronized Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException
    {
        System.out.println("Trying to load");
        try
        {
            System.out.println("Loading class in Child : " + className);
            byte classByte[];
            Class result = null;

            //checks in cached classes
            result = (Class) classes.get(className);
            if (result != null) {
                return result;
            }

            for(String jarFile: jarFiles){
                try {
                    JarFile jar = new JarFile(jarFile);
                    JarEntry entry = jar.getJarEntry(className.replace(".","/") + ".class");
                    InputStream is = jar.getInputStream(entry);
                    ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
                    int nextValue = is.read();
                    while (-1 != nextValue) {
                        byteStream.write(nextValue);
                        nextValue = is.read();
                    }

                    classByte = byteStream.toByteArray();
                    result = defineClass(className, classByte, 0, classByte.length, null);
                    classes.put(className, result);
                } catch (Exception e) {
                    continue;
                }
            }

            result = (Class) classes.get(className);
            if (result != null) {
                return result;
            }
            else{
                throw new ClassNotFoundException("Not found "+ className);
            }
        }
        catch( ClassNotFoundException e ){

            System.out.println("Delegating to parent : " + className);
            // didn't find it, try the parent
            return super.loadClass(className, resolve);
        }
    }
}

The ParentLastClassLoader is instantiated as follows.

ClassLoader loader = new ParentLastClassLoader(Thread.currentThread().getContextClassLoader(), paths);

Once the ParentLastClassLoader is instantiated, the MainClassOfTheFeature will be loaded and its MainMethod will be invoked.



回答2:

Ok, you created your own classloader and then loaded a class using it. The question is - how does the thread classloader will know about that? So, you must load the class using some classloader, and then set this classloader as thread context classloader.