衆所周知, Java 或者其餘運行在 JVM(java 虛擬機)上面的程序都須要最終便覺得字節碼,而後被 JVM加載運行,那麼這個加載
到虛擬機的過程就是 classloader 類加載器所幹的事情.直白一點,就是 經過一個類的全限定類名稱來獲取描述此類的二進制字節流 的過程.java
說到 Java 的類加載器,必不可少的就是它的雙親委派模型,從 Java 虛擬機的角度來看,只存在兩種不一樣的類加載器:git
java.lang.ClassLoader
在 Java 內部,絕大部分的程序都會使用 Java 內部提供的默認加載器.web
負責將$JAVA_HOME/lib
或者 -Xbootclasspath
參數指定路徑下面的文件(按照文件名識別,如 rt.jar) 加載到虛擬機內存中.啓動類加載器沒法直接被 java 代碼引用,若是須要把加載請求委派給啓動類加載器,直接返回null
便可.api
負責加載$JAVA_HOME/lib/ext
目錄中的文件,或者java.ext.dirs
系統變量所指定的路徑的類庫.tomcat
通常是系統的默認加載器,好比用 main 方法啓動就是用此類加載器,也就是說若是沒有自定義過類加載器,同時它也是getSystemClassLoader()
的返回值.app
這幾種類加載器的工做流程被抽象成一個模型,就是雙親委派模型.webapp
工做流程:ide
這基本就是雙親委派模型.測試
可是這種模型只是一種推薦的方式,並非強制的,你也能夠嘗試打破這種規則. 自因此這樣約定,仍是有必定的好處的, Java 類隨着它的類加載器一塊兒具有了一種帶有優先級的層次關係. 好比本身定義了java.lang.Object
對象,那麼按照上面的流程,他永遠都是被啓動類加載器加載的rt.jar 中的那個類,而不是本身定義的這個類,這樣就保證了兄運行的穩定,不然,可能變得很是混亂,能夠隨意改寫任何類.spa
大多數狀況下,其實咱們並不須要知道這些,由於你的程序也會運行的很是正常,雖然像Tomcat
,Spring Boot
都有本身定義的類加載器,可是咱們在不用關心的狀況下也會運行的好好地.
那麼類加載器能夠被運行在哪些地方呢?
JavaAgent
來加強字節碼的時候.JavaAgent 的使用後續文章補上.先上一張圖.
頂層是應用代碼實際運行的 ClassLoader, 多是Application ClassLoader
, 也有多是 tomcat 的webapp ClassLoader
或者其餘容器自定義的類加載器,老是是真實 的用戶編寫的代碼運行的 classloader.
咱們若是要在javaagent
中加強用戶或者用戶使用的包進行加強的話,必須實現一個自定義的 classloader 來"繼承"(委派)應用代碼的類加載器.爲何?
javaagent 的代碼永遠都是被應用類加載器( Application ClassLoader
)所加載,和應用代碼的真實加載器無關,舉個栗子,當前運行在 tomcat 中的代碼是webapp ClassLoader
加載的,若是啓動參數加上-javaagent
, 這個 javaagent 仍是在Application ClassLoader
中加載的.
按照上面的雙親委派模型,若是咱們在 javaagent 中想要訪問應用裏面的 api 包或者類,這是不可能的,由於按照雙親委派模型,通俗來講就是,子加載器能夠訪問父加載器中的類,可是反過來就行不通.
那麼這個時候有沒有辦法可以作到呢?
咱們能夠自定義本身的類加載器繼承應用代碼類加載器(能夠在 javaagent 中完成, javaagent 每加載一個類,就會回調傳回真實的類加載器),而後咱們在Application ClassLoader
中用自定義的類加載器去加載子類,並建立好實例(newInstance()
), 將實例的引用保存 在變量中.
真實運行的時候,就會經過這個變量,去訪問咱們自定義加載器的內容,又因爲咱們的自定義類加載器是繼承自應用代碼的類加載器的,因此自定義類加載器中的代碼能夠訪問應用的代碼.
總結一句就是,父類加載器沒法加載子類加載器的類,可是能夠持有子類加載器所加載類的實例,從而實現父類加載器的代碼能夠調用子類加載器的代碼的形式
貌似比較抽象,後面會補上詳細的例子供參考.
針對上面的情形,咱們定義一個例子,能夠詳細解釋 ClassLoader 的加載使用,
FooClassLoader
:package com.example.test;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
/** * @author lican */
public class FooClassLoader extends ClassLoader {
private static final String NAME = "/Users/lican/git/test/foo/";
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> loadedClass = findLoadedClass(name);
if (loadedClass == null) {
String s = name.substring(name.lastIndexOf(".") + 1) + ".class";
File file = new File(NAME + s);
try (FileInputStream fileInputStream = new FileInputStream(file)) {
byte[] b = new byte[fileInputStream.available()];
fileInputStream.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
e.printStackTrace();
}
}
return loadedClass;
}
}
複製代碼
/Users/lican/git/test/foo/
這裏的,主要是方便測試.package com.example.test;
public class FooTest {
public String getFoo() {
return "foo";
}
}
複製代碼
而後測試程序爲:
package com.example.test;
import java.lang.reflect.Method;
/** * @author lican */
public class ClassLoaderTest {
private Object fooTestInstance;
private FooClassLoader fooClassLoader = new FooClassLoader();
public static void main(String[] args) throws Exception {
ClassLoaderTest classLoaderTest = new ClassLoaderTest();
classLoaderTest.initAndLoad();
Object fooTestInstance = classLoaderTest.getFooTestInstance();
System.out.println(fooTestInstance.getClass().getClassLoader());
Method getFoo = fooTestInstance.getClass().getMethod("getFoo");
System.out.println(getFoo.invoke(fooTestInstance));
System.out.println(classLoaderTest.getClass().getClassLoader());
}
private void initAndLoad() throws Exception {
Class<?> aClass = Class.forName("com.example.test.FooTest", true, fooClassLoader);
fooTestInstance = aClass.newInstance();
}
public Object getFooTestInstance() {
return fooTestInstance;
}
}
複製代碼
咱們用FooClassLoader
來加載com.example.test.FooTest
, 而後在 AppClassLoader中持有引用.被後續使用.