Java自定義ClassLoader實現插件類隔離加載 - 原理篇


書接上回

在 Java自定義ClassLoader實現插件類隔離加載文章中,我們通過

自定義ClassLoader + 插件獨立打包引入的方式,實現了同依賴不同版本的隔離加載

這次咱們來分析下具體實現原理

 

打破雙親委派機制

首先,雙親委派機制不會自己去嘗試加載類,而是把請求委托給父加載器去完成,依次向上

其次,思考一個問題

以前使用Tomcat部署項目時,webapp目錄下可能部署多個項目的war包

這些war包中可能包含同一個類,類全限定名一樣,但是實現不一樣

那么Tomcat如何保證他們不會沖突呢?

Tomcat正是使用了打破雙親委派機制,給每一個應用創建獨立的類加載器實例WebAppClassLoader,並重寫loadClass核心方法,使得優先加載當前應用目錄下的類

 

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded(首先,檢查類是否已經加載)
Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) {
// 委托父加載器加載 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 } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime();
// 查找Class資源 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); } return c; } }

 

先分析一波loadClass的源碼

第一行用synchronized關鍵字做了對象鎖處理,這里說點額外話題,為啥對象鎖用的getClassLoadingLock(name),而不是直接用name作為對象鎖,推薦一篇博文講的非常詳細:https://www.cnblogs.com/thisiswhy/p/15892044.html

進入鎖以后,通過findLoadedClass方法,先找已經加載過的Class

已經加載過的Class中找不到的話,再通過parent.loadClass(name, false)委托父加載器加載

這段邏輯就是雙親委派的處理

 

類加載規則

如果一個類由類加載器A加載,那么這個類的依賴類也是由「相同的類加載器」加載。

 

protected Class<?> findClass(final String name)
    throws ClassNotFoundException
{
    final Class<?> result;
    try {
        // AccessController提供了一個默認的安全策略執行機制,它使用棧檢查來決定潛在不安全的操作是否被允許
        // doPrivileged方法用來終端沒有權限不被允許的操作
        result = AccessController.doPrivileged(
            new PrivilegedExceptionAction<Class<?>>() {
                public Class<?> run() throws ClassNotFoundException {
                    // 獲取Class路徑
                    String path = name.replace('.', '/').concat(".class");
                    // 加載Class資源
                    Resource res = ucp.getResource(path, false);
                    if (res != null) {
                        try {
                            // 加載Class資源,定義Class類
                            return defineClass(name, res);
                        } catch (IOException e) {
                            throw new ClassNotFoundException(name, e);
                        }
                    } else {
                        return null;
                    }
                }
            }, acc);
    } catch (java.security.PrivilegedActionException pae) {
        throw (ClassNotFoundException) pae.getException();
    }
    if (result == null) {
        throw new ClassNotFoundException(name);
    }
    return result;
}

 

總結

我們通過自定義ClassLoader,清空了父加載器,使得打破了雙親委派機制,不會通過緩存加載到AppClassLoader中已加載過的類

加載的Class類中,其它的依賴類,也是經過我們自定義的ClassLoader進行的加載

因此,我們加載不同的插件包時,可以實現類的隔離加載


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM