當(dāng)前類加載器(Current Classloader)
每個(gè)類都會(huì)使用自己的類加載器(即加載自身的類加載器)來去加載其他類(指的是所以來的類)裕坊,例如:如果ClassX引用了ClassY包竹,那么ClassX的類加載器就會(huì)去加載ClassY(前提是ClassY尚未被加載)
線程上下文類加載器(Context Classloader)
- 定義
線程上下文類加載器是從JDK1.2開始引入的,類Thread中的getContextClassloader()
與setContextClassloader(ClassLoader cl)
分別用來獲取和設(shè)置上下文類加載器籍凝。
如果沒有通過setContextClassloader(ClassLoader cl)
進(jìn)行設(shè)置周瞎,線程將繼承其父線程的上下文類加載器。Java應(yīng)用運(yùn)行時(shí)的初始線程的上下文類加載器是應(yīng)用類加載器饵蒂。在線程中運(yùn)行的代碼可以通過該類加載器來加載類與資源声诸。默認(rèn)的線程上下文類加載器是應(yīng)用類加載器 - 父委托機(jī)制加載的缺點(diǎn)
例如:加載JDBC的時(shí)候,java.sql.Connection
是被啟動(dòng)類加載器(Bootstrap Classloader)加載的退盯,當(dāng)程序運(yùn)行時(shí)需要指定對(duì)應(yīng)JDBC的實(shí)現(xiàn)彼乌,但是因?yàn)楦割惣虞d器加載的類不能訪問子類加載器所加載的類,所以就無法加載實(shí)現(xiàn)類渊迁,所有的SPI(Service Provider Interface)都有這個(gè)問題慰照。 - 重要性
- 父ClassLoader可以使用當(dāng)前線程的
Thread.currentThread().getContextClassLoader()
所指定的ClassLoader加載的類。這就改變了父ClassLoader不能使用子ClassLoader或是其他沒有直接父子關(guān)系的ClassLoader加載的類的情況宫纬,即改變了雙親委托模型焚挠。父委托機(jī)制加載的缺點(diǎn) - 線程上下文類加載器就是當(dāng)前線程的Current ClassLoader。
- 在雙親委托模型下漓骚,類加載是由下至上的蝌衔,即下層的類加載器會(huì)委托上層進(jìn)行加載的。但是對(duì)于SPI來說蝌蹂,有些接口是Java核心庫所提供的噩斟,而Java核心庫是由啟動(dòng)類加載器來加載的,而這些接口的實(shí)現(xiàn)卻來自于不同的jar包(廠商提供)孤个,Java的啟動(dòng)類加載器是不會(huì)加載其他來源的jar包剃允,這樣傳統(tǒng)的雙親委托模型就無法滿足SPI的要求,而通過給當(dāng)前線程設(shè)置上下文類加載器齐鲤,就可以由設(shè)置的上下文類加載器來實(shí)現(xiàn)對(duì)于接口實(shí)現(xiàn)類的加載斥废。
- 父ClassLoader可以使用當(dāng)前線程的
- 一般使用模式(獲取 - 使用 - 還原)
ContextClassLoader的作用就是為了破壞Java的類加載委托機(jī)制给郊。ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); try{ Thread.currentThread().setContextClassLoader(targetThreadClassLoader); myMethod(); //myMethod里邊則調(diào)用了Thread.currentThread().getContextClassLoader(), //獲取當(dāng)前線程的上下文類加載器做某些事情。 } finally { Thread.currentThread().setContextClassLoader(classLoader); }
當(dāng)高層提供了統(tǒng)一的接口讓底層去實(shí)現(xiàn)牡肉,同時(shí)又要在高層加載(或?qū)嵗┑讓拥念悤r(shí),就必須要通過線程上下文類加載器來幫助高層的ClassLoader找到并加載該類淆九。 - 為什么不使用靜態(tài)方法直接獲取應(yīng)用類加載器
- 默認(rèn)情況下線程上下文類加載器是應(yīng)用類加載器统锤,但是可以設(shè)置為自定義類加載器毛俏;
- 我們所有的代碼都是運(yùn)行在線程里的,當(dāng)有了線程上下文類加載器之后就可以更方便靈活饲窿。
線程上下文類加載器的應(yīng)用 ServiceLoader
從JDK1.6開始提供java.util.ServiceLoader
煌寇。
- 運(yùn)行原理
按照雙親委托機(jī)制走到這里會(huì)報(bào)ClassNotDef的錯(cuò)誤喂走,ServiceLoader是這么解決的:public static void main(String[] args) { /** 當(dāng)前類是由應(yīng)用類加載器加載的,運(yùn)行到這一行代碼的時(shí)候逾雄, 1. 因?yàn)閖ava.util.ServiceLoader是在rt.jar中阀溶,所以通過雙親委托機(jī)制,會(huì)使用Bootstrap類加載器加載ServiceLoader嘲驾。 2. 但是我們要加載的是java.sql.Driver的實(shí)現(xiàn)類淌哟,一般都是在項(xiàng)目的lib包中。 3. ServiceLoader的類加載器是Bootstrap類加載器辽故,按照正常的模式會(huì)使用相同的類加載加載實(shí)現(xiàn)類徒仓,但是這個(gè)時(shí)候因?yàn)槊臻g的原因,Bootstrap類加載器加載不到對(duì)應(yīng)的實(shí)現(xiàn)類誊垢。 */ ServiceLoader<Driver> load = ServiceLoader.load(Driver.class); //這里打印出的內(nèi)容為null掉弛,也就是是Bootstrap類加載器 System.out.println(load.getClass().getClassLoader()); for (Driver next : load) { //打印:oracle.jdbc.OracleDriver@77a567e1 sun.misc.Launcher$AppClassLoader@18b4aac2 //對(duì)應(yīng)的是應(yīng)用類加載器 System.out.println(next + " " + next.getClass().getClassLoader()); } }
最終加載類的的地方public static <S> ServiceLoader<S> load(Class<S> service) { //在這里獲取當(dāng)前線程的上下文類加載器殃饿,如果沒有特別的設(shè)置,這里獲取到的就是應(yīng)用類加載器芋肠, //應(yīng)用類加載器是可以獲取到我們項(xiàng)目lib包下的類乎芳,所以就可以找到我們添加的JDBC的實(shí)現(xiàn)類。 ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }
//loader就是之前我們獲取到的線程上下文類加載器帖池,通過該行代碼就可以使用我們自定的類加載器去加載指定的類奈惑。 Class.forName(cn, false, loader);
- ServiceLoader運(yùn)行規(guī)范
- ServiceLoader會(huì)從指定的目錄META-INF/services/下加載配置文件
- 配置文件的文件名為要加載的服務(wù)類的全限定類名(binary name)
- 配置文件的內(nèi)容為:服務(wù)類對(duì)應(yīng)的實(shí)現(xiàn)類全限定名(binary name)
- 例子:
在Mysql驅(qū)動(dòng)jar的對(duì)應(yīng)的目錄下可以看到文件名為java.sql.Driver
內(nèi)容為com.mysql.jdbc.Driver com.mysql.fabric.jdbc.FabricMySQLDriver