簡體   English   中英

類加載器如何在清單類路徑中加載類引用?

[英]How does a classloader load classes reference in the manifest classpath?

我使用maven來構建一個帶有外部類路徑添加的jar,使用addClasspath

當我使用java -jar artifact.jar運行該jar時,它能夠從該主jar和libs目錄中的所有jar加載類。

但是,如果我問系統屬性java.class.path它只會列出主jar。 如果我向系統類加載器詢問其url( ClassLoader.getSystemClassLoader().getURLs() ),它也只會返回主jar。 如果我問一些庫中包含的任何類的類加載器,它將返回系統類加載器。

系統類加載器如何加載這些類?

它必須對這些庫有一些了解才能從這些庫中加載類。 有沒有辦法要求它提供這種“擴展”類路徑?

簡短的回答是,實施是Sun內部工作的一部分,而不是通過公共手段提供。 getURLs()只返回傳入的URL。有一個更長的答案,但它只適用於大膽。

使用調試器逐步調試Oracle JVM 8使我通過與OpenJDK6完全相同的結構,您可以在此處查看它在何處加載類路徑。

基本上,類加載器會保留一堆尚未解析到內存中的URL。 當被要求加載一個類時,它將從堆棧中彈出URL,將它們作為類文件或jar文件加載,如果它們是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);

在虛擬設置上運行,我有dummy-plugin.jar引用external.jar(在dummy-plugin.jar的清單中)我得到以下內容:

1)創建類加載器之后(在加載任何類之前):

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

2)從dummy-plugin.jar加載一個類之后:

urlClassLoader.getURLs()=[file:.../dummy-plugin.jar]
getSecretUrlsStack=[file:.../external.jar]
getSecretLmapField={file:.../dummy-plugin.jar=sun.misc.URLClassPath$JarLoader@736e9adb}

3)從external.jar加載一個類后:

urlClassLoader.getURLs()=[file:.../dummy-plugin.jar]
getSecretUrlsStack=[]
getSecretLmapField={file:.../dummy-plugin.jar=sun.misc.URLClassPath$JarLoader@736e9adb, file:.../external.jar=sun.misc.URLClassPath$JarLoader@2d8e6db6}

奇怪的是,這似乎是面對URLClassLoaderJDK

默認情況下,加載的類僅被授予訪問創建URLClassLoader時指定的URL的權限。

使用反射訪問系統類加載器實例中的私有字段會出現幾個問題:

  • 安全管理員可以禁止加入
  • 解決方案取決於實現

另一種不太“干擾”的解決方案是:

  1. 對於給定的類加載器,枚舉所有可用的cl.getResources("META-INF/MANIFEST.MF") 這些清單可以是由當前類加載器或其上級類加載器管理的jar。
  2. 對其父類加載器執行相同操作
  3. 返回(1)中顯示的那些罐子,但不返回(2)

此方法的唯一要求是,類路徑中的jar必須有一個清單才能返回(不多問)。

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM