java 詳解類加載器的雙親委派及打破雙親委派
https://blog.csdn.net/chang_ge/article/details/80262115
/www.jb51.net/article/102920.htm
https://www.cnblogs.com/wxd0108/p/6681618.html
其實,雙親委派模型並不復雜。自定義類加載器也不難!隨便從網上搜一下就能搜出一大把結果,然后copy
一下就能用。但是,如果每次想自定義類加載器就必須搜一遍別人的文章,然后復制,這樣顯然不行。可是自定義類加載器又不經常用,時間久了容易忘記。相信你經常會記不太清loadClass
、findClass
、defineClass
這些函數我到底應該重寫哪一個?它們主要是做什么的?本文大致分析了各個函數的流程,目的就是讓你看完之后,難以忘記!或者說,延長你對自定義類加載器的記憶時間!隨時隨地想自定義就自定義!
1. 雙親委派模型
關於雙親委派模型,網上的資料有很多。我這里只簡單的描述一下,就當是復習。
1.1 什么是雙親委派模型?
首先,先要知道什么是類加載器。簡單說,類加載器就是根據指定全限定名稱將class
文件加載到JVM
內存,轉為Class
對象。如果站在JVM
的角度來看,只存在兩種類加載器:
啟動類加載器(
Bootstrap ClassLoader
):由C++
語言實現(針對HotSpot
),負責將存放在<JAVA_HOME>\lib
目錄或-Xbootclasspath
參數指定的路徑中的類庫加載到內存中。其他類加載器:由
Java
語言實現,繼承自抽象類ClassLoader
。如:
- 擴展類加載器(
Extension ClassLoader
):負責加載<JAVA_HOME>\lib\ext
目錄或java.ext.dirs
系統變量指定的路徑中的所有類庫。- 應用程序類加載器(
Application ClassLoader
)。負責加載用戶類路徑(classpath
)上的指定類庫,我們可以直接使用這個類加載器。一般情況,如果我們沒有自定義類加載器默認就是用這個加載器。
雙親委派模型工作過程是:如果一個類加載器收到類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器完成。每個類加載器都是如此,只有當父加載器在自己的搜索范圍內找不到指定的類時(即ClassNotFoundException
),子加載器才會嘗試自己去加載。

1.2 為什么需要雙親委派模型?
為什么需要雙親委派模型呢?假設沒有雙親委派模型,試想一個場景:
黑客自定義一個
java.lang.String
類,該String
類具有系統的String
類一樣的功能,只是在某個函數稍作修改。比如equals
函數,這個函數經常使用,如果在這這個函數中,黑客加入一些“病毒代碼”。並且通過自定義類加載器加入到JVM
中。此時,如果沒有雙親委派模型,那么JVM
就可能誤以為黑客自定義的java.lang.String
類是系統的String
類,導致“病毒代碼”被執行。
而有了雙親委派模型,黑客自定義的java.lang.String
類永遠都不會被加載進內存。因為首先是最頂端的類加載器加載系統的java.lang.String
類,最終自定義的類加載器無法加載java.lang.String
類。
或許你會想,我在自定義的類加載器里面強制加載自定義的java.lang.String
類,不去通過調用父加載器不就好了嗎?確實,這樣是可行。但是,在JVM
中,判斷一個對象是否是某個類型時,如果該對象的實際類型與待比較的類型的類加載器不同,那么會返回false。
舉個簡單例子:
ClassLoader1
、ClassLoader2
都加載java.lang.String
類,對應Class1、Class2對象。那么Class1
對象不屬於ClassLoad2
對象加載的java.lang.String
類型。
二、打破雙親委派機制則不僅要繼承ClassLoader類,還要重寫loadClass和findClass方法,如下例子:
①定義Test類。
1
2
3
4
5
|
public
class
Test {
public
Test(){
System.out.println(
this
.getClass().getClassLoader().toString());
}
}
|
②重新定義一個繼承ClassLoader的TestClassLoaderN類,這個類與前面的TestClassLoader類很相似,但它除了重寫findClass方法外還重寫了loadClass方法,默認的loadClass方法是實現了雙親委派機制的邏輯,即會先讓父類加載器加載,當無法加載時才由自己加載。這里為了破壞雙親委派機制必須重寫loadClass方法,即這里先嘗試交由System類加載器加載,加載失敗才會由自己加載。它並沒有優先交給父類加載器,這就打破了雙親委派機制。
public
class
TestClassLoaderN
extends
ClassLoader {
private
String name;
public
TestClassLoaderN(ClassLoader parent, String name) {
super
(parent);
this
.name = name;
}
@Override
public
String toString() {
return
this
.name;
}
@Override
public
Class<?> loadClass(String name)
throws
ClassNotFoundException {
Class<?> clazz =
null
;
ClassLoader system = getSystemClassLoader();
try
{
clazz = system.loadClass(name);
}
catch
(Exception e) {
// ignore
}
if
(clazz !=
null
)
return
clazz;
clazz = findClass(name);
return
clazz;
}
@Override
public
Class<?> findClass(String name) {
InputStream is =
null
;
byte
[] data =
null
;
ByteArrayOutputStream baos =
new
ByteArrayOutputStream();
try
{
is =
new
FileInputStream(
new
File(
"d:/Test.class"
));
int
c =
0
;
while
(-
1
!= (c = is.read())) {
baos.write(c);
}
data = baos.toByteArray();
}
catch
(Exception e) {
e.printStackTrace();
}
finally
{
try
{
is.close();
baos.close();
}
catch
(IOException e) {
e.printStackTrace();
}
}
return
this
.defineClass(name, data,
0
, data.length);
}
public
static
void
main(String[] args) {
TestClassLoaderN loader =
new
TestClassLoaderN(
TestClassLoaderN.
class
.getClassLoader(),
"TestLoaderN"
);
Class clazz;
try
{
clazz = loader.loadClass(
"test.classloader.Test"
);
Object object = clazz.newInstance();
}
catch
(Exception e) {
e.printStackTrace();
}
}
}