虛擬機設計團隊把類加載階段中的「經過一個類的全限定名來獲取描述此類的二進制字節流」這個動做放到Java虛 機外部實現,以便讓應用程序本身決定如何去獲取所須要的類。實現這個動做的模塊稱爲「類加載器」。java
周志明. 深刻理解Java虛擬機:JVM高級特性與最佳實踐(第2版) 機械工業出版社.spring
兩個類是「相等」(包括equals、isAssignableFrom、isInstanceOf)的前提條件是這兩個類的類加載器相等。sql
public static void main(String[] args) throws ClassNotFoundException, MalformedURLException { URL jar = new URL("file:\\G:\\code\\demo\\demo-0.0.1-SNAPSHOT.jar"); URL[] urls = new URL[]{jar}; //類加載器1 URLClassLoader classLoader1 = new URLClassLoader(urls,null); Class userClass1 = classLoader1.loadClass("com.demo.User"); //類加載器2 URLClassLoader classLoader2 = new URLClassLoader(urls,null); Class userClass2 = classLoader2.loadClass("com.demo.User"); //輸出false,緣由:userClass來自不一樣的類加載器 System.out.println(userClass1.equals(userClass2)); }
應用程序類加載器(ApplicationClassLoader):這個類加載器由sun.misc.Launcher$App-ClassLoader實現。因爲這個類加載器是ClassLoader中的getSystemClassLoader()方法的返回值,因此通常也稱它爲系統類加載器。它負責加載用戶類路徑(ClassPath)上所指定的類庫,開發者能夠直接使用這個類加載器,若是應用程序中沒有自定義過本身的類加載器,通常狀況下這個就是程序中默認的類加載器。數據庫
周志明. 深刻理解Java虛擬機:JVM高級特性與最佳實踐(第2版) 機械工業出版社tomcat
ClassLoader的結構中有一個重要的成員變量parent,也就是咱們所說的ClassLoader的雙親。微信
// java.lang.ClassLoader public abstract class ClassLoader { private static native void registerNatives(); static { registerNatives(); } // The parent class loader for delegation // Note: VM hardcoded the offset of this field, thus all new fields // must be added *after* it. private final ClassLoader parent; ...
委派ClassLoader進行類加載的過程應該是:oracle
// java.lang.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 { // 若是沒有加載交給parent進行加載,若是加載成功返回類 if (parent != null) { c = parent.loadClass(name, false); } else { // 若是parent=null時,認爲parent=啓動類加載器 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } // 若是parent加載失敗,本身嘗試加載 if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; }
檢查與加載過程如圖所示:
app
要說明弊端,必須引入SPI。框架
SPI ,全稱爲 Service Provider Interface,是一種服務發現機制。JAVA中定義的SPI通常是要第三方進行實現,咱們比較常見的如:java.sql.Driver,JDK中只定義了Driver接口,並無去實現,Driver的實現由數據庫廠商來實現。
oralce數據庫驅動的實現以下:(來自:ojdbc6-11.2.0.4.0.jar)ide
public class OracleDriver implements Driver { ... }
同時第三方jar必須增長配置文件:
java.sql.Driver文件內容:oracle.jdbc.OracleDriver
java虛擬機經過掃描jar包下的配置文件信息加載對應接口的實現類。
定義SayHello接口
package com.demo; public interface SayHello { void hello(); }
實現SayHello接口
package com.demo; public class SayHelloImpl implements SayHello { @Override public void hello() { System.out.println("hello"); } }
在META-INF/services目錄下增長com.demo.SayHello文件,文件內容爲:com.demo.SayHelloImpl
主函數
public class ClassLoaderApplication { public static void main(String[] args) { ServiceLoader<SayHello> sayHellos = ServiceLoader.load(SayHello.class); for (SayHello s : sayHellos) { s.hello(); } } }
以java.sql.Driver爲例,java.sql.Driver接口定義在rt.jar中,而rt.jar由BootstrapClassLoader負責加載,Driver最終由同在rt.jar包中的DriverManager類所使用,代碼以下:
// class : DriverManager private static void loadInitialDrivers() { ... ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); ...
因爲DriverManager類在rt.jar中,因此能夠認定DriverManager類最終由BootstrapClassLoader加載器負責加載,而咱們的Driver實現類(OracleDriver)通常都是由應用程序類加載器(ApplicationClassLoader)或自定義類加載器負責加載,因此Driver的實現對BootstrapClassLoader是不可見的,這樣一定會致使DriverManager的loadInitialDrivers失敗。
爲了解決這個問題,Java設計團隊只好引入了一個不太優雅的設計:線程上下文類加載器(ThreadContextClassLoader)。這個類加載器能夠經過java.lang.Thread類的setContextClassLoaser()方法進行設置,若是建立線程時還未設置,它將會從父線程中繼承一個,若是在應用程序的全局範圍內都沒有設置過的話,那這個類加載器默認就是應用程序類加載器。
看下ServiceLoader的相關源碼:
//class : ServiceLoader public static <S> ServiceLoader<S> load(Class<S> service) { //ServiceLoader就是經過Thread.currentThread().getContextClassLoader()獲取類加載器的 ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }
因此解決DriverManager類中能夠加載OracleDriver的問題,能夠經過將應用程序類加載器(ApplicationClassLoader)設置到java.lang.Thread類的setContextClassLoaser()方法來解決。
其實這一過程咱們基本不用本身來敲代碼實現,由於咱們用的容器都已經幫咱們考慮到了。以tomcat(9.0.24)的源碼爲例:
//class : WebappLoader @Override public void backgroundProcess() { if (reloadable && modified()) { try { Thread.currentThread().setContextClassLoader (WebappLoader.class.getClassLoader()); if (context != null) { context.reload(); } } finally { if (context != null && context.getLoader() != null) { Thread.currentThread().setContextClassLoader (context.getLoader().getClassLoader()); } } } }
咱們定義的JavaBean在spring的getBean方法的建立過程其實與DriverManager建立Driver實例的過程是同樣的。咱們的JavaBean是通常都是由應用程序類加載器(ApplicationClassLoader)或自定義類加載器負責加載,而Spring作爲一款開源框架多是有更高層類加載器負責加載,因此Spring獲取JavaBean的Class時第一優先級是經過Thread.currentThread().getContextClassLoader()來獲取JavaBean的Class的類加載器。如代碼所示:
//org.springframework.util.ClassUtils public static ClassLoader getDefaultClassLoader() { ClassLoader cl = null; try { cl = Thread.currentThread().getContextClassLoader(); } catch (Throwable var3) { } if (cl == null) { cl = ClassUtils.class.getClassLoader(); if (cl == null) { try { cl = ClassLoader.getSystemClassLoader(); } catch (Throwable var2) { } } } return cl; }
虛擬機設計團隊把類加載階段中的「經過一個類的全限定名來獲取描述此類的二進制字節流」這個動做放到Java虛 機外部實現,以便讓應用程序本身決定如何去獲取所須要的類。實現這個動做的模塊稱爲「類加載器」。
周志明. 深刻理解Java虛擬機:JVM高級特性與最佳實踐(第2版) 機械工業出版社.
兩個類是「相等」(包括equals、isAssignableFrom、isInstanceOf)的前提條件是這兩個類的類加載器相等。
public static void main(String[] args) throws ClassNotFoundException, MalformedURLException { URL jar = new URL("file:\\G:\\code\\demo\\demo-0.0.1-SNAPSHOT.jar"); URL[] urls = new URL[]{jar}; //類加載器1 URLClassLoader classLoader1 = new URLClassLoader(urls,null); Class userClass1 = classLoader1.loadClass("com.demo.User"); //類加載器2 URLClassLoader classLoader2 = new URLClassLoader(urls,null); Class userClass2 = classLoader2.loadClass("com.demo.User"); //輸出false,緣由:userClass來自不一樣的類加載器 System.out.println(userClass1.equals(userClass2)); }
應用程序類加載器(ApplicationClassLoader):這個類加載器由sun.misc.Launcher$App-ClassLoader實現。因爲這個類加載器是ClassLoader中的getSystemClassLoader()方法的返回值,因此通常也稱它爲系統類加載器。它負責加載用戶類路徑(ClassPath)上所指定的類庫,開發者能夠直接使用這個類加載器,若是應用程序中沒有自定義過本身的類加載器,通常狀況下這個就是程序中默認的類加載器。
周志明. 深刻理解Java虛擬機:JVM高級特性與最佳實踐(第2版) 機械工業出版社
ClassLoader的結構中有一個重要的成員變量parent,也就是咱們所說的ClassLoader的雙親。
// java.lang.ClassLoader public abstract class ClassLoader { private static native void registerNatives(); static { registerNatives(); } // The parent class loader for delegation // Note: VM hardcoded the offset of this field, thus all new fields // must be added *after* it. private final ClassLoader parent; ...
委派ClassLoader進行類加載的過程應該是:
// java.lang.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 { // 若是沒有加載交給parent進行加載,若是加載成功返回類 if (parent != null) { c = parent.loadClass(name, false); } else { // 若是parent=null時,認爲parent=啓動類加載器 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } // 若是parent加載失敗,本身嘗試加載 if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; }
檢查與加載過程如圖所示:
要說明弊端,必須引入SPI。
SPI ,全稱爲 Service Provider Interface,是一種服務發現機制。JAVA中定義的SPI通常是要第三方進行實現,咱們比較常見的如:java.sql.Driver,JDK中只定義了Driver接口,並無去實現,Driver的實現由數據庫廠商來實現。
oralce數據庫驅動的實現以下:(來自:ojdbc6-11.2.0.4.0.jar)
public class OracleDriver implements Driver { ... }
同時第三方jar必須增長配置文件:
java.sql.Driver文件內容:oracle.jdbc.OracleDriver
java虛擬機經過掃描jar包下的配置文件信息加載對應接口的實現類。
定義SayHello接口
package com.demo; public interface SayHello { void hello(); }
實現SayHello接口
package com.demo; public class SayHelloImpl implements SayHello { @Override public void hello() { System.out.println("hello"); } }
在META-INF/services目錄下增長com.demo.SayHello文件,文件內容爲:com.demo.SayHelloImpl
主函數
public class ClassLoaderApplication { public static void main(String[] args) { ServiceLoader<SayHello> sayHellos = ServiceLoader.load(SayHello.class); for (SayHello s : sayHellos) { s.hello(); } } }
以java.sql.Driver爲例,java.sql.Driver接口定義在rt.jar中,而rt.jar由BootstrapClassLoader負責加載,Driver最終由同在rt.jar包中的DriverManager類所使用,代碼以下:
// class : DriverManager private static void loadInitialDrivers() { ... ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); ...
因爲DriverManager類在rt.jar中,因此能夠認定DriverManager類最終由BootstrapClassLoader加載器負責加載,而咱們的Driver實現類(OracleDriver)通常都是由應用程序類加載器(ApplicationClassLoader)或自定義類加載器負責加載,因此Driver的實現對BootstrapClassLoader是不可見的,這樣一定會致使DriverManager的loadInitialDrivers失敗。
爲了解決這個問題,Java設計團隊只好引入了一個不太優雅的設計:線程上下文類加載器(ThreadContextClassLoader)。這個類加載器能夠經過java.lang.Thread類的setContextClassLoaser()方法進行設置,若是建立線程時還未設置,它將會從父線程中繼承一個,若是在應用程序的全局範圍內都沒有設置過的話,那這個類加載器默認就是應用程序類加載器。
看下ServiceLoader的相關源碼:
//class : ServiceLoader public static <S> ServiceLoader<S> load(Class<S> service) { //ServiceLoader就是經過Thread.currentThread().getContextClassLoader()獲取類加載器的 ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }
因此解決DriverManager類中能夠加載OracleDriver的問題,能夠經過將應用程序類加載器(ApplicationClassLoader)設置到java.lang.Thread類的setContextClassLoaser()方法來解決。
其實這一過程咱們基本不用本身來敲代碼實現,由於咱們用的容器都已經幫咱們考慮到了。以tomcat(9.0.24)的源碼爲例:
//class : WebappLoader @Override public void backgroundProcess() { if (reloadable && modified()) { try { Thread.currentThread().setContextClassLoader (WebappLoader.class.getClassLoader()); if (context != null) { context.reload(); } } finally { if (context != null && context.getLoader() != null) { Thread.currentThread().setContextClassLoader (context.getLoader().getClassLoader()); } } } }
咱們定義的JavaBean在spring的getBean方法的建立過程其實與DriverManager建立Driver實例的過程是同樣的。咱們的JavaBean是通常都是由應用程序類加載器(ApplicationClassLoader)或自定義類加載器負責加載,而Spring作爲一款開源框架多是有更高層類加載器負責加載,因此Spring獲取JavaBean的Class時第一優先級是經過Thread.currentThread().getContextClassLoader()來獲取JavaBean的Class的類加載器。如代碼所示:
//org.springframework.util.ClassUtils public static ClassLoader getDefaultClassLoader() { ClassLoader cl = null; try { cl = Thread.currentThread().getContextClassLoader(); } catch (Throwable var3) { } if (cl == null) { cl = ClassUtils.class.getClassLoader(); if (cl == null) { try { cl = ClassLoader.getSystemClassLoader(); } catch (Throwable var2) { } } } return cl; }
更多spring源碼相關知識點擊
《超哥spring源碼解析之核心容器篇》免費視頻學習
也能夠關注超哥微信公衆號: