如何修改專案引入jar中的程式碼
你是否遇到過這樣的困境:這個問題線上才能出現,可是線上不支援debug,並且懷疑是引入的jar引起的,不能加log怎麼辦?亦或者你覺得引入的jar的某個方法有bug,應該這麼寫才對,如何才能去證實呢。
不知道你是如何解決上訴問題的,筆者的方案就是在專案建立一個一模一樣的需要debug或者修改的Class(package+class name都一樣),然後把jar中的程式碼copy該class,我們可以在copy的這個class做任何程式碼修改。這樣當專案中需要該Class時,呼叫的是我們copy的class,並不會是jar中的class。
為什麼上述修改可以實現我們想要的功能呢?這就要從Java中的類載入器的工作原理。
類載入器
類載入器分類
類載入器主要分成兩類,一類是系統提供的,另外一類是由Java應用開發人員編寫的。可以具體分為4種,引導類載入器(Boostrap ClassLoader)、擴充套件類載入器(Extension ClassLoader)、系統類載入器(App ClassLoader)、自定義類載入器(Custom ClassLoader)。
-
引導類載入器:用來載入Java的核心類庫。這個載入器主要是載入<JAVA_HOME>/lib目錄下的class,也可以通過JVM引數-Xbootclasspath去指定,但是必須是JVM能夠識別的檔案。
-
擴充套件類載入器:用來載入Java的擴充套件類庫,這個類載入器使用C語言實現。它主要是載入<JAVA_HOME>\lib\ext目錄下的class,也可以通過java.ext.dirs這個系統變數改變所指定的類庫。
-
系統類載入器:系統類載入器的載入路徑是Java應用的類路徑(CLASSPATH),也可以通過java.class.path這個系統變數指定載入路徑。也就是說在沒有自定義載入器的情況下,Java應用的類都是由系統類載入器載入的。而且該載入器可以用Classloader類的getSystemClassloader( )方法直接獲取到。
-
自定義類載入器:在很多場景下,類並步子上面的路徑上,我們就可以通過自定義類載入器去載入class,比如從網路中載入,比如class被加密,那麼也需要自定義類載入器。
雙親委派模型
類載入器通過一個叫雙親委派模式進行載入。
雙親委派是如何實現的?
實現類載入器必須要繼承ClassLoader,該類中有個loadClass方法,呼叫者就是通過呼叫該方法載入Class,其具體的程式碼如下:
方法的執行需要一下幾個步驟:
-
呼叫findLoadedClass方法檢查這個類是否已經載入過了。
-
呼叫其載入器的loadClass,若其父載入器沒有,則使用BoostrapClassLoader去執行類載入。
-
若父類沒有載入到,則呼叫findClass去載入類。
一般來說我們只需要實現findClass即可。
ClassLoader這個抽象類有兩個構造方法,一個含有parent引數,一個不含任何引數。從程式碼可以看出若是不傳引數預設使用的是App ClassLoader。
從上面的執行步驟我們可以發現,在實現ClassLoader中很容易破壞雙親委託,第一、可以把parent傳遞為null,第二、重新實現loadClass方法。所以說雙親委託模式只是Java設計者推薦給開發者實現類載入器的方式,並不是一個強制性的約束模型。
四類類載入器載入器的層級關係如下:
系統預設的類載入器如何工作的?
系統預設類載入器是AppClassLoader,它屬於Launcher中的內部類。他繼承了URLClassLoader,URLClassLoader根據在建構函式中傳遞過來的檔案和目錄,順序查詢Class。AppClassLoader具體的程式碼如下:
從程式碼中很明顯看出AppClassLoader的parent傳遞的確實是ExtClassLoader。程式碼中也顯示AppClassLoader主要用於載入“java.class.path”(預設值就是CLASSPATH) 這個系統變數中的Class。可以使用“jinfo -sysprop pid” 來檢視系統配置,顯示截圖如下:
從截圖中很明顯看出,CLASSPATH主要包含兩部分,一個是WEB-INF/classes(這個主要是專案自己開發的類)下的Class,另外一部分就是WEB-INF/lib/(這個目錄下都是專案依賴的jar)目錄下的jar。上面已經說過URLClassLoader是根據傳遞的檔案的順序查詢類,那麼classes/目錄下的類就會優先比lib/載入,也就是說我們在專案中定義的Class會優先被載入。截圖似乎顯示lib/下的jar是按照字母順序來的,實際上按照linux下的inode,但是基本上可以認為是按照字母順序來的。
總結
本文只是簡單的介紹了利用系統預設的類載入器工作機制來解決需要在引入的jar中加log或者修改實現。但是其實可以利用自定義類載入器做很多事情,比如Tomcat使用自定義載入器去載入/common、/server和/shared等目錄下的jar;Spring Boot把所有jar都壓縮成一個jar,使用自定義載入器是解壓該jar,得到WEB-INF/classes和WEB-INF/lib/下所有的class。