在JVM中并行加载同一库的不同版本

时间:2017-04-21 11:05:51

标签: java jvm classloader urlclassloader

我需要测试同一个库的执行之间的差异,但不同的版本 - 以及运行时。因此,我需要加载具有相同包名称的批次类。 整个执行只从一个类开始,其余的作为其依赖项。

我尝试将库#1作为项目​​文件(即通过ClassPath类加载器)加载,将库#2加载为jar,并通过UrlClassLoader加载其类。

问题是当我从UrlClassLoader加载一个类时 - 所有依赖类都来自库#1,它们已经被ClassPath类加载器加载。

我知道类加载器形成一个从Bootstrap类加载器开始然后结束自定义类加载器的层次结构 - 但是你可以通过哪种方式使Java加载不仅一个明确提到的类,而且来自自定义类的所有依赖树装载机?

1 个答案:

答案 0 :(得分:1)

您可以通过传递URLClassLoader并使用反射来实例化成员,从而使用parent: null从类路径和其他版本加载一个版本。

另一种方法是使用自定义类加载器,基于here描述的方法,如下所示:

import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandlerFactory;

public class ParentLastClassLoader extends ClassLoader {

    private ClassLoader parentClassLoader;
    private URLClassLoader noParentClassLoader;

    public ParentLastClassLoader(URL[] urls, ClassLoader parent) {
        super(parent);
        this.noParentClassLoader = new URLClassLoader(urls, null);
    }

    public ParentLastClassLoader(URL[] urls) {
        super(Thread.currentThread().getContextClassLoader());
        this.noParentClassLoader = new URLClassLoader(urls, null);
    }

    public ParentLastClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) {
        super(parent);
        this.noParentClassLoader = new URLClassLoader(urls, null, factory);
    }

    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        try {
            // resolve using child class loader
            return noParentClassLoader.loadClass(name);
        } catch (ClassNotFoundException ex) {
            // resolve using parent class loader
            return super.loadClass(name, resolve);
        }
    }
}

我在this Git repo为此创建了一个POC。细节在README.md。

POC内容

考虑到您有一个包含多个版本的库(项目v1,v2,v3 ),其中包含以下部分:

我们希望我们的库有这个界面:

public interface Core {

    String getVersion();
    String getDependencyVersion();
}

由以下人员实施:

package sample.multiversion;

import sample.multiversion.deps.CoreDependency;

public class ImportantCore implements Core {

    private Utility utility;
    private CoreDependency coreDependency;

    public ImportantCore() {
        utility = new Utility();
        coreDependency = new CoreDependency();
    }

    public String getVersion() {
        return utility.getVersion();
    }

    public String getDependencyVersion() {
        return coreDependency.getVersion();
    }
}

正在使用同一个库的另一个类:

package sample.multiversion;

public class Utility {

    public String getVersion() {
        return "core-v1";
    }
}

最后,库(项目v1,v2,v3 )具有依赖项(项目v1dep,v2dep,v3dep ),其中包含:

package sample.multiversion.deps;

public class CoreDependency {

    public String getVersion() {
        return "core-dep-v1";
    }
}

