版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)
文檔簡介
第深入解析Java類加載的案例與實戰(zhàn)教程目錄一、Tomcat類加載器架構(gòu)二、動態(tài)代理的原理三、Java語法糖的改變本篇文章主要介紹Tomcat類加載器架構(gòu),以及基于類加載和字節(jié)碼相關(guān)知識,去分析動態(tài)代理的原理。
一、Tomcat類加載器架構(gòu)
Tomcat有自己定義的類加載器,因為一個功能健全的Web服務(wù)器,都要解決如下的這些問題:
部署在同一個服務(wù)器上的兩個Web應(yīng)用程序所使用的Java類庫可以實現(xiàn)相互隔離。這是最基本的需求。兩個不同的應(yīng)用程序可能會依賴同一個第三方類庫的不同版本,不能要求每個類庫在一個服務(wù)器中只能有一份。部署在同一個服務(wù)器上的兩個Web應(yīng)用程序所使用的Java類庫可以互相共享。例如用戶可能有10個使用Spring組織的應(yīng)用程序部署在同一臺服務(wù)器上,如果把10份Spring分別存放在各個應(yīng)用程序的隔離目錄中,將會是很大的資源浪費這主要倒不是浪費磁盤空間的問題,而是指類庫在使用時都要被加載到服務(wù)器內(nèi)存,如果類庫不能共享,虛擬機的方法區(qū)就會很容易出現(xiàn)過度膨脹的風險。服務(wù)器需要盡可能地保證自身的安全不受部署的Web應(yīng)用程序影響。基于安全考慮,服務(wù)器所使用的類庫應(yīng)該與應(yīng)用程序的類庫互相獨立。支持JSP應(yīng)用的Web服務(wù)器,十有八九都需要支持HotSwap功能。我們知道JSP文件最終要被編譯成Java的Class文件才能被虛擬機執(zhí)行,所謂的hotswap,就是使用新的代碼替換掉已經(jīng)加載的這個Class中的內(nèi)容。
由于存在上述問題,在部署Web應(yīng)用時,單獨的一個ClassPath就不能滿足需求了,所以各種Web服務(wù)器都不約而同地提供了好幾個有著不同含義的ClassPath路徑供用戶存放第三方類庫,這些路徑一般會以lib或classes命名。被放置到不同路徑中的類庫,具備不同的訪問范圍和服務(wù)對象,通常每一個目錄都會有一個相應(yīng)的自定義類加載器去加載放置在里面的Java類庫。
在Tomcat目錄結(jié)構(gòu)中,把Java類庫放置在這4組目錄中,每一組都有獨立的含義,分別是:
放置在/common目錄中。類庫可被Tomcat和所有的Web應(yīng)用程序共同使用。放置在/server目錄中。類庫可被Tomcat使用,對所有的Web應(yīng)用程序都不可見。放置在/shared目錄中。類庫可被所有的Web應(yīng)用程序共同使用,但對Tomcat自己不可見。放置在/WebApp/WEB-INF目錄中。類庫僅僅可以被該Web應(yīng)用程序使用,對Tomcat和其他Web應(yīng)用程序都不可見。
為了支持這套目錄結(jié)構(gòu),并對目錄里面的類庫進行加載和隔離,Tomcat自定義了多個類加載器,這些類加載器按照經(jīng)典的雙親委派模型來實現(xiàn),關(guān)系如下圖所示。
灰色背景的3個類加載器是默認提供的類加載器,而JDKCommon類加載器、Catalina類加載器(也稱為Server類加載器)、Shared類加載器和Webapp類加載器則是Tomcat自己定義的類加載器。
它們分別加載/common/、/server/、/shared/*和/WebApp/WEB-INF/*中的Java類庫。
其中WebApp類加載器和JSP類加載器通常還會存在多個實例,每一個Web應(yīng)用程序?qū)?yīng)一個WebApp類加載器,每一個JSP文件對應(yīng)一個JasperLoader類加載器。
由上圖得知:
Common類加載器能加載的類都可以被Catalina類加載器和Shared類加載器使用而Catalina類加載器和Shared類加載器自己能加載的類則與對方相互隔離WebApp類加載器可以使用Shared類加載器加載到的類,但各個WebApp類加載器實例之間相互隔離。JasperLoader的加載范圍僅僅是這個JSP文件所編譯出來的那一個Class文件,它存在的目的就是為了被丟棄:當服務(wù)器檢測到JSP文件被修改時,會替換掉目前的JasperLoader的實例,并通過再建立一個新的JSP類加載器來實現(xiàn)JSP文件的HotSwap功能。
本例中的類加載結(jié)構(gòu)在Tomcat6以前是它默認的類加載器結(jié)構(gòu),在Tomcat6及之后的版本簡化了默認的目錄結(jié)構(gòu),只有指定了tomcat/conf/perties配置文件的server.loader和share.loader項后才會真正建立Catalina類加載器和Shared類加載器的實例,否則會用到這兩個類加載器的地方都會用Common類加載器的實例代替。
Tomcat6之后也順理成章地把/common、/server和/shared這3個目錄默認合并到一起變成1個/lib目錄,這個目錄里的類庫相當于以前/common目錄中類庫的作用。
那么筆者不妨再提一個問題讓各位讀者思考一下:前面曾經(jīng)提到過一個場景,如果有10個Web應(yīng)用程序都是用Spring來進行組織和管理的話,可以把Spring放到Common或Shared目錄下讓這些程序共享。Spring要對用戶程序的類進行管理,自然要能訪問到用戶程序的類,而用戶的程序顯然是放在/WebApp/WEB-INF目錄中的。那么被Common類加載器或Shared類加載器加載的Spring如何訪問并不在其加載范圍內(nèi)的用戶程序呢?
答案:如果按主流的雙親委派機制,顯然無法做到讓父類加載器加載的類去訪問子類加載器加載的類,但使用線程上下文加載器,可以讓父類加載器請求子類加載器去完成類加載的動作。spring加載類所用的Classloader是通過Thread.currentThread().getContextClassLoader()來獲取的,而當線程創(chuàng)建時會默認setContextClassLoader(AppClassLoader),即線程上下文類加載器被設(shè)置為AppClassLoader,spring中始終可以獲取到這個AppClassLoader(在Tomcat里就是WebAppClassLoader)子類加載器來加載的bean,以后任何一個線程都可以通過getContextClassLoader()獲取到WebAppClassLoader來getbean了。
二、動態(tài)代理的原理
字節(jié)碼生成并不是什么高深的技術(shù),因為JDK里面的Javac命令就是字節(jié)碼生成技術(shù)的老祖宗,并且Javac也是一個由Java語言寫成的程序。
在Java世界里面除了Javac和字節(jié)碼類庫外,使用到字節(jié)碼生成的例子比比皆是,如Web服務(wù)器中的JSP編譯器,編譯時織入的AOP框架,還有很常用的動態(tài)代理技術(shù),甚至在使用反射的時候虛擬機都有可能會在運行時生成字節(jié)碼來提高執(zhí)行速度。我們選擇其中相對簡單的動態(tài)代理技術(shù)來講解字節(jié)碼生成技術(shù)是如何影響程序運作的。
什么是動態(tài)代理?
動態(tài)代理中所說的動態(tài),是指實現(xiàn)了可以在原始類和接口還未知的時候,就確定代理類的代理行為,當代理類與原始類脫離直接聯(lián)系后,就可以很靈活地重用于不同的應(yīng)用場景之中。
下面代碼演示了一個最簡單的動態(tài)代理的用法,原始的代碼邏輯是打印一句helloworld,代理類的邏輯是在原始類方法執(zhí)行前打印一句welcome。我們先看一下代碼,然后再分析JDK是如何做到的。
importjava.lang.reflect.InvocationHandler;
importjava.lang.reflect.Method;
importjava.lang.reflect.Proxy;
publicclassDynamicProxyTest{
interfaceIHello{
voidsayHello();
staticclassHelloimplementsIHello{
@Override
publicvoidsayHello(){
System.out.println("helloworld");
staticclassDynamicProxyimplementsInvocationHandler{
ObjectoriginalObj;
Objectbind(ObjectoriginalObj){
this.originalObj=originalObj;
returnProxy.newProxyInstance(originalObj.getClass().getClassLoader(),originalObj.getClass().getInterfaces(),this);
@Override
publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{
System.out.println("welcome");
returnmethod.invoke(originalObj,args);
publicstaticvoidmain(String[]args){
IHellohello=(IHello)newDynamicProxy().bind(newHello());
hello.sayHello();
運行結(jié)果如下:
在上述代碼里,唯一的黑匣子就是Proxy::newProxyInstance()方法,除此之外再沒有任何特殊之處。這個方法返回一個實現(xiàn)了IHello的接口,并且代理了newHello()實例行為的對象。
newProxyInstance一共傳進去三個參數(shù):
loader第一個參數(shù),代表的是被代理類的類加載器interfaces代理類要實現(xiàn)的被代理類接口InvocationHandler代表的是將方法調(diào)用分派給的調(diào)用處理程序
跟蹤這個方法的源碼,可以看到程序進行過驗證、優(yōu)化、緩存、同步、生成字節(jié)碼、顯式類加載等操作,前面的步驟并不是我們關(guān)注的重點,這里只分析它最后調(diào)用sun.misc.ProxyGenerator::generateProxyClass()方法來完成生成字節(jié)碼的動作。
這個方法會在運行時產(chǎn)生一個描述代理類的字節(jié)碼byte[]數(shù)組。如果想看一看這個在運行時產(chǎn)生的代理類中寫了些什么,可以在main()方法中加入下面這句:
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
執(zhí)行完可以用idea在debug狀態(tài)下直接雙擊shift搜索$Proxy即可找到j(luò)ava文件,如下:
import.DynamicProxyTest.IHello;
importjava.lang.reflect.InvocationHandler;
importjava.lang.reflect.Method;
importjava.lang.reflect.Proxy;
importjava.lang.reflect.UndeclaredThrowableException;
finalclass$Proxy0extendsProxyimplementsIHello{
privatestaticMethodm1;
privatestaticMethodm3;
privatestaticMethodm2;
privatestaticMethodm0;
public$Proxy0(InvocationHandlervar1)throws{
super(var1);
//此處由于版面原因,省略equals()、hashCode()、toString()3個方法的代碼
publicfinalvoidsayHello()throws{
try{
super.h.invoke(this,m3,(Object[])null);
}catch(RuntimeException|Errorvar2){
throwvar2;
}catch(Throwablevar3){
thrownewUndeclaredThrowableException(var3);
static{
try{
m1=Class.forName("java.lang.Object").getMethod("equals",Class.forName("java.lang.Object"));
m3=Class.forName(".DynamicProxyTest$IHello").getMethod("sayHello");
m2=Class.forName("java.lang.Object").getMethod("toString");
m0=Class.forName("java.lang.Object").getMethod("hashCode");
}catch(NoSuchMethodExceptionvar2){
thrownewNoSuchMethodError(var2.getMessage());
}catch(ClassNotFoundExceptionvar3){
thrownewNoClassDefFoundError(var3.getMessage());
}
動態(tài)代理的原理:
通過ProxyGenerator::generateProxyClass()生成一個代理類這個代理類的實現(xiàn)代碼也很簡單,它為傳入接口中的每一個方法,以及從java.lang.Object中繼承來的equals()、hashCode()、toString()方法都生成了對應(yīng)的實現(xiàn),并且統(tǒng)一調(diào)用了InvocationHandler對象的invoke()方法來實現(xiàn)這些方法的內(nèi)容。代碼中的super.h就是父類Proxy中保存的InvocationHandler實例變量,而實例變量就是剛剛傳入的newHello()。所以無論調(diào)用動態(tài)代理的哪一個方法,實際上都是在執(zhí)行InvocationHandler::invoke()中的代理邏輯。
這個例子中并沒有講到generateProxyClass()方法具體是如何產(chǎn)生代理類$Proxy0.class的字節(jié)碼的,大致的生成過程其實就是根據(jù)Class文件的格式規(guī)范去拼裝字節(jié)碼,但是在實際開發(fā)中,以字節(jié)為單位直接拼裝出字節(jié)碼的應(yīng)用場合很少見,這種生成方式也只能產(chǎn)生一些高度模板化的代碼。
對于用戶的程序代碼來說,如果有要大量操作字節(jié)碼的需求,還是使用封裝好的字節(jié)碼類庫比較合適。如果讀者對動態(tài)代理的字節(jié)碼拼裝過程確實很感興趣,可以在OpenJDK的java.base\share\classes\java\lang\reflect目錄下找到sun.misc.ProxyGenerator的源碼。
三、Java語法糖的改變
在Java世界里,每一次JDK大版本的發(fā)布,對Java程序編寫習慣改變最大的,肯定是那些對Java語法做出重大改變的版本。
譬如JDK5時加入的自動裝箱、泛型、動態(tài)注解、枚舉、變長參數(shù)、遍歷循環(huán)(foreach循環(huán));譬如JDK8時加入的Lambda表達式、StreamAPI、接口默認方法等。事實上在沒有這些語法特性的年代,Java程序也照樣能寫?,F(xiàn)在問題來了,如何把高版本JDK中編
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
- 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 報廢電梯維保合同范本
- 承包魚塘散養(yǎng)合同范本
- 非人工智能技術(shù)辨析
- 醫(yī)患關(guān)系滿分作文范文
- 蘭陵縣公務(wù)員考試試題及答案
- 肉制品制作技藝培訓課件
- 假腦子考試題及答案
- 聚合車間安全環(huán)保培訓課件
- 廣州地鐵考試題及答案
- 沼氣生產(chǎn)工崗前績效目標考核試卷含答案
- 2026秋招:澳森特鋼集團試題及答案
- 哲學史重要名詞解析大全
- 2026年寧夏黃河農(nóng)村商業(yè)銀行科技人員社會招聘備考題庫及答案詳解(易錯題)
- DB37-T4975-2025分布式光伏直采直控技術(shù)規(guī)范
- 脫硫廢水零排放項目施工方案
- 2026年海南衛(wèi)生健康職業(yè)學院單招綜合素質(zhì)考試題庫參考答案詳解
- 急性心梗合并急性心衰護理
- 肺原位腺癌病理課件講解
- 傳承三線精神、砥礪奮進前行課件
- 消防設(shè)施維保服務(wù)方案投標文件(技術(shù)方案)
- 堵漏施工方案報價
評論
0/150
提交評論