2017-04-21 42 views
0

我需要測試同一個庫的不同版本之間的不同版本 - 以及運行時。因此我需要加載很多具有相同包名的類。 整個執行只從一個類中開始,其餘所有類都依賴它。在JVM中並行加載同一個庫的不同版本

我試圖加載庫#1作爲項目​​文件(即通過ClassPath類加載器)和庫#2作爲jar並通過UrlClassLoader加載它的類。

問題是當我從UrlClassLoader加載一個類時 - 所有相關類都從庫#1中獲取,它們已經由ClassPath Class加載器加載。

我知道類加載器從Bootstrap類加載器開始,然後結束您的自定義類加載器 - 但是您可以使Java加載的不僅僅是一個明確提到的類,而是它自定義的所有依賴樹類加載器?

+0

我不知道,但我留言更新,因爲這個問題讓我感興趣。對不起朋友! – Zorglube

回答

1

您可以從classpath加載一個版本,其餘版本使用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中

的代碼將是:

// 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使用,這可以讓我們投射新創建的實例並以類型安全的方式工作。但這可能不是每次都可行的。

+0

的確聽起來不錯。但有時(和我一樣),我們需要一個由Classpath classloader加載的lib(即項目源的一部分),另一個 - 作爲jar。有沒有辦法阻止UrlClassLoader從加載的庫中獲取依賴關係並首先查看JAR? –

+0

@PaulJ。完成了!我更新了POC以包含從classpath加載的V3,從文件加載V1和V2。 – andreim

+0

@PaulJ。重新編輯答案以包含一個自定義類加載器,該加載器將從URL加載第一個類,如果未從父類加載器中找到。這是否解決你的情況? – andreim