書接上回
在 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進行的加載
因此,我們加載不同的插件包時,可以實現類的隔離加載