如何理解和運用ClassLoader
定義
根據《深入理解Java虛擬機》提到“通過一個類的全限定名(packageName.ClassName)來獲取描述此類的二進制字節(class文件字節)這個動作的代碼模塊就叫做類加載器(ClassLoader)”。
作用
1、通常類加載器的作用是加載資源(字節碼文件)到java虛擬機中,想要在一個jvm 進程中唯一確認一個類,除瞭類的全限定名外,還需要指定它是由哪個類加載器加載的。
2、比如我們的類庫需要通過遠程網絡獲取,可以通過自定義類加載器從遠程加載字節碼文件。
3、java的字節碼文件很容易反編譯出來,一些核心的代碼不想被反編譯出來,可以對字節碼進行加密,然後通過自定義的類加載器加載這些字節碼,然後進行解碼返回給虛擬機。
4、比如jvm的熱加載和熱部署等功能也需要自定義類加載器來完成。
…….
java類加載器的種類
1、Bootstrap ClassLoader : 該加載器是最頂層的類加載器,它是加載放在{Java_home}\lib目錄 或者-Xbootclasspath指定路徑下類庫。
2、Extension ClassLoader : 該類加載器負載加載{Java_home}/lib\ext目錄 或者System.getenv(“java.ext.dirs”)系統變量路徑下的類庫。
3、Application ClassLoader : 該類加載器加載用戶程序類路徑下的類庫,它是默認的程序的類加載器。
雙親委派機制
1、雙親委派機制,雙親委派除瞭啟動類加載器(Bootstrap ClassLoader)外,其他的類加載器都應該有自己父加載器。它們的實現不是通過繼承來實現的,而是通過組合的方式。當加載某個Class時,當前類加載器會把這個加載請求委派給其父類加載器加載,同理父類加載器同樣委派其它的父類加載器加載,直到無其父類類加載器加載為止,如果父類加載器加載失敗,才會由其子類加載。
2、使用雙親委派機制,有個明顯的特征是:Java類隨著它的類加載器一起具備瞭一種優先級的層次關系。比如rt.jar包中的java.lang.Object,由於它所在位置是由啟動類加載器加載,所以Object類在程序的各種類加載器環境中都是同一個類。
3、雙親委派模型如下:
ClassLoader
可以看下ClassLoader 雙親委派模型的大致代碼框架如下:
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 1、查看該類是否加載 Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { // 2、如果未加載,委托給父類加載器加載 if (parent != null) { c = parent.loadClass(name, false); } else { //3、沒有父類加載器,委托給BootstrapClassLoader c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // 父類加載器沒有加載到,則自己加載 long t1 = System.nanoTime(); c = findClass(name); // 記錄該類加載的狀態Stat. sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } // resolve :true,需要對類進行鏈接(鏈接階段包括:準備,解析,初始化類) if (resolve) { resolveClass(c); } return c; } }
1、通過以上可以知道,我們可以繼承ClassLoader 來實現自己的類加載器,然後重寫findClass()方法,這些還是保存瞭雙親委派機制。
2、當我們重寫findClass()方法時,得到該類的字節碼後,需要調用defineClass()來放回Class<?>對象。
URLClassLoader
1、一般自定義的類加載器可以直接繼承該類,該類加載器通過添加url路徑來獲取類。
2、可以在其構造函數上URLClassLoader(URL[] urls, ClassLoader parent)直接進行添加其URL。
3、也可以通過addURL(URL)添加其URL。
自定義類加載器
1、首先假設main-project為我們自己編寫的工程,其依賴某一api:service-spi。而service-spi的實現有很多,project-spi-impl是其中的一個。
2、當main-project僅依賴service-spi,而project-spi-impl不在工程的類加載路徑下。所以需要自定義類加載器,從某個路徑下的jar加載進來。CoreClassLoader就是自定義的類加載器。
3、CoreClassLoader繼承自URLClassLoader,然後把相關搜索路徑添加到該類加載器即可。
4.github:https://github.com/zhvqee/class-loader
SpringBoot 對類加載器的運用
1、SpringBoot 工程通過spring-boot-maven-plugin插件打包。把相關資源和依賴包都打到一個jar包中(all in one)。其包的結構如下:
BOOT-INF/classes:存放的是本工程的class文件
BOOT-INF/lib:存放的是本工程依賴的二方包和三方包。
META-INF/MANIFEST.MF:該文件記錄瞭程序啟動入口等。
o.s.b.loader包下就是springboot自定義的類加載器。
2、當我們執行java -jar xxxx 時,會讀取MANIFEST.MF下
Main-Class: org.springframework.boot.loader.JarLauncher,
該配置就是程序的路口。而我們編寫的Main方法不是真正的啟動的入口。
3、當執行Launcher#launch()方法時,會把SpringBoot自定義的類加載器LaunchedURLClassLoader設置線程的上下文中,並通過該自定義類加載器加載我們自己編寫的main方法所在的類,然後利用反射調用main方法。代碼片段如下:
Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName); Method mainMethod = mainClass.getDeclaredMethod("main", String[].class); mainMethod.invoke(null, new Object[] { this.args });
4、LaunchedURLClassLoader 類加載器繼承URLClassLoader類加載器,它加載的路徑就是可執行jar下BOOT-INF下的class文件和二方包、三方包。
Class.forName() 和 ClassLoader.load()
1、JVM 加載的類主要經過以下幾個步驟:加載,鏈接,初始化,試用,卸載。
2、Class.forName()默認是需要對加載的類進行初始化。
3、ClassLoader.load實際調用的是ClassLoader.load(className,false),false:表示不進行鏈接,不進行鏈接也就代表不會進行初始化的操作,類的靜態塊和靜態對象都不會執行。
以上就是如何理解和運用ClassLoader的詳細內容,更多關於理解和運用 ClassLoader的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- ClassLoader雙親委派模式作用詳解
- 面試必時必問的JVM 類加載機制詳解
- 淺談Tomcat如何打破雙親委托機制
- JVM雙親委派模型知識詳細總結
- java類加載機制、類加載器、自定義類加載器的案例