然后我们可以加载所有三个版本:

  1. V1 - 来自档案
  2. V2 - 来自档案
  3. V3 - 来自classpath
  4. 代码如下:

        // multiple versions of the same library to be used at the same time
        URL v1 = Paths.get("./../v1/build/libs/v1.jar").toUri().toURL();
        URL v2 = Paths.get("./../v2/build/libs/v2.jar").toUri().toURL();
    
        // library dependencies
        URL v1Dep = Paths.get("./../v1dep/build/libs/v1dep.jar").toUri().toURL();
        URL v2Dep = Paths.get("./../v2dep/build/libs/v2dep.jar").toUri().toURL();
    
        /**
         * version 1 and 2 loaders
         * - these loaders do not use the root loader - Thread.currentThread().getContextClassLoader()
         * - using the root loader new URLClassLoader(new URL[]{v1, v1Dep}, Thread.currentThread().getContextClassLoader());
         * will solve any class with the root loader and if no class is found then the child loader will be used
         * - because version 3 is loaded from classpath, the root loader should not be used to load version 1 and 2
         * - null needs to be passed to parent argument, else will not work
         */
        URLClassLoader loaderV1 = new URLClassLoader(new URL[]{v1, v1Dep}, null);
        URLClassLoader loaderV2 = new URLClassLoader(new URL[]{v2, v2Dep}, null);
    
        /**
         * Use custom class loader for loading classes first from children and last from parent
         */
        ParentLastClassLoader loaderV1Alt = new ParentLastClassLoader(new URL[]{v1, v1Dep});
        ParentLastClassLoader loaderV2Alt = new ParentLastClassLoader(new URL[]{v2, v2Dep});
        ParentLastClassLoader loaderV3Alt = new ParentLastClassLoader(new URL[]{});
    
        // get class from loader
        Class<?> coreV1Class = loaderV1.loadClass("sample.multiversion.ImportantCore");
        Class<?> coreV2Class = loaderV2.loadClass("sample.multiversion.ImportantCore");
    
        // get class from loader - custom version
        Class<?> coreV1AltClass = loaderV1Alt.loadClass("sample.multiversion.ImportantCore");
        Class<?> coreV2AltClass = loaderV2Alt.loadClass("sample.multiversion.ImportantCore");
        Class<?> coreV3AltClass = loaderV3Alt.loadClass("sample.multiversion.ImportantCore");
    
        // create class instance
        Object coreV1Instance = coreV1Class.newInstance();
        Object coreV2Instance = coreV2Class.newInstance();
    
        // create class instance - obtained from custom class loader
        Object coreV1AltInstance = coreV1AltClass.newInstance();
        Object coreV2AltInstance = coreV2AltClass.newInstance();
    
        // note that this is loaded from classpath
        Core coreV3Instance = new ImportantCore();
        Core coreV3AltInstance = (Core)coreV3AltClass.newInstance();
    
        // get version
        String v1Str = (String) coreV1Class.getMethod("getVersion").invoke(coreV1Instance);
        String v2Str = (String) coreV2Class.getMethod("getVersion").invoke(coreV2Instance);
        String v1AltStr = (String) coreV1AltClass.getMethod("getVersion").invoke(coreV1AltInstance);
        String v2AltStr = (String) coreV2AltClass.getMethod("getVersion").invoke(coreV2AltInstance);
    
        String v3Str = coreV3Instance.getVersion();
        String v3AltStr = coreV3AltInstance.getVersion();
    
        // get version of dependency
        String v1DepStr = (String) coreV1Class.getMethod("getDependencyVersion").invoke(coreV1Instance);
        String v2DepStr = (String) coreV2Class.getMethod("getDependencyVersion").invoke(coreV2Instance);
        String v1AltDepStr = (String) coreV1AltClass.getMethod("getDependencyVersion").invoke(coreV1AltInstance);
        String v2AltDepStr = (String) coreV2AltClass.getMethod("getDependencyVersion").invoke(coreV2AltInstance);
    
        String v3DepStr = coreV3Instance.getDependencyVersion();
        String v3AltDepStr = coreV3AltInstance.getDependencyVersion();
    
        System.out.println(String.format("V1 loader :: version = '%s' :: dependency_version = '%s'", v1Str, v1DepStr));
        System.out.println(String.format("V2 loader :: version = '%s' :: dependency_version = '%s'", v2Str, v2DepStr));
        System.out.println(String.format("V3 loader :: version = '%s' :: dependency_version = '%s'", v3Str, v3DepStr));
    
        System.out.println(String.format("V1 custom loader :: version = '%s' :: dependency_version = '%s'", v1AltStr, v1AltDepStr));
        System.out.println(String.format("V2 custom loader :: version = '%s' :: dependency_version = '%s'", v2AltStr, v2AltDepStr));
        System.out.println(String.format("V3 custom loader :: version = '%s' :: dependency_version = '%s'", v3AltStr, v3AltDepStr));
    

    注意: Core接口可以是项目v1, v2, v3使用的另一个项目的一部分,这可以允许我们转换新创建的实例并在类型安全中工作方式。但这可能不是每次都可行。