2017-04-21 13 views
0

런타임시 동일한 라이브러리의 실행 차이점을 테스트해야하지만 다른 버전은 테스트해야합니다. 따라서 동일한 패키지 이름을 가진 많은 클래스를로드해야합니다. 전체 실행은 모든 나머지가 종속성으로있는 하나의 클래스에서만 시작됩니다.동일한 라이브러리의 다른 버전을 JVM에 병렬로로드하십시오.

라이브러리 # 1을 프로젝트 파일 (예 : ClassPath 클래스 로더)로로드하려고 시도했으며 라이브러리 # 2를 jar로로드하고 클래스를 UrlClassLoader로로드하려고했습니다.

UrlClassLoader에서 클래스를로드 할 때 문제가 발생합니다. 모든 종속 클래스는 ClassPath 클래스 로더가 이미로드 한 라이브러리 # 1에서 가져옵니다.

나는 클래스 로더 폼을 부트 스트랩 클래스 로더에서 시작하여 사용자 정의 클래스 로더를 끝내는 것으로 안다.하지만 자바가 명시 적으로 언급 된 클래스뿐만 아니라 커스텀의 모든 의존성 트리를로드하도록 만들 수있다. 클래스 로더?

+0

나는 잘 모르겠다.하지만 나는 문제가 있기 때문에 업데이트를 유지하기를 원한다. 미안 해요! – Zorglube

답변

1

URLClassLoader을 사용하여 parent: null을 전달하고 리플렉션을 사용하여 멤버를 인스턴스화하여 클래스 경로 및 나머지 버전에서 하나의 버전을로드 할 수 있습니다. 내가 this Git repo이에 대한 POC를 만들어

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

:

대안처럼 그 보일 수 있습니다 here을 설명 접근 방식을 기반으로 사용자 정의 클래스 로더를 사용하는 것입니다. 자세한 내용은 README.md에 있습니다.

POC 내용은 여러 버전 (프로젝트 V1, V2, V3) 다음 조각으로 구성된 도서관이 점을 고려

:이 인터페이스를 가지고

우리가 원하는 우리의 라이브러리 :

0,123,636 :에 의해 구현된다
public interface Core { 

    String getVersion(); 
    String getDependencyVersion(); 
} 

3,975,142,674,234,255,819,633,210 다른 동일한 라이브러리의 클래스를 사용

:

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. V3 - - 파일
  2. V2에서 -
  3. V1 클래스 경로

의 코드는 다음과 같습니다

// 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 클래스 로더 (즉, 프로젝트 소스의 일부)와 다른 하나가 jar로로드 된 하나의 lib를 가질 필요가 있습니다. UrlClassLoader가로드 된 lib에서 종속성을 가져 와서 JAR을 먼저 조사하는 것을 방지하는 방법이 있습니까? –

+0

@PaulJ. 끝난! 클래스 경로에서로드 된 V3를 포함하도록 POC를 업데이트하여 파일에서로드 할 V1 및 V2를 남겨 둡니다. – andreim

+0

@PaulJ. URL에서 첫 번째 클래스를로드하고 상위 클래스 로더에서 찾지 못한 경우 사용자 정의 클래스 로더를 포함하도록 해답을 다시 편집했습니다. 이것이 당신의 시나리오를 해결합니까? – andreim