[英]Java How to load classes out of a jar in the classpath with the System ClassLoader (no URLClassLoader)?
[英]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}
奇怪的是,這似乎是面對URLClassLoader的JDK :
默認情況下,加載的類僅被授予訪問創建URLClassLoader時指定的URL的權限。
使用反射訪問系統類加載器實例中的私有字段會出現幾個問題:
另一種不太“干擾”的解決方案是:
cl.getResources("META-INF/MANIFEST.MF")
。 這些清單可以是由當前類加載器或其上級類加載器管理的jar。 此方法的唯一要求是,類路徑中的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.