上接: https://www.cnblogs.com/ronnieyuan/p/11885463.html
簡介
- 雙親委派模型並不是一個強制性的約束模型, 而是Java設計者推薦給開發者的類加載器實現方式。
- 在Java的世界中大部分的類加載器都遵循這個模型, 但也有例外, 歷史上出現過3次較大規模的雙親委派模型"被破壞"情況。
三次較大規模的破壞
-
第一次發生在雙親委派模型出現之前(jdk1.2發布之前)。
-
由於類加載器和抽象類java.lang.ClassLoader 在JDK1.0時代就已經存在, 面對已經存在的用戶自定義類加載器的實現代碼, Java 設計者引入雙清委派模型時不得不做出一些妥協。
-
為了向前兼容, jdk1.2之后的java.lang.ClassLoader 添加了一個新的 protected 方法 findClass()
/** * Finds the class with the specified <a href="#name">binary name</a>. * 根據特定的二進制名來查找該類。 * This method should be overridden by class loader implementations that * follow the delegation model for loading classes, and will be invoked * by the {@link #loadClass <tt>loadClass</tt>} method after checking the * parent class loader for the requested class. * 該方法應該被實現了類加載器的的類重載, 二次遵循雙親委派模型, 並且在檢查請求的 * 類的父類加載之后會被loadClass方法調用 * The default implementation throws a <tt>ClassNotFoundException</tt>. * * @param name * The <a href="#name">binary name</a> of the class * * @return The resulting <tt>Class</tt> object * * @throws ClassNotFoundException * If the class could not be found * * @since 1.2 */ protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); }
-
在此之前, 用戶去繼承java.long.Classloader 的唯一目的是為了重寫loadClass() 方法
-
因為虛擬機在進行類加載的時候會調用加載器的私有方法loadClassInternal(), 而該方法的唯一邏輯就是去調用自己的loadClass()
// This method is invoked by the virtual machine to load a class. // 該方法有虛擬機調用來加載類 private Class<?> loadClassInternal(String name) throws ClassNotFoundException { // For backward compatibility, explicitly lock on 'this' when // the current class loader is not parallel capable. // 為了向后兼容性, 當前類加載器不是可並行的時,顯示地鎖了當前對象 if (parallelLockMap == null) { synchronized (this) { return loadClass(name); } } else { return loadClass(name); } }
-
-
第二次發生是由模型自身的缺陷導致, 咋么處理基礎類調用回用戶的代碼?
-
雙親委派很好地解決了各個類加載器的基礎類統一問題 (越基礎的類由越上層的加載器進行加載)
-
基礎類通常作為被用戶代碼調用的API, 但是當基礎類要調用回用戶的代碼時該咋么解決?
-
典型例子: JNDI(Java Naming and Directory Interface)服務,
-
它的代碼由啟動類加載器去加載(jdk1.3植入的rt.jar)。
-
JNDI的目的就是對資源進行集中管理和查找, 它需要調用獨立廠商實現並部署在應用程序的ClassPath下的JNDI接口提供者 (SPI, Service Provider Interface) 的代碼, 但啟動類加載器不認識這些代碼。
-
為了解決該問題, Java設計團隊只能引入一個不太優雅的設計: 線程上下文類加載器(Thread Context ClassLoader)。
-
這個類加載器可以通過java.lang.Thread 類的setContextClassLoader() 方法進行設置, 如果創建線程時還未設置, 它將會從父線程中繼承一個, 如果在應用程序的全局范圍內都沒有設置過的話, 那該類加載器默認就是應用程序類加載器。
/** * Sets the context ClassLoader for this Thread. The context * ClassLoader can be set when a thread is created, and allows * the creator of the thread to provide the appropriate class loader, * through {@code getContextClassLoader}, to code running in the thread * when loading classes and resources. * * <p>If a security manager is present, its {@link * SecurityManager#checkPermission(java.security.Permission) checkPermission} * method is invoked with a {@link RuntimePermission RuntimePermission}{@code * ("setContextClassLoader")} permission to see if setting the context * ClassLoader is permitted. * * @param cl * the context ClassLoader for this Thread, or null indicating the * system class loader (or, failing that, the bootstrap class loader) * * @throws SecurityException * if the current thread cannot set the context ClassLoader * * @since 1.2 */ public void setContextClassLoader(ClassLoader cl) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(new RuntimePermission("setContextClassLoader")); } contextClassLoader = cl; }
-
JNDI服務使用這個線程上下文類加載器去加載所需要的SPI代碼, 也就是父類加載器請求子類加載器去完成類加載的動作。
-
這種行為實際上就是打通了雙親委派模型的層次結構來逆向使用類加載器, 實際上違背了雙親委派模型的一般性原則。
-
Java中所有涉及 SPI的加載動作基本上都采用這種方式, 如JNDI, JDBC, JCE(Java Cryptography Extension), JAXB(JavaArchitectureforXMLBinding) 和 JBI(Java Business Integration)等。
-
-
-
第三次發生時由於用戶對程序動態性的追求而導致的。
- 如: 代碼熱替換(Hotswap), 模塊熱部署(Hot Deployment) 等, 說白了急速希望應用程序向計算機外設那樣, 接上鼠標, U盤, 不用重啟機器技能立即使用, 鼠標有問題或要升級就換個鼠標, 不用停機也不用重啟。
- 生產系統需要盡可能的不重啟, 重啟一次可能就被認定為生產事故, 這種情況下熱部署就對企業級軟件開發者有很大的吸引力。
- OSGI(開放服務網關協議,Open Service Gateway Initiative) 目前是業界的Java模塊化標准, 而OSGI實現模塊化熱部署的關鍵則是它自定義的類加載器機制的實現。
- 每一個程序模塊(Bundle) 都有一個自己的類加載器, 當需要換一個Bundle時, 就把Bundle連同類加載器一起換掉以實現代碼的熱替換。
- OSGI環境下, 類加載器不再是雙親委派模型中的樹狀結構, 而是進一步發展為更加復雜的網狀結構, 當收到類加載請求時, OSGI 將按照下面的順序進行類搜索:
- 將以java.* 開頭的類委派給父類加載器加載。
- 否則, 將委派列表名單以內的類委派給父類加載器加載。
- 否則, 將Import列表中的類委派給 Export 這個類的Bundle 的類加載加載。
- 否則, 查找當前Bundle的 ClassPath, 使用自己的類加載器加載。
- 否則, 查找類是否在自己的Fragment Bundle中, 如果在, 則委派給 Fragment Bundle的類加載器加載。
- 否則, 查找 Dynamic Import(動態導入) 列表的 Bundle, 委派給對應Bundle的類加載器。
- 否則, 類查找失敗
- 上述的查找順序只有開頭兩點仍然符合雙親委派規則, 其余的類查找都是在平級的類加載器中進行的。
- OSGI中對類加載器的使用是值得學習的。