Java類加載器ClassLoader的使用詳解
BootstrapClassLoader
加載范圍(根據(jù)系統(tǒng)參數(shù)):
System.getProperty("sun.boot.class.path");
負責加載核心類庫,以我的本地的環(huán)境來展示獲取的內(nèi)容:
D:\development\jdk\jdk8\jre\lib\resources.jar;
D:\development\jdk\jdk8\jre\lib\rt.jar;
D:\development\jdk\jdk8\jre\lib\sunrsasign.jar;
D:\development\jdk\jdk8\jre\lib\jsse.jar;
D:\development\jdk\jdk8\jre\lib\jce.jar;
D:\development\jdk\jdk8\jre\lib\charsets.jar;
D:\development\jdk\jdk8\jre\lib\jfr.jar;
D:\development\jdk\jdk8\jre\classes;
C:\Users\18140\AppData\Local\JetBrains\Toolbox\apps\IDEA-U\ch-0\222.3345.118\plugins\java\lib\rt\debugger-agent.jar
修改加載范圍:
- -Xbootclasspath: 重新設定核心類庫的搜索路徑,不建議使用。
- -Xbootclasspath/a: 在已有的-XBootclasspath后面追加搜索路徑。較常用。
- -Xbootclasspath/p: 在已有的-XBootclasspath前面追加搜索路徑。不常用,因為可能會引起依賴丟失。
例(:
號賦參數(shù)值):
java -Xbootclasspath/a:D:\XXXFolder; ProgramName
ExtClassLoader
加載范圍(根據(jù)系統(tǒng)參數(shù)):
System.getProperty("java.ext.dirs");
負責加載擴展類庫,以我的本地的環(huán)境來展示獲取的內(nèi)容:
D:\development\jdk\jdk8\jre\lib\ext;
C:\Windows\Sun\Java\lib\ext
修改加載范圍,例(=
號賦參數(shù)值):
java -Djava.ext.dirs=D:\XXXFolder; ProgramName
AppClassLoader
加載范圍(根據(jù)系統(tǒng)參數(shù)):
System.getProperty("java.class.path");
負責加載應用程序的類與類庫,其實就是classpath下的內(nèi)容。以我的本地的環(huán)境來展示獲取的內(nèi)容:
.......
D:\development\jdk\jdk8\jre\lib\rt.jar;
D:\development\idea\workspace\personal\resources-operation\target\classes;
D:\development\maven\apache-maven-resp\org\springframework\boot\spring-boot-starter-web\2.3.7.RELEASE\spring-boot-starter-web-2.3.7.RELEASE.jar;
.......
修改加載范圍,例(空格后賦參數(shù)值):
java -classpath D:\XXXFolder; ProgramName
// 簡寫
java -cp D:\XXXFolder; ProgramName
類加載器的具體實現(xiàn)在哪里
Ext、AppClassLoader類加載器在sun.misc.Launcher中,通過內(nèi)部靜態(tài)類的形式進行了實現(xiàn)。
由于BootstrapClassLoader是非java語言實現(xiàn)的,故沒有對應的內(nèi)部靜態(tài)類,但存有一個BootClassPathHolder靜態(tài)內(nèi)部類在Launcher中。
public class Launcher { static class AppClassLoader extends URLClassLoader {} static class ExtClassLoader extends URLClassLoader {} private static class BootClassPathHolder {} }
類加載器的初始化時機
public class Launcher { public Launcher() { ExtClassLoader var1; try { // 1. 創(chuàng)建ExtClassLoader var1 = Launcher.ExtClassLoader.getExtClassLoader();// 68 } catch (IOException var10) {// 69 throw new InternalError("Could not create extension class loader", var10);// 70 } try { // 2. 創(chuàng)建AppClassLoader,并將ExtClassLoader作為ParentClassLoader this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);// 76 } catch (IOException var9) {// 77 throw new InternalError("Could not create application class loader", var9);// 78 } // 3. 將AppClassLoader掛載到當前線程 Thread.currentThread().setContextClassLoader(this.loader);// 83 // ...... } }
如何進行的類加載
不同的類加載器會根據(jù)不同的系統(tǒng)參數(shù),獲取不同的一組掃描路徑。并根據(jù)掃描路徑(Path)的類型(文件夾、Jar包),
維護一組Path與Loader實例在類加載器的父類URLClassLoader的ucp(URLClassPath)字段中。
當我們通過類加載器的loadClass方法加載類時,真正依賴的便是loader。
package sun.misc; public class URLClassPath { private static class Loader implements Closeable {} static class JarLoader extends Loader {} private static class FileLoader extends Loader {} }
那么,當我們通過lodeClass時,
public static void main(String[] args) throws ClassNotFoundException { // 1. 重新設定ClassPath System.setProperty("java.class.path", "D:\\development\\idea\\workspace\\personal\\resources-operation\\target\\classes"); // 2. 重新初始化Launcher Launcher launcher = new Launcher(); // 3. 獲取AppClassLoader ClassLoader classLoader = launcher.getClassLoader(); // 4. loadClass Class<?> aClass = classLoader.loadClass(ResourcesOperationApplication.class.getName()); System.out.println(aClass.getName()); }
是如何落實到loader的呢?以AppClassLoader為例:
static class AppClassLoader extends URLClassLoader { final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this); // 獲取AppClassLoader實例 public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException { // 1. 讀取classpath系統(tǒng)參數(shù) final String var1 = System.getProperty("java.class.path"); // 2. 轉(zhuǎn)為File實例 final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1); return (ClassLoader) AccessController.doPrivileged(new PrivilegedAction<AppClassLoader>() { public AppClassLoader run() { // 3. File轉(zhuǎn)為URL URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2); // 3. 創(chuàng)建實例 return new AppClassLoader(var1x, var0); } }); } AppClassLoader(URL[] var1, ClassLoader var2) { // 4. 初始化父類,并傳遞了一組URL實例 super(var1, var2, Launcher.factory); this.ucp.initLookupCache(this); } // ...... } public class URLClassLoader extends SecureClassLoader implements Closeable { /* The search path for classes and resources */ private final URLClassPath ucp; /* The context to be used when loading classes and resources */ private final AccessControlContext acc; public URLClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) { super(parent); SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkCreateClassLoader(); } acc = AccessController.getContext(); // 5. 將URL實例封裝到URLClassPath中 ucp = new URLClassPath(urls, factory, acc); } } public class URLClassPath { private ArrayList<URL> path; Stack<URL> urls; ArrayList<Loader> loaders; HashMap<String, Loader> lmap; private URLStreamHandler jarHandler; private boolean closed; public URLClassPath(URL[] var1, URLStreamHandlerFactory var2, AccessControlContext var3) { this.path = new ArrayList();// 101 this.urls = new Stack();// 104 this.loaders = new ArrayList();// 107 this.lmap = new HashMap();// 110 this.closed = false;// 116 // 6. 將URL封裝到內(nèi)部的path中 for(int var4 = 0; var4 < var1.length; ++var4) {// 138 this.path.add(var1[var4]);// 139 } this.push(var1);// 141 if (var2 != null) {// 142 this.jarHandler = var2.createURLStreamHandler("jar");// 143 } if (DISABLE_ACC_CHECKING) {// 145 this.acc = null;// 146 } else { this.acc = var3;// 148 } } }
但是并沒有發(fā)現(xiàn)關于將URL轉(zhuǎn)為loader的代碼,那么loader是何時創(chuàng)建的?以上面的測試代碼為切入口:
Class<?> aClass = classLoader.loadClass(ResourcesOperationApplication.class.getName());
直接定位到核心方法,前繼的非核心方法就跳過了
public abstract class ClassLoader { protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); // 父系委托機制 try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { } if (c == null) { // ... // 查找類資源 c = findClass(name); // ... } // ... } return c; } } protected Class<?> findClass(final String name) throws ClassNotFoundException { final Class<?> result; try { result = AccessController.doPrivileged( new PrivilegedExceptionAction<Class<?>>() { public Class<?> run() throws ClassNotFoundException { // 1. 將類的限定名轉(zhuǎn)為 '/' 分隔的形式,例:life.cqq.xxx.ClassName -> life/cqq/xxx/ClassName.class String path = name.replace('.', '/').concat(".class"); // 2. 通過URLClassPath獲取類資源 Resource res = ucp.getResource(path, false); if (res != null) { try { // 最終根據(jù)類名 & 已加載的類資源創(chuàng)建Class對象 return defineClass(name, res); } catch (IOException e) { throw new ClassNotFoundException(name, e); } catch (ClassFormatError e2) { if (res.getDataError() != null) { e2.addSuppressed(res.getDataError()); } throw e2; } } else { return null; } } }, acc); } catch (java.security.PrivilegedActionException pae) { throw (ClassNotFoundException) pae.getException(); } if (result == null) { throw new ClassNotFoundException(name); } return result; } } public class URLClassPath { public Resource getResource(String var1, boolean var2) { // ...... Loader var3; // 3. getNextLoader:獲取可以處理類資源的loader for (int var5 = 0; (var3 = this.getNextLoader(var4, var5)) != null; ++var5) {// 249 // 5. 通過獲取到的loader獲取類資源 Resource var6 = var3.getResource(var1, var2);// 250 if (var6 != null) {// 251 return var6;// 252 } } return null;// 255 } // getNextLoader最終會調(diào)用到該方法 private Loader getLoader(final URL var1) throws IOException { try { return (Loader) AccessController.doPrivileged(new PrivilegedExceptionAction<Loader>() {// 565 public Loader run() throws IOException { String var1x = var1.getFile();// 568 // 4. 最終的判斷:根據(jù)URL創(chuàng)建不同的loader并返回 // url.endsWith("/") && "file".equals(var1.getProtocol() ? FileLoader : JarLoader if (var1x != null && var1x.endsWith("/")) {// 569 return (Loader) ("file".equals(var1.getProtocol()) ? new FileLoader(var1) : new Loader(var1));// 570 } else { return new JarLoader(var1, URLClassPath.this.jarHandler, URLClassPath.this.lmap, URLClassPath.this.acc);// 576 } } }, this.acc); } catch (PrivilegedActionException var3) {// 580 throw (IOException) var3.getException();// 581 } } }
可以發(fā)現(xiàn),loader其實是懶加載的。僅有當已有l(wèi)oader無法加載時,會從path列表中讀取仍未被創(chuàng)建loader的path并創(chuàng)建loader。
總結(jié)一下:
類加載器會根據(jù)系統(tǒng)參數(shù)創(chuàng)建對應的適配類型的Loader(FileLoader | JarLoader),且Loader是懶加載的。并維護在父類URLClassLoader的ucp(URLClassPath)屬性中的loaders列表中。
loadClass時,最終會執(zhí)行getResource方法,迭代Loader實例集合,嘗試獲取資源實例。最終根據(jù)類名 & 已加載的類資源在defineClass中創(chuàng)建Class對象。
Loader.getResource(resourceName)
這個方法有必要說明一下,為后繼獲取資源文件問題做下鋪墊。方法是用法就是獲取資源,在loadClass方法時獲取類文件,類文件抽象來說也是一種資源,只是loadClass方法中多了一些關于類的處理。
當我們獲取的非類文件資源,而是其他資源,如不同類型的配置文件,那么這些資源在哪里,如何加載,是需要說清的兩個問題。
- where問題的答案其實就是當前類加載器能夠搜索的范圍是什么,即設定的系統(tǒng)參數(shù)。
- 如何加載說的也是前面提到的loader,不同的loader(file、jar)處理不同的內(nèi)容。比如FileLoader就會判斷資源是否在目錄下,那么JarLoader便是判斷資源是否在Jar包中。
public class URLClassPath { public Resource getResource(String var1, boolean var2) { // ...... Loader var3; for (int var5 = 0; (var3 = this.getNextLoader(var4, var5)) != null; ++var5) {// 249 // 0. 通過獲取到的loader獲取類資源 Resource var6 = var3.getResource(var1, var2);// 250 if (var6 != null) {// 251 return var6;// 252 } } return null;// 255 } // 1. 文件加載器 private static class FileLoader extends Loader { private File dir; // ... Resource getResource(final String var1, boolean var2) { try { URL var4 = new URL(this.getBaseURL(), ".");// 1370 final URL var3 = new URL(this.getBaseURL(), ParseUtil.encodePath(var1, false));// 1371= if (!var3.getFile().startsWith(var4.getFile())) {// 1373 return null;// 1375 } else { if (var2) {// 1378 URLClassPath.check(var3);// 1379 } // 1) 根據(jù)loader對應的文件目錄,拼接資源路徑參數(shù)構(gòu)建File實例 final File var5; if (var1.indexOf("..") != -1) {// 1382 var5 = (new File(this.dir, var1.replace('/', File.separatorChar))).getCanonicalFile();// 1383 1384 if (!var5.getPath().startsWith(this.dir.getPath())) {// 1385 return null;// 1387 } } else { var5 = new File(this.dir, var1.replace('/', File.separatorChar));// 1390 } // 2) 若File實例映射的文件存在,則返回Resource實例,否則返回空 return var5.exists() ? new Resource() {// 1393 // ... } : null;// 1407 } } catch (Exception var6) {// 1404 return null;// 1405 } } } // 2. Jar加載器 static class JarLoader extends Loader { Resource getResource(String var1, boolean var2) { if (this.metaIndex != null && !this.metaIndex.mayContain(var1)) {// 1059 1060 return null;// 1061 } else { try { this.ensureOpen();// 1066 } catch (IOException var5) {// 1067 throw new InternalError(var5);// 1068 } // 1) 若loader對應Jar包中,存有資源名稱的節(jié)點,則返回資源節(jié)點內(nèi)容 JarEntry var3 = this.jar.getJarEntry(var1);// 1070 if (var3 != null) {// 1071 return this.checkResource(var1, var2, var3);// 1072 } else if (this.index == null) {// 1074 return null;// 1075 } else { HashSet var4 = new HashSet();// 1077 return this.getResource(var1, var2, var4);// 1078 } } } } }
對于應用層的開發(fā)者而言,需要通過ClassLoader.gerResource(resourceName)進行調(diào)用。
public abstract class ClassLoader { // 1. 獲取資源也會遵循父系委托機制 public URL getResource(String name) { URL url; if (parent != null) { url = parent.getResource(name); } else { url = getBootstrapResource(name); } if (url == null) { url = findResource(name); } return url; } protected URL findResource(String name) { return null; } } public class URLClassLoader extends SecureClassLoader implements Closeable { public URL findResource(final String name) { /* * The same restriction to finding classes applies to resources */ URL url = AccessController.doPrivileged( new PrivilegedAction<URL>() { public URL run() { // 2. 繼續(xù)通過類加載器在父類中維護的URLClassPath獲取資源 return ucp.findResource(name, true); } }, acc); return url != null ? ucp.checkURL(url) : null; } } public class URLClassPath { public URL findResource(String var1, boolean var2) { int[] var4 = this.getLookupCache(var1);// 224 // 3. 本質(zhì)還是迭代loader集合進行資源獲取 Loader var3; for(int var5 = 0; (var3 = this.getNextLoader(var4, var5)) != null; ++var5) {// 225 URL var6 = var3.findResource(var1, var2);// 226 if (var6 != null) {// 227 return var6;// 228 } } return null;// 231 } }
調(diào)用獲取資源方法的一些常見問題
Class.getResource & ClassLoader.getResource 的差異
通過ClassLoader獲取資源已經(jīng)在上面說過了,主要在于通過Class對象調(diào)用getResource方法有什么特別之處?
public final class Class<T> implements java.io.Serializable, GenericDeclaration, Type, AnnotatedElement { // 本質(zhì)依舊是調(diào)用ClassLoader.getResource,唯一的不同在于對于資源名稱進行了一次特殊處理。 // 做了哪些處理正如官方注釋所描述: // 1. 如果是絕對路徑(以 '/' 開頭),剔除 '/' 符號后返回 // 2. 如果非絕對路徑,則拼接'.'號轉(zhuǎn)為'/'后的包名作為前綴后返回 // 其實理解下來就是:以當前類文件所在的包為基準,進行相對、絕對路徑的拼接。絕對路徑則不采用包名作為前綴,相對路徑則以類所在的包名作為前綴。 public java.net.URL getResource(String name) { name = resolveName(name); ClassLoader cl = getClassLoader0(); if (cl == null) { return ClassLoader.getSystemResource(name); } return cl.getResource(name); } /** * Add a package name prefix if the name is not absolute Remove leading "/" * if name is absolute */ private String resolveName(String name) { if (name == null) { return name; } if (!name.startsWith("/")) { Class<?> c = this; while (c.isArray()) { c = c.getComponentType(); } String baseName = c.getName(); int index = baseName.lastIndexOf('.'); if (index != -1) { name = baseName.substring(0, index).replace('.', '/') +"/"+name; } } else { name = name.substring(1); } return name; } }
加載不同文件目錄 或 Jar包中含有的同名資源
比如classpath中包含的不同目錄、Jar包下都有一個同名文件config.properties,那么到底該加載哪一個呢?
根據(jù)之前的源碼分析,可以推斷出目錄或jar對應的loader,哪個最先執(zhí)行,則返回該loader對應的目錄或者jar下的config.properties。
如果我想要獲取所有的同名文件呢?
Enumeration<URL> resources = classLoader.getResources("config.properties"); while (resources.hasMoreElements()) { URL url = resources.nextElement(); // do something }
到此這篇關于Java類加載器ClassLoader的使用詳解的文章就介紹到這了,更多相關Java ClassLoader內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
java實現(xiàn)利用String類的簡單方法讀取xml文件中某個標簽中的內(nèi)容
下面小編就為大家?guī)硪黄猨ava實現(xiàn)利用String類的簡單方法讀取xml文件中某個標簽中的內(nèi)容。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-12-12SpringBoot?+DynamicDataSource切換多數(shù)據(jù)源的全過程
這篇文章主要介紹了SpringBoot?+DynamicDataSource切換多數(shù)據(jù)源的全過程,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01