您好,登錄后才能下訂單哦!
這篇文章將為大家詳細(xì)講解有關(guān)JVM虛擬機(jī)7中JNDI、OSGI、Tomcat類加載器如何實(shí)現(xiàn),小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。
JNDI是 Java 命名與文件夾接口(Java Naming and Directory Interface),在J2EE規(guī)范中是重要的規(guī)范之中的一個(gè),不少專家覺得,沒有透徹理解JNDI的意義和作用,就沒有真正掌握J(rèn)2EE特別是EJB的知識。
那么,JNDI究竟起什么作用?//帶著問題看文章是最有效的
要了解JNDI的作用,我們能夠從“假設(shè)不用JNDI我們?nèi)绾巫??用了JNDI后我們又將如何做?”這個(gè)問題來探討。
沒有JNDI的做法:
程序猿開發(fā)時(shí),知道要開發(fā)訪問MySQL數(shù)據(jù)庫的應(yīng)用,于是將一個(gè)對 MySQL JDBC 驅(qū)動程序類的引用進(jìn)行了編碼,并通過使用適當(dāng)?shù)?JDBC URL 連接到數(shù)據(jù)庫。
就像以下代碼這樣:
1. Connection conn=null; 2. try { 3. Class.forName("com.mysql.jdbc.Driver", 4. true, Thread.currentThread().getContextClassLoader()); 5. conn=DriverManager. 6. getConnection("jdbc:mysql://MyDBServer?user=qingfeng&password=mingyue"); 7. ...... 8. conn.close(); 9. } catch(Exception e) { 10. e.printStackTrace(); 11. } finally { 12. if(conn!=null) { 13. try { 14. conn.close(); 15. } catch(SQLException e) {} 16. } 17. }
這是傳統(tǒng)的做法,也是曾經(jīng)非Java程序猿(如Delphi、VB等)常見的做法。
這種做法一般在小規(guī)模的開發(fā)過程中不會產(chǎn)生問題,僅僅要程序猿熟悉Java語言、了解JDBC技術(shù)和MySQL,能夠非??扉_發(fā)出對應(yīng)的應(yīng)用程序。
沒有JNDI的做法存在的問題:
1、數(shù)據(jù)庫server名稱MyDBServer 、username和口令都可能須要改變,由此引發(fā)JDBC URL須要改動; 2、數(shù)據(jù)庫可能改用別的產(chǎn)品,如改用DB2或者Oracle,引發(fā)JDBC驅(qū)動程序包和類名須要改動; 3、隨著實(shí)際使用終端的添加,原配置的連接池參數(shù)可能須要調(diào)整; 4、......
解決的方法:
程序猿應(yīng)該不須要關(guān)心“詳細(xì)的數(shù)據(jù)庫后臺是什么?JDBC驅(qū)動程序是什么?JDBC URL格式是什么?訪問數(shù)據(jù)庫的username和口令是什么?”等等這些問題。
程序猿編寫的程序應(yīng)該沒有對 JDBC驅(qū)動程序的引用,沒有server名稱,沒實(shí)username稱或口令 —— 甚至沒有數(shù)據(jù)庫池或連接管理。
而是把這些問題交給J2EE容器(比方weblogic)來配置和管理,程序猿僅僅須要對這些配置和管理進(jìn)行引用就可以。
由此,就有了JNDI。
//看的出來。是為了一個(gè)最最核心的問題:是為了解耦,是為了開發(fā)出更加可維護(hù)、可擴(kuò)展//的系統(tǒng)
用了JNDI之后的做法:
首先。在在J2EE容器中配置JNDI參數(shù),定義一個(gè)數(shù)據(jù)源。也就是JDBC引用參數(shù),給這個(gè)數(shù)據(jù)源設(shè)置一個(gè)名稱;然后,在程序中,通過數(shù)據(jù)源名稱引用數(shù)據(jù)源從而訪問后臺數(shù)據(jù)庫。
//紅色的字能夠看出。JNDI是由j2ee容器提供的功能
詳細(xì)操作例如以下(以JBoss為例):
1、配置數(shù)據(jù)源
在JBoss 的 D:\jboss420GA\docs\examples\jca 文件夾以下。有非常多不同數(shù)據(jù)庫引用的數(shù)據(jù)源定義模板。
將當(dāng)中的 mysql-ds.xml 文件Copy到你使用的server下,如 D:\jboss420GA\server\default\deploy。
改動 mysql-ds.xml 文件的內(nèi)容,使之能通過JDBC正確訪問你的MySQL數(shù)據(jù)庫。例如以下:
1. <? xml version="1.0" encoding="UTF-8"?> 2. <datasources> 3. <local-tx-datasource> 4. <jndi-name>MySqlDS</jndi-name> 5. <connection-url>jdbc:mysql://localhost:3306/lw</connection-url> 6. <driver-class>com.mysql.jdbc.Driver</driver-class> 7. <user-name>root</user-name> 8. <password>rootpassword</password> 9. <exception-sorter-class-name> 10. org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter 11. </exception-sorter-class-name> 12. <metadata> 13. <type-mapping>mySQL</type-mapping> 14. </metadata> 15. </local-tx-datasource> 16. </datasources>
這里,定義了一個(gè)名為MySqlDS的數(shù)據(jù)源。其參數(shù)包含JDBC的URL。驅(qū)動類名,username及密碼等。
2、在程序中引用數(shù)據(jù)源:
1. Connection conn=null; 2. try { 3. Context ctx=new InitialContext(); 4. Object datasourceRef=ctx.lookup("java:MySqlDS"); //引用數(shù)據(jù)源 5. DataSource ds=(Datasource)datasourceRef; 6. conn=ds.getConnection(); 7. ...... 8. c.close(); 9. } catch(Exception e) { 10. e.printStackTrace(); 11. } finally { 12. if(conn!=null) { 13. try { 14. conn.close(); 15. } catch(SQLException e) { } 16. } 17. }
直接使用JDBC或者通過JNDI引用數(shù)據(jù)源的編程代碼量相差無幾,可是如今的程序能夠不用關(guān)心詳細(xì)JDBC參數(shù)了。
//解藕了??蓴U(kuò)展了
在系統(tǒng)部署后。假設(shè)數(shù)據(jù)庫的相關(guān)參數(shù)變更。僅僅須要又一次配置 mysql-ds.xml 改動當(dāng)中的JDBC參數(shù),僅僅要保證數(shù)據(jù)源的名稱不變,那么程序源碼就無需改動。
由此可見。JNDI避免了程序與數(shù)據(jù)庫之間的緊耦合,使應(yīng)用更加易于配置、易于部署。
JNDI的擴(kuò)展:
JNDI在滿足了數(shù)據(jù)源配置的要求的基礎(chǔ)上。還進(jìn)一步擴(kuò)充了作用:全部與系統(tǒng)外部的資源的引用,都能夠通過JNDI定義和引用。
//注意什么叫資源
所以,在J2EE規(guī)范中,J2EE 中的資源并不局限于 JDBC 數(shù)據(jù)源。
引用的類型有非常多,當(dāng)中包含資源引用(已經(jīng)討論過)、環(huán)境實(shí)體和 EJB 引用。
特別是 EJB 引用,它暴露了 JNDI 在 J2EE 中的另外一項(xiàng)關(guān)鍵角色:查找其它應(yīng)用程序組件。
EJB 的 JNDI 引用非常相似于 JDBC 資源的引用。在服務(wù)趨于轉(zhuǎn)換的環(huán)境中,這是一種非常有效的方法。能夠?qū)?yīng)用程序架構(gòu)中所得到的全部組件進(jìn)行這類配置管理,從 EJB 組件到 JMS 隊(duì)列和主題。再到簡單配置字符串或其它對象。這能夠降低隨時(shí)間的推移服務(wù)變更所產(chǎn)生的維護(hù)成本,同一時(shí)候還能夠簡化部署,降低集成工作。外部資源”。
總結(jié):
J2EE 規(guī)范要求全部 J2EE 容器都要提供 JNDI 規(guī)范的實(shí)現(xiàn)。//sun 果然喜歡制定規(guī)范JNDI 在 J2EE 中的角色就是“交換機(jī)” —— J2EE 組件在執(zhí)行時(shí)間接地查找其它組件、資源或服務(wù)的通用機(jī)制。在多數(shù)情況下,提供 JNDI 供應(yīng)者的容器能夠充當(dāng)有限的數(shù)據(jù)存儲。這樣管理員就能夠設(shè)置應(yīng)用程序的執(zhí)行屬性,并讓其它應(yīng)用程序引用這些屬性(Java 管理擴(kuò)展(Java Management Extensions,JMX)也能夠用作這個(gè)目的)。JNDI 在 J2EE 應(yīng)用程序中的主要角色就是提供間接層,這樣組件就能夠發(fā)現(xiàn)所須要的資源,而不用了解這些間接性。
在 J2EE 中,JNDI 是把 J2EE 應(yīng)用程序合在一起的粘合劑。JNDI 提供的間接尋址同意跨企業(yè)交付可伸縮的、功能強(qiáng)大且非常靈活的應(yīng)用程序。
這是 J2EE 的承諾,并且經(jīng)過一些計(jì)劃和預(yù)先考慮。這個(gè)承諾是全然能夠?qū)崿F(xiàn)的。
從上面的文章中能夠看出:
1、JNDI 提出的目的是為了解藕,是為了開發(fā)更加easy維護(hù),easy擴(kuò)展。easy部署的應(yīng)用。
2、JNDI 是一個(gè)sun提出的一個(gè)規(guī)范(相似于jdbc),詳細(xì)的實(shí)現(xiàn)是各個(gè)j2ee容器提供商。sun 僅僅是要求,j2ee容器必須有JNDI這種功能。
3、JNDI 在j2ee系統(tǒng)中的角色是“交換機(jī)”,是J2EE組件在執(zhí)行時(shí)間接地查找其它組件、資源或服務(wù)的通用機(jī)制。
4、JNDI 是通過資源的名字來查找的,資源的名字在整個(gè)j2ee應(yīng)用中(j2ee容器中)是唯一的。
上文提到過雙親委派模型并不是一個(gè)強(qiáng)制性的約束模型,而是 Java設(shè)計(jì)者推薦給開發(fā)者的類加載器實(shí)現(xiàn)方式。在Java 的世界中大部分的類加載器都遵循這個(gè)模型,但也有例外。
雙親委派模型的一次“被破壞”是由這個(gè)模型自身的缺陷所導(dǎo)致的,雙親委派很好地解決了各個(gè)類加載器的基礎(chǔ)類的統(tǒng)一問題(越基礎(chǔ)的類由越上層的加載器進(jìn)行加載),基礎(chǔ)類之所以稱為“基礎(chǔ)”,是因?yàn)樗鼈兛偸亲鳛楸挥脩舸a調(diào)用的API ,但世事往往沒有絕對的完美,如果基礎(chǔ)類又要調(diào)用回用戶的代碼,那該怎么辦?
這并非是不可能的事情,一個(gè)典型的例子便是JNDI 服務(wù),JNDI現(xiàn)在已經(jīng)是Java的標(biāo)準(zhǔn)服務(wù),它的代碼由啟動類加載器去加載(在 JDK 1.3時(shí)放進(jìn)去的rt.jar),但JNDI 的目的就是對資源進(jìn)行集中管理和查找,它需要調(diào)用由獨(dú)立廠商實(shí)現(xiàn)并部署在應(yīng)用程序的Class Path下的JNDI 接口提供者(SPI,Service Provider Interface)的代碼,但啟動類加載器不可能“認(rèn)識” 這些代碼 ,因?yàn)閱宇惣虞d器的搜索范圍中找不到用戶應(yīng)用程序類,那該怎么辦?
為了解決這個(gè)問題,Java設(shè)計(jì)團(tuán)隊(duì)只好引入了一個(gè)不太優(yōu)雅的設(shè)計(jì):線程上下文類加載器(Thread Context ClassLoader)。這個(gè)類加載器可以通過java.lang.Thread類的setContextClassLoader()方法進(jìn)行設(shè)置,如果創(chuàng)建線程時(shí)還未設(shè)置,它將會從父線程中繼承一個(gè),如果在應(yīng)用程序的全局范圍內(nèi)都沒有設(shè)置過的話,那這個(gè)類加載器默認(rèn)就是應(yīng)用程序類加載器(Application ClassLoader)。
有了線程上下文類加載器,就可以做一些“舞弊”的事情了,JNDI服務(wù)使用這個(gè)線程上下文類加載器去加載所需要的 SPI代碼,也就是父類加載器請求子類加載器去完成類加載的動作,這種行為實(shí)際上就是打通了雙親委派模型的層次結(jié)構(gòu)來逆向使用類加載器 ,實(shí)際上已經(jīng)違背了雙親委派模型的一般性原則,但這也是無可奈何的事情。Java中所有涉及SPI的加載動作基本上都采用這種方式,例如JNDI 、JDBC、JCE、 JAXB 和JBI等。
目前,業(yè)內(nèi)關(guān)于OSGI技術(shù)的學(xué)習(xí)資源或者技術(shù)文檔還是很少的。我在某寶網(wǎng)搜索了一下“OSGI”的書籍,結(jié)果倒是有,但是種類少的可憐,而且?guī)缀鯖]有人購買。
因?yàn)楣ぷ鞯脑蛭倚枰獙W(xué)習(xí)OSGI,所以我不得不想盡辦法來主動學(xué)習(xí)OSGI。我將用文字記錄學(xué)習(xí)OSGI的整個(gè)過程,通過整理書籍和視頻教程,來讓我更加了解這門技術(shù),同時(shí)也讓需要學(xué)習(xí)這門技術(shù)的同志們有一個(gè)清晰的學(xué)習(xí)路線。
我們需要解決一下幾問題:
我們從外文資料上或者從翻譯過來的資料上看到OSGi解釋和定義,都是直譯過來的,但是OSGI的真實(shí)意義未必是中文直譯過來的意思。OSGI的解釋就是Open Service Gateway Initiative,直譯過來就是“開放的服務(wù)入口(網(wǎng)關(guān))的初始化”,聽起來非常費(fèi)解,什么是服務(wù)入口初始化?
所以我們不去直譯這個(gè)OSGI,我們換一種說法來描述OSGI技術(shù)。
我們來回到我們以前的某些開發(fā)場景中去,假設(shè)我們使用SSH(struts+spring+hibernate)框架來開發(fā)我們的Web項(xiàng)目,我們做產(chǎn)品設(shè)計(jì)和開發(fā)的時(shí)候都是分模塊的,我們分模塊的目的就是實(shí)現(xiàn)模塊之間的“解耦”,更進(jìn)一步的目的是方便對一個(gè)項(xiàng)目的控制和管理。
我們對一個(gè)項(xiàng)目進(jìn)行模塊化分解之后,我們就可以把不同模塊交給不同的開發(fā)人員來完成開發(fā),然后項(xiàng)目經(jīng)理把大家完成的模塊集中在一起,然后拼裝成一個(gè)最終的產(chǎn)品。一般我們開發(fā)都是這樣的基本情況。
那么我們開發(fā)的時(shí)候預(yù)計(jì)的是系統(tǒng)的功能,根據(jù)系統(tǒng)的功能來進(jìn)行模塊的劃分,也就是說,這個(gè)產(chǎn)品的功能或客戶的需求是劃分的重要依據(jù)。
但是我們在開發(fā)過程中,我們模塊之間還要彼此保持聯(lián)系,比如A模塊要從B模塊拿到一些數(shù)據(jù),而B模塊可能要調(diào)用C模塊中的一些方法(除了公共底層的工具類之外)。所以這些模塊只是一種邏輯意義上的劃分。
最重要的一點(diǎn)是,我們把最終的項(xiàng)目要去部署到tomcat或者jBoss的服務(wù)器中去部署。那么我們啟動服務(wù)器的時(shí)候,能不能關(guān)閉項(xiàng)目的某個(gè)模塊或功能呢?很明顯是做不到的,一旦服務(wù)器啟動,所有模塊就要一起啟動,都要占用服務(wù)器資源,所以關(guān)閉不了模塊,假設(shè)能強(qiáng)制拿掉,就會影響其它的功能。
以上就是我們傳統(tǒng)模塊式開發(fā)的一些局限性。
我們做軟件開發(fā)一直在追求一個(gè)境界,就是模塊之間的真正“解耦”、“分離”,這樣我們在軟件的管理和開發(fā)上面就會更加的靈活,甚至包括給客戶部署項(xiàng)目的時(shí)候都可以做到更加的靈活可控。但是我們以前使用SSH框架等架構(gòu)模式進(jìn)行產(chǎn)品開發(fā)的時(shí)候我們是達(dá)不到這種要求的。
所以我們“架構(gòu)師”或頂尖的技術(shù)高手都在為模塊化開發(fā)努力的摸索和嘗試,然后我們的OSGI的技術(shù)規(guī)范就應(yīng)運(yùn)而生。
現(xiàn)在我們的OSGI技術(shù)就可以滿足我們之前所說的境界:在不同的模塊中做到徹底的分離,而不是邏輯意義上的分離,是物理上的分離,也就是說在運(yùn)行部署之后都可以在不停止服務(wù)器的時(shí)候直接把某些模塊拿下來,其他模塊的功能也不受影響。
由此,OSGI技術(shù)將來會變得非常的重要,因?yàn)樗趯?shí)現(xiàn)模塊化解耦的路上,走得比現(xiàn)在大家經(jīng)常所用的SSH框架走的更遠(yuǎn)。這個(gè)技術(shù)在未來大規(guī)模、高訪問、高并發(fā)的Java模塊化開發(fā)領(lǐng)域,或者是項(xiàng)目規(guī)范化管理中,會大大超過SSH等框架的地位。
現(xiàn)在主流的一些應(yīng)用服務(wù)器,Oracle的weblogic服務(wù)器,IBM的WebSphere,JBoss,還有Sun公司的glassfish服務(wù)器,都對OSGI提供了強(qiáng)大的支持,都是在OSGI的技術(shù)基礎(chǔ)上實(shí)現(xiàn)的。有那么多的大型廠商支持OSGI這門技術(shù),我們既可以看到OSGI技術(shù)的重要性。所以將來OSGI是將來非常重要的技術(shù)。
但是OSGI仍然脫離不了框架的支持,因?yàn)镺SGI本身也使用了很多spring等框架的基本控件(因?yàn)橐獙?shí)現(xiàn)AOP依賴注入等功能),但是哪個(gè)項(xiàng)目又不去依賴第三方j(luò)ar呢?
雙親委派模型的另一次“被破壞”是由于用戶對程序動態(tài)性的追求而導(dǎo)致的,這里所說的“ 動態(tài)性”指的是當(dāng)前一些非?!盁衢T”的名詞:代碼熱替換(HotSwap)、模塊熱部署(HotDeployment)等 ,說白了就是希望應(yīng)用程序能像我們的計(jì)算機(jī)外設(shè)那樣,接上鼠標(biāo)、U盤,不用重啟機(jī)器就能立即使用,鼠標(biāo)有問題或要升級就換個(gè)鼠標(biāo),不用停機(jī)也不用重啟。
對于個(gè)人計(jì)算機(jī)來說,重啟一次其實(shí)沒有什么大不了的,但對于一些生產(chǎn)系統(tǒng)來說,關(guān)機(jī)重啟一次可能就要被列為生產(chǎn)事故,這種情況下熱部署就對軟件開發(fā)者,尤其是企業(yè)級軟件開發(fā)者具有很大的吸引力。Sun 公司所提出的JSR-294、JSR-277規(guī)范在與 JCP組織的模塊化規(guī)范之爭中落敗給JSR-291(即 OSGi R4.2),雖然Sun不甘失去Java 模塊化的主導(dǎo)權(quán),獨(dú)立在發(fā)展 Jigsaw項(xiàng)目,但目前OSGi已經(jīng)成為了業(yè)界“ 事實(shí)上” 的Java模塊化標(biāo)準(zhǔn),而OSGi實(shí)現(xiàn)模塊化熱部署的關(guān)鍵則是它自定義的類加載器機(jī)制的實(shí)現(xiàn)。
每一個(gè)程序模塊( OSGi 中稱為Bundle)都有一個(gè)自己的類加載器,當(dāng)需要更換一個(gè)Bundle 時(shí),就把Bundle連同類加載器一起換掉以實(shí)現(xiàn)代碼的熱替換。
在OSGi環(huán)境下,類加載器不再是雙親委派模型中的樹狀結(jié)構(gòu),而是進(jìn)一步發(fā)展為更加復(fù)雜的網(wǎng)狀結(jié)構(gòu),當(dāng)收到類加載請求時(shí),OSGi 將按照下面的順序進(jìn)行類搜索:
1)將以java.*開頭的類委派給父類加載器加載。 2)否則,將委派列表名單內(nèi)的類委派給父類加載器加載。 3)否則,將Import列表中的類委派給 Export這個(gè)類的Bundle的類加載器加載。 4)否則,查找當(dāng)前Bundle的 Class Path,使用自己的類加載器加載。 5)否則,查找類是否在自己的Fragment Bundle中,如果在,則委派給 Fragment Bundle的類加載器加載。 6)否則,查找Dynamic Import列表的 Bundle,委派給對應(yīng)Bundle的類加載器加載。 7)否則,類查找失敗。
上面的查找順序中只有開頭兩點(diǎn)仍然符合雙親委派規(guī)則,其余的類查找都是在平級的類加載器中進(jìn)行的。
只要有足夠意義和理由,突破已有的原則就可認(rèn)為是一種創(chuàng)新。正如OSGi中的類加載器并不符合傳統(tǒng)的雙親委派的類加載器,并且業(yè)界對其為了實(shí)現(xiàn)熱部署而帶來的額外的高復(fù)雜度還存在不少爭議,但在Java 程序員中基本有一個(gè)共識:OSGi中對類加載器的使用是很值得學(xué)習(xí)的,弄懂了OSGi的實(shí)現(xiàn),就可以算是掌握了類加載器的精髓。
Tomcat的用戶一定都使用過其應(yīng)用部署功能,無論是直接拷貝文件到webapps目錄,還是修改server.xml以目錄的形式部署,或者是增加虛擬主機(jī),指定新的appBase等等。
但部署應(yīng)用時(shí),不知道你是否曾注意過這幾點(diǎn):
如果在一個(gè)Tomcat內(nèi)部署多個(gè)應(yīng)用,甚至多個(gè)應(yīng)用內(nèi)使用了某個(gè)類似的幾個(gè)不同版本,但它們之間卻互不影響。這是如何做到的。
如果多個(gè)應(yīng)用都用到了某類似的相同版本,是否可以統(tǒng)一提供,不在各個(gè)應(yīng)用內(nèi)分別提供,占用內(nèi)存呢。
還有時(shí)候,在開發(fā)Web應(yīng)用時(shí),在pom.xml中添加了servlet-api的依賴,那實(shí)際應(yīng)用的class加載時(shí),會加載你的servlet-api 這個(gè)jar嗎
以上提到的這幾點(diǎn),在Tomcat以及各類的應(yīng)用服務(wù)器中,都是通過類加載器(ClasssLoader)來實(shí)現(xiàn)的。通過本文,你可以了解到Tomcat內(nèi)部提供的各種類加載器,Web應(yīng)用的class和資源等加載的方式,以及其內(nèi)部的實(shí)現(xiàn)原理。在遇到類似問題時(shí),更胸有成竹。
Java語言本身,以及現(xiàn)在其它的一些基于JVM之上的語言(Groovy,Jython, Scala…),都是在將代碼編譯生成class文件,以實(shí)現(xiàn)跨多平臺,write once, run anywhere。最終的這些class文件,在應(yīng)用中,又被加載到JVM虛擬機(jī)中,開始工作。而把class文件加載到JVM的組件,就是我們所說的類加載器。而對于類加載器的抽象,能面對更多的class數(shù)據(jù)提供形式,例如網(wǎng)絡(luò)、文件系統(tǒng)等。
Java中常見的那個(gè)ClassNotFoundException和NoClassDefFoundError就是類加載器告訴我們的。
Servlet規(guī)范指出,容器用于加載Web應(yīng)用內(nèi)Servlet的class loader, 允許加載位于Web應(yīng)用內(nèi)的資源。但不允許重寫java., javax.以及容器實(shí)現(xiàn)的類。同時(shí)
每個(gè)應(yīng)用內(nèi)使用Thread.currentThread.getContextClassLoader()獲得的類加載器,都是該應(yīng)用區(qū)別于其它應(yīng)用的類加載器等等。
根據(jù)Servlet規(guī)范,各個(gè)應(yīng)用服務(wù)器廠商自行實(shí)現(xiàn)。所以像其他的一些應(yīng)用服務(wù)器一樣, Tomcat也提供了多種的類加載器,以便應(yīng)用服務(wù)器內(nèi)的class以及部署的Web應(yīng)用類文件運(yùn)行在容器中時(shí),可以使用不同的class repositories。
在Java中,類加載器是以一種父子關(guān)系樹來組織的。除Bootstrap外,都會包含一個(gè)parent 類加載器。(這里寫parent 類加載器,而不是父類加載器,不是為了裝X,是為了避免和Java里的父類混淆) 一般以類加載器需要加載一個(gè)class或者資源文件的時(shí)候,他會先委托給他的parent類加載器,讓parent類加載器先來加載,如果沒有,才再在自己的路徑上加載。這就是人們常說的雙親委托,即把類加載的請求委托給parent。
但是…,這里需要注意一下
對于Web應(yīng)用的類加載,和上面的雙親委托是有區(qū)別的。
主流的Java Web服務(wù)器(也就是Web容器) ,如Tomcat、Jetty、WebLogic、WebSphere 或其他筆者沒有列舉的服務(wù)器,都實(shí)現(xiàn)了自己定義的類加載器(一般都不止一個(gè))。因?yàn)橐粋€(gè)功能健全的 Web容器,要解決如下幾個(gè)問題:
1)部署在同一個(gè)Web容器上 的兩個(gè)Web應(yīng)用程序所使用的Java類庫可以實(shí)現(xiàn)相互隔離。這是最基本的需求,兩個(gè)不同的應(yīng)用程序可能會依賴同一個(gè)第三方類庫的不同版本,不能要求一個(gè)類庫在一個(gè)服務(wù)器中只有一份,服務(wù)器應(yīng)當(dāng)保證兩個(gè)應(yīng)用程序的類庫可以互相獨(dú)立使用。
2)部署在同一個(gè)Web容器上 的兩個(gè)Web應(yīng)用程序所使用的Java類庫可以互相共享 。這個(gè)需求也很常見,例如,用戶可能有10個(gè)使用 spring 組織的應(yīng)用程序部署在同一臺服務(wù)器上,如果把10份Spring分別存放在各個(gè)應(yīng)用程序的隔離目錄中,將會是很大的資源浪費(fèi)——這主要倒不是浪費(fèi)磁盤空間的問題,而是指類庫在使用時(shí)都要被加載到Web容器的內(nèi)存,如果類庫不能共享,虛擬機(jī)的方法區(qū)就會很容易出現(xiàn)過度膨脹的風(fēng)險(xiǎn)。
3)Web容器需要盡可能地保證自身的安全不受部署的Web應(yīng)用程序影響。目前,有許多主流的Java Web容器自身也是使用Java語言來實(shí)現(xiàn)的。因此,Web容器本身也有類庫依賴的問題,一般來說,基于安全考慮,容器所使用的類庫應(yīng)該與應(yīng)用程序的類庫互相獨(dú)立。
4)支持JSP應(yīng)用的Web容器,大多數(shù)都需要支持 HotSwap功能。我們知道,JSP文件最終要編譯成Java Class才能由虛擬機(jī)執(zhí)行,但JSP文件由于其純文本存儲的特性,運(yùn)行時(shí)修改的概率遠(yuǎn)遠(yuǎn)大于第三方類庫或程序自身的Class文件 。而且ASP、 PHP 和JSP這些網(wǎng)頁應(yīng)用也把修改后無須重啟作為一個(gè)很大的“優(yōu)勢”來看待 ,因此“主流”的Web容器都會支持JSP生成類的熱替換 ,當(dāng)然也有“非主流”的,如運(yùn)行在生產(chǎn)模式(Production Mode)下的WebLogic服務(wù)器默認(rèn)就不會處理JSP文件的變化。
由于存在上述問題,在部署Web應(yīng)用時(shí),單獨(dú)的一個(gè)Class Path就無法滿足需求了,所以各種 Web容都“不約而同”地提供了好幾個(gè)Class Path路徑供用戶存放第三方類庫,這些路徑一般都以“l(fā)ib”或“classes ”命名。被放置到不同路徑中的類庫,具備不同的訪問范圍和服務(wù)對象,通常,每一個(gè)目錄都會有一個(gè)相應(yīng)的自定義類加載器去加載放置在里面的Java類庫 。現(xiàn)在,就以Tomcat 容器為例,看一看Tomcat具體是如何規(guī)劃用戶類庫結(jié)構(gòu)和類加載器的。
在Tomcat目錄結(jié)構(gòu)中,有3組目錄(“/common/”、“/server/”和“/shared/”)可以存放Java類庫,另外還可以加上Web 應(yīng)用程序自身的目錄“/WEB-INF/” ,一共4組,把Java類庫放置在這些目錄中的含義分別如下:
①放置在/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),并對目錄里面的類庫進(jìn)行加載和隔離,Tomcat自定義了多個(gè)類加載器,這些類加載器按照經(jīng)典的雙親委派模型來實(shí)現(xiàn),其關(guān)系如下圖所示。
cdn.xitu.io/2017/5/8/0dddae151e8fe1eba5db1a35d2b7b9b2?imageView2/0/w/1280/h/960/format/webp/ignore-error/1">
上圖中灰色背景的3個(gè)類加載器是JDK默認(rèn)提供的類加載器,這3個(gè)加載器的作用已經(jīng)介紹過了。而CommonClassLoader、CatalinaClassLoader、SharedClassLoader和WebappClassLoader則是Tomcat自己定義的類加載器,它們分別加載/common/、/server/、/shared/和/WebApp/WEB-INF/中的Java類庫。其中WebApp類加載器和Jsp類加載器通常會存在多個(gè)實(shí)例,每一個(gè)Web應(yīng)用程序?qū)?yīng)一個(gè)WebApp類加載器,每一個(gè)JSP文件對應(yīng)一個(gè)Jsp類加載器。
從圖中的委派關(guān)系中可以看出,CommonClassLoader能加載的類都可以被Catalina ClassLoader和SharedClassLoader使用,而CatalinaClassLoader和Shared ClassLoader自己能加載的類則與對方相互隔離。WebAppClassLoader可以使用SharedClassLoader加載到的類,但各個(gè)WebAppClassLoader實(shí)例之間相互隔離。
而JasperLoader的加載范圍僅僅是這個(gè)JSP文件所編譯出來的那一個(gè).Class文件,它出現(xiàn)的目的就是為了被丟棄:當(dāng)Web容器檢測到JSP文件被修改時(shí),會替換掉目前的JasperLoader的實(shí)例,并通過再建立一個(gè)新的Jsp類加載器來實(shí)現(xiàn)JSP文件的HotSwap功能。
對于Tomcat的6.x版本,只有指定了tomcat/conf/catalina.properties配置文件的server.loader和share.loader項(xiàng)后才會真正建立Catalina ClassLoader和Shared ClassLoader的實(shí)例,否則在用到這兩個(gè)類加載器的地方都會用Common ClassLoader的實(shí)例代替,而默認(rèn)的配置文件中沒有設(shè)置這兩個(gè)loader項(xiàng),所以Tomcat 6.x順理成章地把/common、/server和/shared三個(gè)目錄默認(rèn)合并到一起變成一個(gè)/lib目錄,這個(gè)目錄里的類庫相當(dāng)于以前/common目錄中類庫的作用。
這是Tomcat設(shè)計(jì)團(tuán)隊(duì)為了簡化大多數(shù)的部署場景所做的一項(xiàng)改進(jìn),如果默認(rèn)設(shè)置不能滿足需要,用戶可以通過修改配置文件指定server.loader和share.loader的方式重新啟用Tomcat 5.x的加載器 架構(gòu)。
Tomcat加載器的實(shí)現(xiàn)清晰易懂,并且采用了官方推薦的“正統(tǒng)”的使用類加載器的方式。如果讀者閱讀完上面的案例后,能完全理解Tomcat設(shè)計(jì)團(tuán)隊(duì)這樣布置加載器架構(gòu)的用意,那說明已經(jīng)大致掌握了類加載器“主流”的使用方式,那么筆者不妨再提一個(gè)問題讓讀者思考一下:前面曾經(jīng)提到過一個(gè)場景,如果有10個(gè)Web應(yīng)用程序都是用Spring來進(jìn)行組織和管理的話,可以把Spring放到Common或Shared目錄下讓這些程序共享。 Spring要對用戶程序的類進(jìn)行管理,自然要能訪問到用戶程序的類,而用戶的程序顯然是放在/WebApp/WEB-INF目錄中的,那么被CommonClassLoader或SharedClassLoader加載的Spring如何訪問并不在其加載范圍內(nèi)的用戶程序呢?如果研究過虛擬機(jī)類加載器機(jī)制中的雙親委派模型,相信讀者可以很容易地回答這個(gè)問題。
分析:如果按主流的雙親委派機(jī)制,顯然無法做到讓父類加載器加載的類 去訪問子類加載器加載的類,上面在類加載器一節(jié)中提到過通過線程上下文方式傳播類加載器。
答案是使用線程上下文類加載器來實(shí)現(xiàn)的,使用線程上下文加載器,可以讓父類加載器請求子類加載器去完成類加載的動作。
看spring源碼發(fā)現(xiàn),spring加載類所用的Classloader是通過Thread.currentThread().getContextClassLoader()來獲取的,而當(dāng)線程創(chuàng)建時(shí)會默認(rèn)setContextClassLoader(AppClassLoader),即線程上下文類加載器被設(shè)置為 AppClassLoader,spring中始終可以獲取到這個(gè)AppClassLoader( 在 Tomcat里就是WebAppClassLoader)子類加載器來加載bean ,以后任何一個(gè)線程都可以通過 getContextClassLoader()獲取到WebAppClassLoader來getbean 了 。
關(guān)于“JVM虛擬機(jī)7中JNDI、OSGI、Tomcat類加載器如何實(shí)現(xiàn)”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,使各位可以學(xué)到更多知識,如果覺得文章不錯(cuò),請把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。