雙親委派是什么?
1、首先我們看下如何自定義一個類加載器
自定義類加載器需要繼承ClassLoader類,并重寫loadClass(String name, boolean resolve)、loadClass(String name)和findClass(String name)方法;
自定義類加載器中被重寫的loadClass方法將由項目代碼調(diào)用。
public class MyClassLoader extends ClassLoader{
//用于指定類加載器要加載的類地址
private String classPath;
//無參構(gòu)造方法
public MyClassLoader(String classPath){
super();
this.classPath = classPath;
}
//帶參構(gòu)造方法,
//參數(shù)是ClassLoader parent,用于指定當前類加載器的父加載器
public MyClassLoader(String classPath, ClassLoader parent){
super(parent);
this.classPath = classPath;
}
/**
* 最終會由這個方法來調(diào)用findClass方法
* 這個方法中體現(xiàn)了類的雙親委派加載
* 想要打破雙親委派也是在這個方法實現(xiàn)
* @param name
* @param resolve
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
//判斷類是否已經(jīng)加載過了,如果加載過了則直接返回即可
Class<?> loadedClass = findLoadedClass(name);
ClassLoader systemClassLoader = getSystemClassLoader();
loadedClass = super.loadClass(name, resolve);
//如果需要即時解析,則進行解析操作
if (resolve) resolveClass(loadedClass);
//返回加載的Class對象
return loadedClass;
}
/**
* 這個方法與上面那個方法的作用是一樣的,唯一的區(qū)別是,這個方法沒有resolve參數(shù)
* 默認加載后先不解析
* @param name
* @return
* @throws ClassNotFoundException
*/
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
return this.loadClass(name, false);
}
/**
* 其作用是從磁盤讀取字節(jié)碼文件到內(nèi)存
* @param name
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
File file = new File(classPath);
try {
/**
* 讀字節(jié)流,不需要reader
*/
FileInputStream in = new FileInputStream(file);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesNumRead = 0;
while ((bytesNumRead = in.read(buffer)) != -1) {
baos.write(buffer, 0, bytesNumRead);
}
byte[] data = baos.toByteArray();
if (data == null){
throw new ClassNotFoundException("找不到目標類,加載目標類失敗...");
} else {
/**
* 將讀取的字節(jié)碼數(shù)據(jù)交給jvm去構(gòu)建成字節(jié)碼對象
*/
return defineClass(name, data, 0, data.length);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
測試類:
public class TestClassLoad {
public static void main(String[] args) {
TestClassLoad testClassLoad = new TestClassLoad();
//指定當前加載器的父加載器為AppClassLoader
MyClassLoader myClassLoader = new MyClassLoader("F:\\cc\\Person.class", ClassLoader.getSystemClassLoader());
System.out.println("myClassLoader的父加載器是: " + myClassLoader.getParent());
try {
//加載的目標類是test.Person,在外界代碼中調(diào)用loadClass方法
Class<?> loadClass = myClassLoader.loadClass("test.Person");
//打印加載的類是不是目標類
System.out.println(loadClass.getName());
//獲取當前已加載的Class對象的類加載器是什么
System.out.println(loadClass.getClassLoader());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
再分析下ClassLoader類的構(gòu)造方法:
它有3個構(gòu)造方法,兩個protected的和一個private的;
protected ClassLoader(),無參構(gòu)造方法 ;
protected ClassLoader(ClassLoader parent),帶參構(gòu)造方法,可以用于指定當前類加載器的父加載器;
private ClassLoader(Void unused, ClassLoader parent) ,類內(nèi)部使用的構(gòu)造方法, ClassLoader()和ClassLoader(ClassLoader parent)都是調(diào)用的這個構(gòu)造方法做初始化。
private ClassLoader(Void unused, ClassLoader parent) {
this.parent = parent;
if (ParallelLoaders.isRegistered(this.getClass())) {
parallelLockMap = new ConcurrentHashMap<>();
package2certs = new ConcurrentHashMap<>();
domains =
Collections.synchronizedSet(new HashSet<ProtectionDomain>());
assertionLock = new Object();
} else {
// no finer-grained lock; lock on the classloader instance
parallelLockMap = null;
package2certs = new Hashtable<>();
domains = new HashSet<>();
assertionLock = this;
}
}
/**
* Creates a new class loader using the specified parent class loader for
* delegation.
*
* 創(chuàng)建自定義類加載器對象時,指定parent類加載器
*
* @since 1.2
*/
protected ClassLoader(ClassLoader parent) {
this(checkCreateClassLoader(), parent);
}
/**
* Creates a new class loader using the <tt>ClassLoader</tt> returned by
* the method {@link #getSystemClassLoader()
* <tt>getSystemClassLoader()</tt>} as the parent class loader.
*
* 不帶參數(shù),默認通過getSystemClassLoader()方法創(chuàng)建類加載器的父加載器
*
*/
protected ClassLoader() {
this(checkCreateClassLoader(), getSystemClassLoader());
}
ClassLoader()無參構(gòu)造方法因為不帶參數(shù),所以沒法通過外界指定當前類加載器的父加載器,在它的默認實現(xiàn)中,它默認通過getSystemClassLoader()方法來指定當前類加載器的父加載器;
可以看到這個方法的返回值是ClassLoader類, 這個方法的作用是創(chuàng)建系統(tǒng)類加載類對象scl,并返回scl對象作為自定義類加載器對象的父加載器

下面重點分析下initSystemClassLoader()方法:

private static synchronized void initSystemClassLoader() {
/**
*
* sclSet屬性是一個boolean類型的靜態(tài)屬性,它的作用是用于判斷system classloader是否已經(jīng)被創(chuàng)建了,
* 如果已經(jīng)被創(chuàng)建了,則將sclSet屬性的值設置為true,否則它的值還是默認值false
*
*
* 能夠進入這個語句塊說明system classloader還沒被創(chuàng)建
*/
if (!sclSet) {
/**
*
* scl表示system classloader,
* 如果它不為null,卻還重新創(chuàng)建的話,就拋異常
*
*/
if (scl != null)
throw new IllegalStateException("recursive invocation");
/**
* 執(zhí)行到這里說明scl還沒被創(chuàng)建
*
* getLauncher()獲取Launcher對象
*
*/
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
/**
*
* Launcher對象不為null
*
*/
if (l != null) {
Throwable oops = null;
/**
*
* 通過調(diào)用Launcher對象的getClassLoader()方法獲取Launcher類定義的ClassLoader對象
*/
scl = l.getClassLoader();
try {
/**
*
* 將上面獲取的類加載器封裝為系統(tǒng)類加載器
*/
scl = AccessController.doPrivileged(
new SystemClassLoaderAction(scl));
} catch (PrivilegedActionException pae) {
oops = pae.getCause();
if (oops instanceof InvocationTargetException) {
oops = oops.getCause();
}
}
if (oops != null) {
if (oops instanceof Error) {
throw (Error) oops;
} else {
// wrap the exception
throw new Error(oops);
}
}
}
//系統(tǒng)類加載器創(chuàng)建完成之后,將sclSet標識設置為true
sclSet = true;
}
}
通過上面的分析我們可以知道,initSystemClassLoader()方法涉及到一個非常重要的類Launcher,下面我們要好好分析下Launcher類:
從上面的分析我們知道了initSystemClassLoader()方法會調(diào)用getLauncher()方法和getClassLoader()方法,我們來看下代碼:

getLauncher()方法的作用是返回Launcher類的Launcher launcher屬性,而launcher默認是已經(jīng)初始化的了,通過new Launcher()構(gòu)造方法創(chuàng)建了一個Launcher對象
我們再看下Launcher類的構(gòu)造方法邏輯:

因為initSystemClassLoader()方法還調(diào)用了getClassLoader()方法,我們再看下getClassLoader()方法的邏輯:
可以知道它是直接返回了Launcher類的loader對象引用,根據(jù)上面的分析我們知道,loader引用指向的是應用類加載器,所以調(diào)用Launcher類的getClassLoader()方法得到的是應用類加載器對象

至此,自定義類加載器的創(chuàng)建過程算是分析完成了。
此外,關于Launcher類還有一個需要分析的點:
Launcher類有三個靜態(tài)內(nèi)部類需要關注:
1)ExtClassLoader和AppClassLoader,它們都繼承自URLClassLoader
2)BootClassPathHolder
ExtClassLoader類

ExtClassLoader類加載器加載的類路徑由系統(tǒng)參數(shù)java.ext.dirs參數(shù)指定,默認是jdk/jre/lib/ext目錄下的jar包和.class文件

AppClassLoader類

AppClassLoader類加載器加載的類路徑由系統(tǒng)參數(shù)java.class.path指定,默認是jdk/jre/lib目錄下的某些jar包以及項目編譯目錄target\classes下的.class文件

還有一個是引導類加載器加載的類路徑是由系統(tǒng)參數(shù)sun.boot.class.path指定的,默認是jdk/jre/lib目錄下的某些jar包以及jdk/jre/classes目錄下的.class文件

根據(jù)上面的分析,我們知道自定義類加載器、應用類加載器、擴展類加載器和引導類加載器直接的關系如下圖所示:

2、上面已經(jīng)分析完了如何創(chuàng)建一個類加載器,如何指定類加載器的父加載器以及分析它的父加載器是什么:appClassLoader?null?看自己實例化了加載器時的選擇。
基于上述分析,我們已經(jīng)可以知道一個類加載器對象是怎么創(chuàng)建的了,接下來我們需要分析類加載器是如何加載類信息的:
重點分析ClassLoader類的loadClass方法邏輯:
ClassLoader類有l(wèi)oadClass(String name) 和 loadClass(String name, boolean resolve)方法,可以看到loadClass(String name)在ClassLoader類中實際上是調(diào)用了loadClass(String name, boolean resolve)方法的,它的resolve參數(shù)默認設置為false了

然后查看loadClass(String name, boolean resolve)方法的邏輯
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
//加載的邏輯加鎖了,是線程安全的
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
//這行代碼的邏輯是判斷指定類是否之前已經(jīng)加載過了,內(nèi)存中是否已經(jīng)存在了
//如果已經(jīng)加載過了,則不需要重新加載,直接返回其Class對象即可
Class<?> c = findLoadedClass(name);
// 結(jié)果為null,說明之前還沒加載過,通過這個邏輯進行加載
if (c == null) {
long t0 = System.nanoTime();
try {
//這里表示如果當前類加載器的父加載器不為null,則調(diào)用父加載器的loadClass方法來加載目標類
//重點來了,java的雙親委派機制在這里就體現(xiàn)出來了:
//如果當前類加載器的父加載器不為null,就通過父加載器來加載器目標類,即將加載目標類的任務委托給了父加載器來執(zhí)行
//這里有遞歸調(diào)用的意味了:
//當前類加載器委托它的父加載器來加載目標類,它父加載器也會判斷下自己有沒有父加載器,如果有的話,也會委托給自己的父加載器來執(zhí)行加載任務,就這樣層層地委托下去,直到父加載器為null
if (parent != null) {
//如果父加載器為null,就將加載目標類的任務委托給引導類加載器來加載,也就是說通過雙親委派機制的層層委托之后,最終接收這個加載任務來執(zhí)行的是引導類加載器
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
//然后這里判斷下引導類加載器是否加載目標類成功了,如果成功則返回加載類信息得到的字節(jié)碼對象,
//否則,通過本類加載器的findClass方法來加載目標類
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
//通過本類加載器的findClass方法來加載目標類
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);
}
//返回目標類的字節(jié)碼對象,可能為null
return c;
}
}
通過上述分析,可以了解到,當前類加載器會將加載目標類的任務先委托給它的父加載器,然后父加載器也是同理先把加載目標類的任務委托給它的父加載器,...,就這樣層層委托,最后委托給引導類加載器,如果引導類加載器加載不到的話(因為它只能加載特定目錄下的,不在那個目錄下就加載不到),就由當前那個類加載器自己的加載邏輯findClass來加載,然后返回。
由于上述的委派給父類加載的流程其實是一個遞歸調(diào)用邏輯,所以就存在層層返回的情況,如果上一層返回后,沒有加載到目標類,那么就返回null;
當返回結(jié)果是null,那么就需要調(diào)用當前的類加載器的加載邏輯findClass來加載目標類,如果加載到了目標類,則直接層層返回即可。
4、上述過程解析了類加載器加載目標類的思路,分析我們常聽說的雙親委派機制到底是什么,如何實現(xiàn)委派的。
我們知道了雙親委派是什么樣的,那么我們應該如何破壞這個機制呢?
破壞雙親委派的正確思路:
因為一個最基本的java類最起碼都要繼承java.lang.Object類,而這個類是核心類,核心類是只能由引導類加載器進行加載,所以正確的實現(xiàn)邏輯應該是自己想要特殊加載的直接由自定義加載器加載,不向父加載器委托,但是呢實現(xiàn)邏輯里必須兼容的一點是對java核心類使用雙親委派機制,將核心類交由引導類加載器進行加載,以防直接使用自定義類加載器的話出現(xiàn)最基本的java.lang.Object類加載不到的情況
破壞雙親委派機制的正確代碼示例,自定義類加載器類詳情,建議創(chuàng)建類加載器時可以選擇不指定父加載器,或者指定父加載器為非空的類加載器,但是不建議指定父加載器為null;
因為如果指定類加載器的父加載器為null的話,那么當加載目標類時,如果目標類的父類是需要委托加載的話,那么它是直接委托給引導類加載器了(因為parent為null),如果這個類是核心類,直接由引導類加載器加載那倒是沒有問題,但如果這個父類是需要擴展類加載器或應用類加載器加載的話,就會出現(xiàn)加載不到的問題。
而如果創(chuàng)建類加載器時指定了父加載器的話,那么肯定是可以做到層層往上委托的,而不是一下子直接委托給引導類加載器,就不會出現(xiàn)類加載不到的問題。
public class MyClassLoader extends ClassLoader{
//用于指定類加載器要加載的類地址
private String classPath;
//無參構(gòu)造方法
public MyClassLoader(String classPath){
super();
this.classPath = classPath;
}
//帶參構(gòu)造方法,
//參數(shù)是ClassLoader parent,用于指定當前類加載器的父加載器
public MyClassLoader(String classPath, ClassLoader parent){
super(parent);
this.classPath = classPath;
}
/**
* 最終會由這個方法來調(diào)用findClass方法
* 這個方法中體現(xiàn)了類的雙親委派加載
* 想要打破雙親委派也是在這個方法實現(xiàn)
* @param name
* @param resolve
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
/**
* 沿用雙親委派的那一套
*/
// return super.loadClass(name, resolve);
/**
* 破壞了類加載的雙親委派流程:
* 自己需要特殊加載的類直接就由本類加載器進行加載;
* 而核心類就向父加載器進行委托,最終由引導類加載器進行加載。
*
* 破壞雙親委派就那么簡單。
*
* 需要重寫loadClass方法;
* 因為雙親委派的邏輯實現(xiàn)就是在ClassLoader的loadClass方法中。
*
* 在這種情況下,自定義的類加載器的父加載器在賦值時絕不能賦空值,否則它就沒有父加載器了,就沒法向上委托加載任務了。
*
* 可以只重寫loadClass方法而不用去管findClass方法,把findClass的實現(xiàn)邏輯都放到loadClass就行了;
* 當然也可以同時重寫loadClass和findClass方法。
*
*/
//判斷類是否已經(jīng)加載過了,如果加載過了則直接返回即可
Class<?> loadedClass = findLoadedClass(name);
//沒加載過的話,需要執(zhí)行加載邏輯
if (loadedClass == null){
//如果加載的是目標類,那么直接調(diào)用本類的findClass方法即可,不委托給父加載器
if (name.startsWith("test.")){
loadedClass = this.findClass(name);
} else {
//否則調(diào)用父類的加載邏輯,使用雙親委派機制進行加載(目標類可能繼承了某些核心類,需要系統(tǒng)類加載器才能加載的)
return super.loadClass(name, false);
}
}
//如果需要即時解析,則進行解析操作
if (resolve) resolveClass(loadedClass);
//返回加載的Class對象
return loadedClass;
}
/**
* 這個方法與上面那個方法的作用是一樣的,唯一的區(qū)別是,這個方法沒有resolve參數(shù)
* 默認加載后先不解析
* @param name
* @return
* @throws ClassNotFoundException
*/
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
return this.loadClass(name, false);
}
/**
* 其作用是從磁盤讀取字節(jié)碼文件到內(nèi)存,
* 實際的類加載流程,最終loadClass方法還是會調(diào)用findClass方法的
* @param name
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
File file = new File(classPath);
try {
/**
* 讀字節(jié)流,不需要reader
*/
FileInputStream in = new FileInputStream(file);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesNumRead = 0;
while ((bytesNumRead = in.read(buffer)) != -1) {
baos.write(buffer, 0, bytesNumRead);
}
byte[] data = baos.toByteArray();
if (data == null){
throw new ClassNotFoundException("找不到目標類,加載目標類失敗...");
} else {
/**
* 將讀取的字節(jié)碼數(shù)據(jù)交給jvm去構(gòu)建成字節(jié)碼對象
*/
return defineClass(name, data, 0, data.length);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
破壞雙親委派錯誤的代碼示例:

這個代碼示例會報錯:java.lang.Object類找不到。

原因:loadClass方法中僅包含了本類實現(xiàn)的加載類的邏輯,但是并沒有包含加載java核心類的邏輯,即沒有將核心類的加載委托給父加載器進行加載,所以導致核心類加載不到,因為任何一個java類最起碼都會繼承java.lang.Object類,如果子類的實現(xiàn)邏輯中完全沒有雙親委托邏輯,核心類的加載最終就到不了引導類加載器,所以就報了找不到Object類的異常。
System.out.println(loadClass.getClassLoader());
類的加載器是什么取決于該類被哪個加載器加載的,如果它是被自定義加載器加載的,那么它的類加載器肯定是自定義的加載器;
但是如果它是被應用類加載器加載的,那么它的加載器就應該是應用類加載器;
同理,對于其他的加載器也是這樣。
因為類加載有委托機制,所以目標類的加載器就不一定是最底層的子加載器了,有可能是它的父加載器或者更上層的加載器。
總之,需要明白的一點就是:哪種加載器加載的目標類,那么目標類的加載器就是哪種。
java雙親委派模型解析
參考:
https://blog.csdn.net/briblue/article/details/54973413
如何破壞雙親委派模型
參考:
https://blog.csdn.net/weixin_43884884/article/details/107719529