Java中的ClassLoader和SPI机制
深入探討 Java 類加載器
成富是著名的Java專家,在IBM技術(shù)網(wǎng)站發(fā)表很多Java好文,也有著作。
線程上下文類加載器
線程上下文類加載器(context class loader)是從 JDK 1.2 開始引入的。類?java.lang.Thread中的方法?getContextClassLoader()和?setContextClassLoader(ClassLoader cl)用來獲取和設(shè)置線程的上下文類加載器。如果沒有通過?setContextClassLoader(ClassLoader cl)方法進(jìn)行設(shè)置的話,線程將繼承其父線程的上下文類加載器。Java 應(yīng)用運(yùn)行的初始線程的上下文類加載器是系統(tǒng)類加載器。在線程中運(yùn)行的代碼可以通過此類加載器來加載類和資源。
前面提到的類加載器的代理模式并不能解決 Java 應(yīng)用開發(fā)中會(huì)遇到的類加載器的全部問題。Java 提供了很多服務(wù)提供者接口(Service Provider Interface,SPI),允許第三方為這些接口提供實(shí)現(xiàn)。常見的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。這些 SPI 的接口由 Java 核心庫(kù)來提供,如 JAXP 的 SPI 接口定義包含在?javax.xml.parsers包中。這些 SPI 的實(shí)現(xiàn)代碼很可能是作為 Java 應(yīng)用所依賴的 jar 包被包含進(jìn)來,可以通過類路徑(CLASSPATH)來找到,如實(shí)現(xiàn)了 JAXP SPI 的?Apache Xerces所包含的 jar 包。SPI 接口中的代碼經(jīng)常需要加載具體的實(shí)現(xiàn)類。如 JAXP 中的?javax.xml.parsers.DocumentBuilderFactory類中的?newInstance()方法用來生成一個(gè)新的?DocumentBuilderFactory的實(shí)例。這里的實(shí)例的真正的類是繼承自?javax.xml.parsers.DocumentBuilderFactory,由 SPI 的實(shí)現(xiàn)所提供的。如在 Apache Xerces 中,實(shí)現(xiàn)的類是?org.apache.xerces.jaxp.DocumentBuilderFactoryImpl。而問題在于,SPI 的接口是 Java 核心庫(kù)的一部分,是由引導(dǎo)類加載器來加載的;SPI 實(shí)現(xiàn)的 Java 類一般是由系統(tǒng)類加載器來加載的。引導(dǎo)類加載器是無法找到 SPI 的實(shí)現(xiàn)類的,因?yàn)樗患虞d Java 的核心庫(kù)。它也不能代理給系統(tǒng)類加載器,因?yàn)樗窍到y(tǒng)類加載器的祖先類加載器。也就是說,類加載器的代理模式無法解決這個(gè)問題。
線程上下文類加載器正好解決了這個(gè)問題。如果不做任何的設(shè)置,Java 應(yīng)用的線程的上下文類加載器默認(rèn)就是系統(tǒng)上下文類加載器。在 SPI 接口的代碼中使用線程上下文類加載器,就可以成功的加載到 SPI 實(shí)現(xiàn)的類。線程上下文類加載器在很多 SPI 的實(shí)現(xiàn)中都會(huì)用到。
?
譯文:走出類加載器迷宮
原文:https://www.javaworld.com/article/2077344/find-a-way-out-of-the-classloader-maze.html
Q:我什么時(shí)候該用Thread.getContextClassLoader()?
當(dāng)動(dòng)態(tài)加載一個(gè)資源時(shí),至少有三種類加載器可供選擇:?系統(tǒng)類加載器(也被稱為應(yīng)用類加載器)(system?classloader),當(dāng)前類加載器(current?classloader),和當(dāng)前線程的上下文類加載器(?the current thread?context?classloader)。上面提到的問題指的是最后一種加載器。
容易排除的一個(gè)選擇:系統(tǒng)類加載器。這個(gè)類加載器處理classpath環(huán)境變量所指定的路徑下的類和資源,可以通過ClassLoader.getSystemClassLoader()方法以編程式訪問。所有的ClassLoader.getSystemXXX()API方法也是通過這個(gè)類加載器訪問。
當(dāng)前類加載器加載和定義當(dāng)前方法所屬的那個(gè)類。這個(gè)類加載器在你使用帶單個(gè)參數(shù)的Class.forName()方法,Class.getResource()方法和相似方法時(shí)會(huì)在運(yùn)行時(shí)類的鏈接過程中被隱式調(diào)用。
線程上下文類加載器是在J2SE中被引進(jìn)的。每一個(gè)線程分配一個(gè)上下文類加載器(除非線程由本地代碼創(chuàng)建)。該加載器是通過Thread.setContextClassLoader()方法來設(shè)置。如果你在線程構(gòu)造后不調(diào)用這個(gè)方法,這個(gè)線程將會(huì)從它的父線程中繼承上下文類加載器。如果你在整個(gè)應(yīng)用中不做任何設(shè)置,所有線程將以系統(tǒng)類加載器作為它們自己的上下文加載器。重要的是明白自從Web和J2EE應(yīng)用服務(wù)器為了像JNDI,線程池,組件熱部署等特性而采用復(fù)雜的類加載器層次結(jié)構(gòu)后,這是很少見的情況。
上下文類加載器提供了一個(gè)后門繞過在J2SE中介紹的類的加載委托機(jī)制。通常情況下,一個(gè)JVM中的所有類加載器被組織成一個(gè)層次結(jié)構(gòu),使得每一個(gè)類加載器(除了啟動(dòng)整個(gè)JVM的原始類加載器)都有一個(gè)父加載器。當(dāng)被要求加載一個(gè)類時(shí),每一個(gè)類加載器都將先委托父加載器來加載,只有父加載器都不能成功加載時(shí)當(dāng)前類加載器才會(huì)加載。
有時(shí)這種加載順序不能正常工作,通常發(fā)生在有些JVM核心代碼必須動(dòng)態(tài)加載由應(yīng)用程序開發(fā)人員提供的資源時(shí)。以JNDI舉例:它的核心內(nèi)容(從J2SE1.3開始)在rt.jar中的引導(dǎo)類中實(shí)現(xiàn)了,但是這些JNDI核心類可能加載由獨(dú)立廠商實(shí)現(xiàn)和部署在應(yīng)用程序的classpath中的JNDI提供者。這個(gè)場(chǎng)景要求一個(gè)父類加載器(這個(gè)例子中的原始類加載器,即加載rt.jar的加載器)去加載一個(gè)在它的子類加載器(系統(tǒng)類加載器)中可見的類。此時(shí)通常的J2SE委托機(jī)制不能工作,解決辦法是讓JNDI核心類使用線程上下文加載器,從而有效建立一條與類加載器層次結(jié)構(gòu)相反方向的“通道”達(dá)到正確的委托。
?
Tomcat官網(wǎng)類裝載機(jī)如何操作
?
【Tomcat學(xué)習(xí)筆記】9-ClassLoader
Tomcat的三大ClassLoader
為什么 Tomcat 里要自定義 ClassLoader 呢,先來考慮一個(gè)問題:一個(gè)Tomcat 部署兩個(gè)應(yīng)用,App1 和 App2, App1 里定義了一個(gè) com.fdx.AAA 類,App2 也定義了一個(gè) com.fdx.AAA 類,但是里面的實(shí)現(xiàn)是不一樣的,如果不自定義 ClassLoader,
而都用 AppClassLoader 來加載的話,你讓它加載哪一個(gè)呢,一個(gè) ClassLoader 是不能加載兩個(gè)一樣的類的。所以,ClassLoader 最重要的一個(gè)功能就是 類隔離。
?
SPI機(jī)制
JavaSPI 實(shí)際上是“基于接口的編程+策略模式+配置文件”組合實(shí)現(xiàn)的動(dòng)態(tài)加載機(jī)制。具體而言:
? ? ? ?STEP1. 定義一組接口, 假設(shè)是 autocomplete.PrefixMatcher;
? ? ? ?STEP2. 寫出接口的一個(gè)或多個(gè)實(shí)現(xiàn)(autocomplete.EffectiveWordMatcher,?autocomplete.SimpleWordMatcher);
? ? ? ?STEP3. 在 src/main/resources/ 下建立 /META-INF/services 目錄, 新增一個(gè)以接口命名的文件?autocomplete.PrefixMatcher, 內(nèi)容是要應(yīng)用的實(shí)現(xiàn)類(autocomplete.EffectiveWordMatcher 或 autocomplete.SimpleWordMatcher 或兩者);
? ? ? ?STEP4. 使用 ServiceLoader 來加載配置文件中指定的實(shí)現(xiàn)。?
SPI 的應(yīng)用之一是可替換的插件機(jī)制。比如查看 JDBC 數(shù)據(jù)庫(kù)驅(qū)動(dòng)包,mysql-connector-java-5.1.18.jar?就有一個(gè) /META-INF/services/java.sql.Driver 里面內(nèi)容是?com.mysql.jdbc.Driver 。
package org.foo.demo;public interface IShout {void shout();
}
package org.foo.demo;import java.util.ServiceLoader;public class SPIMain {public static void main(String[] args) {ServiceLoader<IShout> shouts = ServiceLoader.load(IShout.class);for (IShout s : shouts) {s.shout();}System.out.println("Thread "+Thread.currentThread().getName()+" classloader: "+Thread.currentThread().getContextClassLoader().toString());}
}
package org.foo.demo.animal;import org.foo.demo.IShout;public class Cat implements IShout {@Overridepublic void shout() {System.out.println("喵喵");System.out.println("Thread "+Thread.currentThread().getName()+" classloader: "+Thread.currentThread().getContextClassLoader().toString());}
}
package org.foo.demo.animal;import org.foo.demo.IShout;public class Dog implements IShout {@Overridepublic void shout() {System.out.println("旺旺");System.out.println("Thread "+Thread.currentThread().getName()+" classloader: "+Thread.currentThread().getContextClassLoader().toString());}
}
《高級(jí)開發(fā)必須理解的Java中SPI機(jī)制》
優(yōu)點(diǎn):
使用Java SPI機(jī)制的優(yōu)勢(shì)是實(shí)現(xiàn)解耦,使得第三方服務(wù)模塊的裝配控制的邏輯與調(diào)用者的業(yè)務(wù)代碼分離,而不是耦合在一起。應(yīng)用程序可以根據(jù)實(shí)際業(yè)務(wù)情況啟用框架擴(kuò)展或替換框架組件。
缺點(diǎn):
- 雖然ServiceLoader也算是使用的延遲加載,但是基本只能通過遍歷全部獲取,也就是接口的實(shí)現(xiàn)類全部加載并實(shí)例化一遍。如果你并不想用某些實(shí)現(xiàn)類,它也被加載并實(shí)例化了,這就造成了浪費(fèi)。獲取某個(gè)實(shí)現(xiàn)類的方式不夠靈活,只能通過Iterator形式獲取,不能根據(jù)某個(gè)參數(shù)來獲取對(duì)應(yīng)的實(shí)現(xiàn)類。
- 多個(gè)并發(fā)多線程使用ServiceLoader類的實(shí)例是不安全的。
---------------------------------------------
Tomcat的類加載機(jī)制是違反了雙親委托原則的,對(duì)于一些未加載的非基礎(chǔ)類(Object,String等),各個(gè)web應(yīng)用自己的類加載器(WebAppClassLoader)會(huì)優(yōu)先加載,加載不到時(shí)再交給commonClassLoader走雙親委托。?
對(duì)于JVM來說:
因此,按照這個(gè)過程可以想到,如果同樣在CLASSPATH指定的目錄中和自己工作目錄中存放相同的class,會(huì)優(yōu)先加載CLASSPATH目錄中的文件。
1、既然 Tomcat 不遵循雙親委派機(jī)制,那么如果我自己定義一個(gè)惡意的HashMap,會(huì)不會(huì)有風(fēng)險(xiǎn)呢?
答: 顯然不會(huì)有風(fēng)險(xiǎn),如果有,Tomcat都運(yùn)行這么多年了,那群Tomcat大神能不改進(jìn)嗎? tomcat不遵循雙親委派機(jī)制,只是自定義的classLoader順序不同,但頂層還是相同的,
還是要去頂層請(qǐng)求classloader.
2、我們思考一下:Tomcat是個(gè)web容器, 那么它要解決什么問題:?
1. 一個(gè)web容器可能需要部署兩個(gè)應(yīng)用程序,不同的應(yīng)用程序可能會(huì)依賴同一個(gè)第三方類庫(kù)的不同版本,不能要求同一個(gè)類庫(kù)在同一個(gè)服務(wù)器只有一份,因此要保證每個(gè)應(yīng)用程序的類庫(kù)都是獨(dú)立的,保證相互隔離。?
2. 部署在同一個(gè)web容器中相同的類庫(kù)相同的版本可以共享。否則,如果服務(wù)器有10個(gè)應(yīng)用程序,那么要有10份相同的類庫(kù)加載進(jìn)虛擬機(jī),這是扯淡的。?
3. web容器也有自己依賴的類庫(kù),不能于應(yīng)用程序的類庫(kù)混淆?;诎踩紤],應(yīng)該讓容器的類庫(kù)和程序的類庫(kù)隔離開來。?
4. web容器要支持jsp的修改,我們知道,jsp 文件最終也是要編譯成class文件才能在虛擬機(jī)中運(yùn)行,但程序運(yùn)行后修改jsp已經(jīng)是司空見慣的事情,否則要你何用? 所以,web容器需要支持 jsp 修改后不用重啟。
再看看我們的問題:Tomcat 如果使用默認(rèn)的類加載機(jī)制行不行??
答案是不行的。為什么?我們看,第一個(gè)問題,如果使用默認(rèn)的類加載器機(jī)制,那么是無法加載兩個(gè)相同類庫(kù)的不同版本的,默認(rèn)的累加器是不管你是什么版本的,只在乎你的全限定類名,并且只有一份。第二個(gè)問題,默認(rèn)的類加載器是能夠?qū)崿F(xiàn)的,因?yàn)樗穆氊?zé)就是保證唯一性。第三個(gè)問題和第一個(gè)問題一樣。我們?cè)倏吹谒膫€(gè)問題,我們想我們要怎么實(shí)現(xiàn)jsp文件的熱修改(樓主起的名字),jsp 文件其實(shí)也就是class文件,那么如果修改了,但類名還是一樣,類加載器會(huì)直接取方法區(qū)中已經(jīng)存在的,修改后的jsp是不會(huì)重新加載的。那么怎么辦呢?我們可以直接卸載掉這jsp文件的類加載器,所以你應(yīng)該想到了,每個(gè)jsp文件對(duì)應(yīng)一個(gè)唯一的類加載器,當(dāng)一個(gè)jsp文件修改了,就直接卸載這個(gè)jsp類加載器。重新創(chuàng)建類加載器,重新加載jsp文件。
《一看你就懂,超詳細(xì)java中的ClassLoader詳解》
《java中的反射》
《深入探討Java類加載機(jī)制》
《Java類加載器ClassLoader總結(jié)》
《圖解Tomcat類加載機(jī)制(阿里面試題)》
《java attach機(jī)制源碼閱讀》
=====================
《Class熱替換與卸載》代碼實(shí)現(xiàn)
所以一個(gè)class被一個(gè)ClassLoader實(shí)例加載過的話,就不能再被這個(gè)ClassLoader實(shí)例再次加載(這里的加載指的是,調(diào)用了defileClass(...)方法,重新加載字節(jié)碼、解析、驗(yàn)證)。而系統(tǒng)默認(rèn)的AppClassLoader加載器,他們內(nèi)部會(huì)緩存加載過的class,重新加載的話,就直接取緩存。所與對(duì)于熱加載的話,只能重新創(chuàng)建一個(gè)ClassLoader,然后再去加載已經(jīng)被加載過的class文件。
GIT@OSC工程路徑:http://git.oschina.net/taomk/king-training/tree/master/class-loader
------------------
《Java服務(wù)器熱部署的實(shí)現(xiàn)原理》
Java中類的加載方式。每一個(gè)應(yīng)用程序的類都會(huì)被ClassLoader加載,所以,要實(shí)現(xiàn)一個(gè)支持熱部署的應(yīng)用,我們可以對(duì)每一個(gè)用戶自定義的應(yīng)用程序使用一個(gè)單獨(dú)的ClassLoader進(jìn)行加載。然后,當(dāng)某個(gè)用戶自定義的應(yīng)用程序發(fā)生變化的時(shí)候,我們首先銷毀原來的應(yīng)用,然后使用一個(gè)新的ClassLoader來加載改變之后的應(yīng)用。而所有其他的應(yīng)用程序不會(huì)受到一點(diǎn)干擾。
?
有了總體實(shí)現(xiàn)思路之后,我們可以想到如下幾個(gè)需要完成的目標(biāo):
1、定義一個(gè)用戶自定義應(yīng)用程序的接口,這是因?yàn)?#xff0c;我們需要在容器應(yīng)用中去加載用戶自定義的應(yīng)用程序。
2、我們還需要一個(gè)配置文件,讓用戶去配置他們的應(yīng)用程序。
3、應(yīng)用啟動(dòng)的時(shí)候,加載所有已有的用戶自定義應(yīng)用程序。
4、為了支持熱部署,我們需要一個(gè)監(jiān)聽器,來監(jiān)聽?wèi)?yīng)用發(fā)布目錄中每個(gè)文件的變動(dòng)。這樣,當(dāng)某個(gè)應(yīng)用重新部署之后,我們就可以得到通知,進(jìn)而進(jìn)行熱部署處理。
?
要實(shí)現(xiàn)熱部署,我們之前說過,需要一個(gè)監(jiān)聽器,來監(jiān)聽發(fā)布目錄applications,這樣當(dāng)某個(gè)應(yīng)用程序的jar文件改變時(shí),我們可以進(jìn)行熱部署處理。其實(shí),要實(shí)現(xiàn)目錄文件改變的監(jiān)聽,有很多種方法,這個(gè)例子中我使用的是apache的一個(gè)開源虛擬文件系統(tǒng)——common-vfs。如果你對(duì)其感興趣,你可以訪問http://commons.apache.org/proper/commons-vfs/。
當(dāng)某個(gè)文件改變的時(shí)候,該方法會(huì)被回調(diào)。所以,我們?cè)谶@個(gè)方法中調(diào)用了ApplicationManager的reloadApplication方法,重現(xiàn)加載該應(yīng)用程序。
public void reloadApplication (String name){IApplication oldApp = this.apps .remove(name);if(oldApp == null){return;}oldApp.destory(); //call the destroy method in the user's applicationAppConfig config = this.configManager .getConfig(name);if(config == null){return;}createApplication(getBasePath(), config);}
重新加載應(yīng)用程序時(shí),我們首先從內(nèi)存中刪除該應(yīng)用程序,然后調(diào)用原來應(yīng)用程序的destory方法,最后按照配置重新創(chuàng)建該應(yīng)用程序?qū)嵗?/p>
為了讓整個(gè)應(yīng)用程序可以持續(xù)的運(yùn)行而不會(huì)結(jié)束,我們修改下啟動(dòng)方法無限循環(huán),300ms.
public static void main(String[] args){Thread t = new Thread(new Runnable() {@Overridepublic void run() {ApplicationManager manager = ApplicationManager.getInstance();manager.init();}});t.start();while(true ){try {Thread. sleep(300);} catch (InterruptedException e) {e.printStackTrace();}}}
?
談?wù)凧ava的的SPI機(jī)制
當(dāng)服務(wù)的提供者提供了服務(wù)接口的一種實(shí)現(xiàn)之后,必須根據(jù)SPI約定在 META-INF/services/目錄里創(chuàng)建一個(gè)以服務(wù)接口命名的文件,該文件里寫的就是實(shí)現(xiàn)該服務(wù)接口的具體實(shí)現(xiàn)類。當(dāng)程序調(diào)用ServiceLoader的load方法的時(shí)候,ServiceLoader能夠通過約定的目錄找到指定的文件,并裝載實(shí)例化,完成服務(wù)的發(fā)現(xiàn)。
JDBC中的SPI機(jī)制
回到之前的一個(gè)問題,為什么只需要下面的一行代碼,再提供商不同廠商的jar包,就可以輕松創(chuàng)建連接了呢?
Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
DriverManager中有一個(gè)靜態(tài)代碼塊,在調(diào)用getConnection之前就會(huì)被調(diào)用:
/*** Load the initial JDBC drivers by checking the System property* jdbc.properties and then use the {@code ServiceLoader} mechanism*/static {loadInitialDrivers();println("JDBC DriverManager initialized");}private static void loadInitialDrivers() {String drivers;// 1、處理系統(tǒng)屬性jdbc.drivers配置的值try {drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {public String run() {return System.getProperty("jdbc.drivers");}});} catch (Exception ex) {drivers = null;}// If the driver is packaged as a Service Provider, load it.// Get all the drivers through the classloader// exposed as a java.sql.Driver.class service.// ServiceLoader.load() replaces the sun.misc.Providers()AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {
// 2、處理通過ServiceLoader加載的Driver類ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);Iterator<Driver> driversIterator = loadedDrivers.iterator();/* Load these drivers, so that they can be instantiated.* It may be the case that the driver class may not be there* i.e. there may be a packaged driver with the service class* as implementation of java.sql.Driver but the actual class* may be missing. In that case a java.util.ServiceConfigurationError* will be thrown at runtime by the VM trying to locate* and load the service.** Adding a try catch block to catch those runtime errors* if driver not available in classpath but it's* packaged as service and that service is there in classpath.*/
// 加載配置在META-INF/services/java.sql.Driver文件里的Driver實(shí)現(xiàn)類try{while(driversIterator.hasNext()) {driversIterator.next();}} catch(Throwable t) {// Do nothing}return null;}});println("DriverManager.initialize: jdbc.drivers = " + drivers);if (drivers == null || drivers.equals("")) {return;}String[] driversList = drivers.split(":");println("number of Drivers:" + driversList.length);for (String aDriver : driversList) {try {println("DriverManager.Initialize: loading " + aDriver);
// 3、加載driver類Class.forName(aDriver, true,ClassLoader.getSystemClassLoader());} catch (Exception ex) {println("DriverManager.Initialize: load failed: " + ex);}}}
JDBC使用了SPI機(jī)制,讓所有的任務(wù)都交給不同的數(shù)據(jù)庫(kù)廠商各自去完成,無論是實(shí)現(xiàn)Driver接口,還是SPI要求的接口文件,都做到了讓用戶不需要關(guān)心一點(diǎn)細(xì)節(jié),一行代碼建立連接。
?
[討論]?關(guān)于Thread.getContextClassLoader的使用場(chǎng)景問題
=================
以下出自尚學(xué)堂高琪課程 ,說的還是比較全面和準(zhǔn)確的
類加載器的作用
– 將class文件字節(jié)碼內(nèi)容加載到內(nèi)存中,并將這些靜態(tài)數(shù)據(jù)轉(zhuǎn)換成方法區(qū)中的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu),在堆中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象,作為方法區(qū)類數(shù)據(jù)的訪問入口。
? 類緩存
? 標(biāo)準(zhǔn)的Java SE類加載器可以按要求查找類,但一旦某個(gè)類被加載到類加載器中,它將維持加載(緩存)一段時(shí)間。不過,JVM垃圾收集器可以回收這些Class對(duì)象。
?
類加載器的代理模式
? 代理模式
– 交給其他加載器來加載指定的類
? 雙親委托機(jī)制
– 就是某個(gè)特定的類加載器在接到加載類的請(qǐng)求時(shí),首先將加載任務(wù)委托給父類加載器,依次追溯,直到最高的爺爺輩的,如果父類加載器可以完成類加載任務(wù),就成功返回;只有父類加載器無法完成此加載任務(wù)時(shí),才自己去加載。
– 雙親委托機(jī)制是為了保證 Java 核心庫(kù)的類型安全。
? 這種機(jī)制就保證不會(huì)出現(xiàn)用戶自己能定義java.lang.Object類的情況。
– 類加載器除了用于加載類,也是安全的最基本的屏障。
? 雙親委托機(jī)制是代理模式的一種
– 并不是所有的類加載器都采用雙親委托機(jī)制。
– tomcat服務(wù)器類加載器也使用代理模式,所不同的是它是首先嘗試去加載某個(gè)類,如果找不到再代理給父類加載器。
這與一般類加載器的順序是相反的
?
自定義類加載器的流程:
– 1、首先檢查請(qǐng)求的類型是否已經(jīng)被這個(gè)類裝載器裝載到命名空間中了,如果已經(jīng)裝載,直接返回;否則轉(zhuǎn)入步驟2
– 2、委派類加載請(qǐng)求給父類加載器(更準(zhǔn)確的說應(yīng)該是雙親類加載器,真?zhèn)€虛擬機(jī)中各種類加載器最終會(huì)呈現(xiàn)樹狀結(jié)構(gòu)),如果父類加載器能夠完成,則返回父類加載器加載的Class實(shí)例;否則轉(zhuǎn)入步驟3
– 3、調(diào)用本類加載器的findClass(…)方法,試圖獲取對(duì)應(yīng)的字節(jié)碼,如果獲取的到,則調(diào)用defineClass(…)導(dǎo)入類型到方法區(qū);如果獲取不到對(duì)應(yīng)的字節(jié)碼或者其他原因失敗,返回異常給loadClass(…), loadClass(…)轉(zhuǎn)拋異常,終止加載過程(注意:這里的異常種類不止一種)。
– 注意:被兩個(gè)類加載器加載的同一個(gè)類,JVM不認(rèn)為是相同的類。
? 文件類加載器
? 網(wǎng)絡(luò)類加載器
? 加密解密類加載器(取反操作,DES對(duì)稱加密解密)?
?
線程上下文類加載器
雙親委托機(jī)制以及默認(rèn)類加載器的問題
– 一般情況下, 保證同一個(gè)類中所關(guān)聯(lián)的其他類都是由當(dāng)前類的類加載器所加載的.。
比如,ClassA本身在Ext下找到,那么他里面new出來的一些類也就只能用Ext去查找了(不會(huì)低一個(gè)級(jí)別),所以有
些明明App可以找到的,卻找不到了。
– JDBC API,他有實(shí)現(xiàn)的driven部分(mysql/sql server),我們的JDBC API都是由Boot或者Ext來載入的,但是
JDBC driver卻是由Ext或者App來載入,那么就有可能找不到driver了。在Java領(lǐng)域中,其實(shí)只要分成這種Api+SPI(
Service Provide Interface,特定廠商提供)的,都會(huì)遇到此問題。
– 常見的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。這些 SPI 的接口由 Java 核心庫(kù)來提供,如 JAXP 的 SPI 接口定
義包含在 javax.xml.parsers 包中。SPI 的接口是 Java 核心庫(kù)的一部分,是由引導(dǎo)類加載器來加載的;SPI 實(shí)現(xiàn)的
Java 類一般是由系統(tǒng)類加載器來加載的。引導(dǎo)類加載器是無法找到 SPI 的實(shí)現(xiàn)類的,因?yàn)樗患虞d Java 的核心庫(kù)。
? 通常當(dāng)你需要?jiǎng)討B(tài)加載資源的時(shí)候 , 你至少有三個(gè) ClassLoader 可以選擇 :
– 1.系統(tǒng)類加載器或叫作應(yīng)用類加載器 (system classloader or application classloader)
– 2.當(dāng)前類加載器
– 3.當(dāng)前線程類加載器
? 當(dāng)前線程類加載器是為了拋棄雙親委派加載鏈模式。
– 每個(gè)線程都有一個(gè)關(guān)聯(lián)的上下文類加載器。
如果你使用new Thread()方式生成新的線程,新線程將繼承其父線程的上下文類加載器。
如果程序?qū)€程上下文類加載器沒有任何改動(dòng)的話,程序中所有的線程將都使用系統(tǒng)類加載器作為上下文類加載器。
? Thread.currentThread().getContextClassLoader()
public class TCCC {public static void main(String[] args) throws Exception {ClassLoader loader = TCCC.class.getClassLoader();System.out.println(loader);ClassLoader loader2 = Thread.currentThread().getContextClassLoader();System.out.println(loader2);Thread.currentThread().setContextClassLoader(new FileSystemClassLoader("d:/"));System.out.println(Thread.currentThread().getContextClassLoader());Class<Test> c = (Class<Test>) Thread.currentThread().getContextClassLoader().loadClass("com.current.www.Test");System.out.println(c);System.out.println(c.getClassLoader());}
}
TOMCAT服務(wù)器的類加載機(jī)制
? 一切都是為了安全!
– TOMCAT不能使用系統(tǒng)默認(rèn)的類加載器。
? 如果TOMCAT跑你的WEB項(xiàng)目使用系統(tǒng)的類加載器那是相當(dāng)危險(xiǎn)的,你可以直接是無忌憚操作操作系統(tǒng)的各個(gè)目錄了。
? 對(duì)于運(yùn)行在 Java EE?容器中的 Web 應(yīng)用來說,類加載器的實(shí)現(xiàn)方式與一般的 Java 應(yīng)用有所不同。
? 每個(gè) Web 應(yīng)用都有一個(gè)對(duì)應(yīng)的類加載器實(shí)例。該類加載器也使用代理模式(不同于前面說的雙親委托機(jī)制),所不同的是它是首先嘗試去加載某個(gè)類,如果找不到再代理給父類加載器。這與一般類加載器的順序是相反的但也是為了保證安全,這樣核心庫(kù)就不在查詢范圍之內(nèi)。
? 為了安全TOMCAT需要實(shí)現(xiàn)自己的類加載器。
? 我可以限制你只能把類寫在指定的地方,否則我不給你加載!
總結(jié)
以上是生活随笔為你收集整理的Java中的ClassLoader和SPI机制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 我爱你你爱她她爱他这是什么歌?
- 下一篇: 升降机多少钱啊?