2014-09-08 3 views
7

나는 외부 클래스 경로가 추가 된 jar를 빌드하기 위해 maven을 사용했으며, addClasspath을 사용했다.클래스 로더가 매니페스트 클래스 경로에서 클래스 참조를로드하는 방법은 무엇입니까?

java -jar artifact.jar을 사용하여 해당 jar를 실행할 때 해당 jar와 libs 디렉토리의 모든 jar에서 클래스를로드 할 수 있습니다.

그러나 시스템 속성 인 java.class.path을 요청하면 기본 항아리 만 표시됩니다. 시스템 클래스 로더에 URL (ClassLoader.getSystemClassLoader().getURLs())을 요청하면 기본 항아리 만 반환됩니다. 클래스 로더에 대해 라이브러리에 포함 된 클래스에 요청하면 시스템 클래스 로더가 반환됩니다.

시스템 클래스 로더는 어떻게 이러한 클래스를로드 할 수 있습니까?

클래스를로드하려면 해당 라이브러리에 대한 지식이 있어야합니다. 이런 종류의 "확장 된"classpath를 요청하는 방법이 있습니까?

+0

병을 열고 무슨 일이 벌어지고 있는지 확인하기 위해 생성 된 매니페스트를 볼 수 있습니까? – gandaliter

+0

매니페스트에는 libs 디렉토리의 모든 jar를 나열하는 클래스 경로 항목이 있습니다. - 예상대로. – michas

답변

5

간단한 대답은 구현이 Sun의 내부 작동의 일부이며 공용 수단을 통해 사용할 수 없다는 것입니다. getURLs()은 전달 된 URL 만 반환합니다. 대답은 더 길지만 대담한 것입니다.

디버거를 사용하여 Oracle JVM 8을 실행하면 OpenJDK6과 거의 동일한 구조로 안내되고 클래스 경로 here이로드되는 위치를 확인할 수 있습니다.

기본적으로 클래스 로더는 아직 구문 분석하지 않은 URL 스택을 메모리에 보관합니다. 클래스를로드하라는 요청을 받으면 스택에서 URL을 팝핑하여 클래스 파일 또는 jar 파일로로드하고 jar 파일 인 경우 매니페스트를 읽고 클래스 경로 항목을 스택에 푸시합니다. 파일을 처리 할 때마다 해당 파일을 로더 맵에로드 한 "로더"를 추가합니다 (다른 파일이없는 경우 동일한 파일을 여러 번 처리하지 못하도록합니다).

당신이 (그것을 권하고 싶지 않다) 할 정말 동기 경우이지도에 액세스 할 수 있습니다

: external.jar을 참조 I 더미-plugin.jar이 더미 설정에 그 실행

 Field secretField = URLClassLoader.class.getDeclaredField("ucp"); 
     secretField.setAccessible(true); 
     Object ucp = secretField.get(loader); 
     secretField = ucp.getClass().getDeclaredField("lmap"); 
     secretField.setAccessible(true); 
     return secretField.get(ucp); 

(더미-plugin.jar의 매니페스트에) 나는 다음 얻을 :

1) 즉시 클래스 로더를 만든 후 (모든 클래스) 넣기 전에 : 후

urlClassLoader.getURLs()=[file:.../dummy-plugin.jar] 
getSecretUrlsStack=[file:.../dummy-plugin.jar] 
getSecretLmapField={} 

2) 더미-plugin.jar에서 클래스를로드 :

urlClassLoader.getURLs()=[file:.../dummy-plugin.jar] 
getSecretUrlsStack=[file:.../external.jar] 
getSecretLmapField={file:.../[email protected]} 

3) external.jar에서 클래스를로드 한 후 :

urlClassLoader.getURLs()=[file:.../dummy-plugin.jar] 
getSecretUrlsStack=[] 
getSecretLmapField={file:.../[email protected], file:.../[email protected]} 

이상하게도이이 JDK for URLClassLoader의 얼굴에 비행 보인다

로드되는 클래스는 기본적으로 URLClassLoader를 만들 때 지정된 URL에 에 액세스 할 수있는 권한 만 부여됩니다.시스템 클래스 로더 인스턴스의 private 필드에 액세스하는 반사를 사용

+0

좋은 해킹! 보안 관리자가있을 때 문제를 나타낼 수 있습니다. – idelvall

2

몇 가지 문제 제시 :

  • 기탁은 보안 관리자
  • 솔루션에 의해 금지 될 수 의존 구현입니다

덜 침입적인 해결책은 다음과 같습니다.

  1. 주어진 클래스 로더에 대해 사용 가능한 모든 매니페스트를 열거합니다. cl.getResources("META-INF/MANIFEST.MF"). 이러한 매니페스트는 현재 클래스 로더 또는 ascendants 클래스 로더가 관리하는 jar 일 수 있습니다.
  2. 는 부모 클래스 로더에 대한 매니페스트 사람들의 항아리의
  3. 반환 세트를 동일한 작업을 수행 (1)하지만 (2)

작업이 방법에 대한 유일한 요구 사항은 아닙니다에 그 항아리 classpath는 반드시 반환되어야하는 매니페스트를 가져야합니다.

/** 
* Returns the search path of URLs for loading classes and resources for the 
* specified class loader, including those referenced in the 
* {@code Class-path} header of the manifest of a executable jar, in the 
* case of class loader being the system class loader. 
* <p> 
* Note: These last jars are not returned by 
* {@link java.net.URLClassLoader#getURLs()}. 
* </p> 
* @param cl 
* @return 
*/ 
public static URL[] getURLs(URLClassLoader cl) { 
    if (cl.getParent() == null || !(cl.getParent() 
      instanceof URLClassLoader)) { 
     return cl.getURLs(); 
    } 
    Set<URL> urlSet = new LinkedHashSet(); 
    URL[] urLs = cl.getURLs(); 
    URL[] urlsFromManifest = getJarUrlsFromManifests(cl); 
    URLClassLoader parentCl = (URLClassLoader) cl.getParent(); 
    URL[] ancestorUrls = getJarUrlsFromManifests(parentCl); 

    for (int i = 0; i < urlsFromManifest.length; i++) { 
     urlSet.add(urlsFromManifest[i]); 
    } 
    for (int i = 0; i < ancestorUrls.length; i++) { 
     urlSet.remove(ancestorUrls[i]); 
    } 
    for (int i = 0; i < urLs.length; i++) { 
     urlSet.add(urLs[i]); 
    } 
    return urlSet.toArray(new URL[urlSet.size()]); 
} 

/** 
* Returns the URLs of those jar managed by this classloader (or its 
* ascendant classloaders) that have a manifest 
* @param cl 
* @return 
*/ 
private static URL[] getJarUrlsFromManifests(ClassLoader cl) { 
    try { 
     Set<URL> urlSet = new LinkedHashSet(); 
     Enumeration<URL> manifestUrls = 
       cl.getResources("META-INF/MANIFEST.MF"); 
     while (manifestUrls.hasMoreElements()) { 
      try { 
       URL manifestUrl = manifestUrls.nextElement(); 
       if(manifestUrl.getProtocol().equals("jar")) { 
        urlSet.add(new URL(manifestUrl.getFile().substring(0, 
          manifestUrl.getFile().lastIndexOf("!")))); 
       } 
      } catch (MalformedURLException ex) { 
       throw new AssertionError(); 
      } 
     } 
     return urlSet.toArray(new URL[urlSet.size()]); 
    } catch (IOException ex) { 
     throw new RuntimeException(ex); 
    } 
} 
관련 문제