深入理解JVM虛擬機7:JNDI,OSGI,Tomcat類加載器實現

打破雙親委派模型


JNDI


JNDI 的理解

JNDI是 Java 命名與文件夾接口(Java Naming and Directory Interface),在J2EE規範中是重要的規範之中的一個,不少專家覺得,沒有透徹理解JNDI的意義和作用,就沒有真正掌握J2EE特別是EJB的知識。 

那麼,JNDI究竟起什麼作用?//帶着問題看文章是最有效的 

要了解JNDI的作用,我們能夠從“假設不用JNDI我們如何做?用了JNDI後我們又將如何做?”這個問題來探討。 

沒有JNDI的做法: 

程序猿開發時,知道要開發訪問MySQL數據庫的應用,於是將一個對 MySQL JDBC 驅動程序類的引用進行了編碼,並通過使用適當的 JDBC URL 連接到數據庫。 
就像以下代碼這樣: 

Java代碼  收藏代碼

  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. }  




這是傳統的做法,也是曾經非Java程序猿(如Delphi、VB等)常見的做法。


這種做法一般在小規模的開發過程中不會產生問題,僅僅要程序猿熟悉Java語言、瞭解JDBC技術和MySQL,能夠非常快開發出對應的應用程序。 

沒有JNDI的做法存在的問題: 
1、數據庫server名稱MyDBServer 、username和口令都可能須要改變,由此引發JDBC URL須要改動; 
2、數據庫可能改用別的產品,如改用DB2或者Oracle,引發JDBC驅動程序包和類名須要改動; 
3、隨着實際使用終端的添加,原配置的連接池參數可能須要調整; 
4、...... 

解決的方法: 
程序猿應該不須要關心“詳細的數據庫後臺是什麼?JDBC驅動程序是什麼?JDBC URL格式是什麼?訪問數據庫的username和口令是什麼?”等等這些問題。程序猿編寫的程序應該沒有對 JDBC 驅動程序的引用,沒有server名稱,沒實username稱或口令 —— 甚至沒有數據庫池或連接管理。

而是把這些問題交給J2EE容器(比方weblogic)來配置和管理,程序猿僅僅須要對這些配置和管理進行引用就可以。 

由此,就有了JNDI。

 
//看的出來。是爲了一個最最核心的問題:是爲了解耦,是爲了開發出更加可維護、可擴展//的系統 

用了JNDI之後的做法: 
首先。在在J2EE容器中配置JNDI參數,定義一個數據源。也就是JDBC引用參數,給這個數據源設置一個名稱;然後,在程序中,通過數據源名稱引用數據源從而訪問後臺數據庫。 

//紅色的字能夠看出。JNDI是由j2ee容器提供的功能 

詳細操作例如以下(以JBoss爲例): 
1、配置數據源 
在JBoss 的 D:\jboss420GA\docs\examples\jca 文件夾以下。有非常多不同數據庫引用的數據源定義模板。

將當中的 mysql-ds.xml 文件Copy到你使用的server下,如 D:\jboss420GA\server\default\deploy。 
改動 mysql-ds.xml 文件的內容,使之能通過JDBC正確訪問你的MySQL數據庫。例如以下: 

Java代碼  收藏代碼

  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>  



這裏,定義了一個名爲MySqlDS的數據源。其參數包含JDBC的URL。驅動類名,username及密碼等。 

2、在程序中引用數據源: 

Java代碼  收藏代碼

  1. Connection conn=null;  

  2. try {  

  3.   Context ctx=new InitialContext();  

  4.   Object datasourceRef=ctx.lookup("java:MySqlDS"); //引用數據源  

  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引用數據源的編程代碼量相差無幾,可是如今的程序能夠不用關心詳細JDBC參數了。


//解藕了。可擴展了 
在系統部署後。假設數據庫的相關參數變更。僅僅須要又一次配置 mysql-ds.xml 改動當中的JDBC參數,僅僅要保證數據源的名稱不變,那麼程序源碼就無需改動。

 

由此可見。JNDI避免了程序與數據庫之間的緊耦合,使應用更加易於配置、易於部署

 

JNDI的擴展: 
JNDI在滿足了數據源配置的要求的基礎上。還進一步擴充了作用:全部與系統外部的資源的引用,都能夠通過JNDI定義和引用。

 
//注意什麼叫資源 

所以,在J2EE規範中,J2EE 中的資源並不侷限於 JDBC 數據源。

引用的類型有非常多,當中包含資源引用(已經討論過)、環境實體和 EJB 引用。

特別是 EJB 引用,它暴露了 JNDI 在 J2EE 中的另外一項關鍵角色:查找其它應用程序組件。 

EJB 的 JNDI 引用非常相似於 JDBC 資源的引用。在服務趨於轉換的環境中,這是一種非常有效的方法。能夠對應用程序架構中所得到的全部組件進行這類配置管理,從 EJB 組件到 JMS 隊列和主題。再到簡單配置字符串或其它對象。這能夠降低隨時間的推移服務變更所產生的維護成本,同一時候還能夠簡化部署,降低集成工作。外部資源”。

 


總結: 
J2EE 規範要求全部 J2EE 容器都要提供 JNDI 規範的實現。//sun 果然喜歡制定規範JNDI 在 J2EE 中的角色就是“交換機” —— J2EE 組件在執行時間接地查找其它組件、資源或服務的通用機制。在多數情況下,提供 JNDI 供應者的容器能夠充當有限的數據存儲。這樣管理員就能夠設置應用程序的執行屬性,並讓其它應用程序引用這些屬性(Java 管理擴展(Java Management Extensions,JMX)也能夠用作這個目的)。JNDI 在 J2EE 應用程序中的主要角色就是提供間接層,這樣組件就能夠發現所須要的資源,而不用瞭解這些間接性。 

在 J2EE 中,JNDI 是把 J2EE 應用程序合在一起的粘合劑。JNDI 提供的間接尋址同意跨企業交付可伸縮的、功能強大且非常靈活的應用程序。

這是 J2EE 的承諾,並且經過一些計劃和預先考慮。這個承諾是全然能夠實現的。 


從上面的文章中能夠看出: 
1、JNDI 提出的目的是爲了解藕,是爲了開發更加easy維護,easy擴展。easy部署的應用。 
2、JNDI 是一個sun提出的一個規範(相似於jdbc),詳細的實現是各個j2ee容器提供商。sun   僅僅是要求,j2ee容器必須有JNDI這種功能。

 
3、JNDI 在j2ee系統中的角色是“交換機”,是J2EE組件在執行時間接地查找其它組件、資源或服務的通用機制。 
4、JNDI 是通過資源的名字來查找的,資源的名字在整個j2ee應用中(j2ee容器中)是唯一的。 


   上文提到過雙親委派模型並不是一個強制性的約束模型,而是 Java設計者推薦給開發者的類加載器實現方式。在Java 的世界中大部分的類加載器都遵循這個模型,但也有例外

   雙親委派模型的被破壞是由這個模型自身的缺陷所導致的,雙親委派很好地解決了各個類加載器的基礎類的統一問題(越基礎的類由越上層的加載器進行加載) 基礎類之所以稱爲基礎,是因爲它們總是作爲被用戶代碼調用的API ,但世事往往沒有絕對的完美,如果基礎類又要調用回用戶的代碼,那該怎麼辦這並非是不可能的事情,一個典型的例子便是JNDI 服務,JNDI現在已經是Java的標準服務,它的代碼由啓動類加載器去加載(在 JDK 1.3時放進去的rt.jar),但JNDI 的目的就是對資源進行集中管理和查找,它需要調用由獨立廠商實現並部署在應用程序的Class Path下的JNDI 接口提供者(SPI,Service Provider Interface)的代碼,但啓動類加載器不可能認識” 這些代碼 ,因爲啓動類加載器的搜索範圍中找不到用戶應用程序類,那該怎麼辦?爲了解決這個問題,Java設計團隊只好引入了一個不太優雅的設計:線程上下文類加載器(Thread Context ClassLoader)。這個類加載器可以通過java.lang.Thread類的setContextClassLoader()方法進行設置,如果創建線程時還未設置,它將會從父線程中繼承一個,如果在應用程序的全局範圍內都沒有設置過的話,那這個類加載器默認就是應用程序類加載器(Application ClassLoader)。

   有了線程上下文類加載器,就可以做一些舞弊的事情了,JNDI服務使用這個線程上下文類加載器去加載所需要的 SPI代碼,也就是父類加載器請求子類加載器去完成類加載的動作這種行爲實際上就是打通了雙親委派模型的層次結構來逆向使用類加載器 ,實際上已經違背了雙親委派模型的一般性原則,但這也是無可奈何的事情。Java中所有涉及SPI的加載動作基本上都採用這種方式,例如JNDI JDBCJCE JAXB JBI


OSGI


目前,業內關於OSGI技術的學習資源或者技術文檔還是很少的。我在某寶網搜索了一下“OSGI”的書籍,結果倒是有,但是種類少的可憐,而且幾乎沒有人購買。
因爲工作的原因我需要學習OSGI,所以我不得不想盡辦法來主動學習OSGI。我將用文字記錄學習OSGI的整個過程,通過整理書籍和視頻教程,來讓我更加了解這門技術,同時也讓需要學習這門技術的同志們有一個清晰的學習路線。

我們需要解決一下幾問題:
1.如何正確的理解和認識OSGI技術?

我們從外文資料上或者從翻譯過來的資料上看到OSGi解釋和定義,都是直譯過來的,但是OSGI的真實意義未必是中文直譯過來的意思。OSGI的解釋就是Open Service Gateway Initiative,直譯過來就是“開放的服務入口(網關)的初始化”,聽起來非常費解,什麼是服務入口初始化?

所以我們不去直譯這個OSGI,我們換一種說法來描述OSGI技術。

我們來回到我們以前的某些開發場景中去,假設我們使用SSH(struts+spring+hibernate)框架來開發我們的Web項目,我們做產品設計和開發的時候都是分模塊的,我們分模塊的目的就是實現模塊之間的“解耦”,更進一步的目的是方便對一個項目的控制和管理。
我們對一個項目進行模塊化分解之後,我們就可以把不同模塊交給不同的開發人員來完成開發,然後項目經理把大家完成的模塊集中在一起,然後拼裝成一個最終的產品。一般我們開發都是這樣的基本情況。

那麼我們開發的時候預計的是系統的功能,根據系統的功能來進行模塊的劃分,也就是說,這個產品的功能或客戶的需求是劃分的重要依據。

但是我們在開發過程中,我們模塊之間還要彼此保持聯繫,比如A模塊要從B模塊拿到一些數據,而B模塊可能要調用C模塊中的一些方法(除了公共底層的工具類之外)。所以這些模塊只是一種邏輯意義上的劃分。

最重要的一點是,我們把最終的項目要去部署到tomcat或者jBoss的服務器中去部署。那麼我們啓動服務器的時候,能不能關閉項目的某個模塊或功能呢?很明顯是做不到的,一旦服務器啓動,所有模塊就要一起啓動,都要佔用服務器資源,所以關閉不了模塊,假設能強制拿掉,就會影響其它的功能。

以上就是我們傳統模塊式開發的一些侷限性。

我們做軟件開發一直在追求一個境界,就是模塊之間的真正“解耦”、“分離”,這樣我們在軟件的管理和開發上面就會更加的靈活,甚至包括給客戶部署項目的時候都可以做到更加的靈活可控。但是我們以前使用SSH框架等架構模式進行產品開發的時候我們是達不到這種要求的。

所以我們“架構師”或頂尖的技術高手都在爲模塊化開發努力的摸索和嘗試,然後我們的OSGI的技術規範就應運而生。

現在我們的OSGI技術就可以滿足我們之前所說的境界:在不同的模塊中做到徹底的分離,而不是邏輯意義上的分離,是物理上的分離,也就是說在運行部署之後都可以在不停止服務器的時候直接把某些模塊拿下來,其他模塊的功能也不受影響。

由此,OSGI技術將來會變得非常的重要,因爲它在實現模塊化解耦的路上,走得比現在大家經常所用的SSH框架走的更遠。這個技術在未來大規模、高訪問、高併發的Java模塊化開發領域,或者是項目規範化管理中,會大大超過SSH等框架的地位。

現在主流的一些應用服務器,Oracle的weblogic服務器,IBM的WebSphere,JBoss,還有Sun公司的glassfish服務器,都對OSGI提供了強大的支持,都是在OSGI的技術基礎上實現的。有那麼多的大型廠商支持OSGI這門技術,我們既可以看到OSGI技術的重要性。所以將來OSGI是將來非常重要的技術。

但是OSGI仍然脫離不了框架的支持,因爲OSGI本身也使用了很多spring等框架的基本控件(因爲要實現AOP依賴注入等功能),但是哪個項目又不去依賴第三方jar呢?


   雙親委派模型的另一被破壞是由於用戶對程序動態性的追求而導致的,這裏所說的“ 動態性指的是當前一些非常熱門的名詞:代碼熱替換(HotSwap)、模塊熱部署(HotDeployment)等 ,說白了就是希望應用程序能像我們的計算機外設那樣,接上鼠標、U盤,不用重啓機器就能立即使用,鼠標有問題或要升級就換個鼠標,不用停機也不用重啓。對於個人計算機來說,重啓一次其實沒有什麼大不了的,但對於一些生產系統來說,關機重啓一次可能就要被列爲生產事故,這種情況下熱部署就對軟件開發者,尤其是企業級軟件開發者具有很大的吸引力Sun 公司所提出的JSR-294JSR-277規範在與 JCP組織的模塊化規範之爭中落敗給JSR-291(即 OSGi R4.2),雖然Sun不甘失去Java 模塊化的主導權,獨立在發展 Jigsaw項目,但目前OSGi已經成爲了業界“ 事實上” Java模塊化標準,而OSGi實現模塊化熱部署的關鍵則是它自定義的類加載器機制的實現。每一個程序模塊( OSGi 中稱爲Bundle)都有一個自己的類加載器,當需要更換一個Bundle 時,就把Bundle連同類加載器一起換掉以實現代碼的熱替換。

   OSGi環境下,類加載器不再是雙親委派模型中的樹狀結構,而是進一步發展爲更加複雜的網狀結構,當收到類加載請求時,OSGi 將按照下面的順序進行類搜索:

1)將以java.*開頭的類委派給父類加載器加載。

2)否則,將委派列表名單內的類委派給父類加載器加載。

3)否則,將Import列表中的類委派給 Export這個類的Bundle的類加載器加載。

4)否則,查找當前Bundle Class Path,使用自己的類加載器加載。

5)否則,查找類是否在自己的Fragment Bundle中,如果在,則委派給 Fragment Bundle的類加載器加載。

6)否則,查找Dynamic Import列表的 Bundle,委派給對應Bundle的類加載器加載。

7)否則,類查找失敗。

   上面的查找順序中只有開頭兩點仍然符合雙親委派規則,其餘的類查找都是在平級的類加載器中進行的。

   只要有足夠意義和理由,突破已有的原則就可認爲是一種創新。正如OSGi中的類加載器並不符合傳統的雙親委派的類加載器,並且業界對其爲了實現熱部署而帶來的額外的高複雜度還存在不少爭議,但在Java 程序員中基本有一個共識:OSGi中對類加載器的使用是很值得學習的,弄懂了OSGi的實現,就可以算是掌握了類加載器的精髓


Tomcat類加載器以及應用間class隔離與共享



Tomcat的用戶一定都使用過其應用部署功能,無論是直接拷貝文件到webapps目錄,還是修改server.xml以目錄的形式部署,或者是增加虛擬主機,指定新的appBase等等。


但部署應用時,不知道你是否曾注意過這幾點:


  1. 如果在一個Tomcat內部署多個應用,甚至多個應用內使用了某個類似的幾個不同版本,但它們之間卻互不影響。這是如何做到的。


  2. 如果多個應用都用到了某類似的相同版本,是否可以統一提供,不在各個應用內分別提供,佔用內存呢。


  3. 還有時候,在開發Web應用時,在pom.xml中添加了servlet-api的依賴,那實際應用的class加載時,會加載你的servlet-api 這個jar嗎


以上提到的這幾點,在Tomcat以及各類的應用服務器中,都是通過類加載器(ClasssLoader)來實現的。通過本文,你可以瞭解到Tomcat內部提供的各種類加載器,Web應用的class和資源等加載的方式,以及其內部的實現原理。在遇到類似問題時,更胸有成竹。



類加載器


Java語言本身,以及現在其它的一些基於JVM之上的語言(Groovy,Jython, Scala...),都是在將代碼編譯生成class文件,以實現跨多平臺,write once, run anywhere。最終的這些class文件,在應用中,又被加載到JVM虛擬機中,開始工作。而把class文件加載到JVM的組件,就是我們所說的類加載器。而對於類加載器的抽象,能面對更多的class數據提供形式,例如網絡、文件系統等。


Java中常見的那個ClassNotFoundExceptionNoClassDefFoundError就是類加載器告訴我們的。


Servlet規範指出,容器用於加載Web應用內Servlet的class loader, 允許加載位於Web應用內的資源。但不允許重寫java.*, javax.*以及容器實現的類。同時

每個應用內使用Thread.currentThread.getContextClassLoader()獲得的類加載器,都是該應用區別於其它應用的類加載器等等。


根據Servlet規範,各個應用服務器廠商自行實現。所以像其他的一些應用服務器一樣, Tomcat也提供了多種的類加載器,以便應用服務器內的class以及部署的Web應用類文件運行在容器中時,可以使用不同的class repositories


在Java中,類加載器是以一種父子關係樹來組織的。除Bootstrap外,都會包含一個parent 類加載器。(這裏寫parent 類加載器,而不是父類加載器,不是爲了裝X,是爲了避免和Java裏的父類混淆) 一般以類加載器需要加載一個class或者資源文件的時候,他會先委託給他的parent類加載器,讓parent類加載器先來加載,如果沒有,纔再在自己的路徑上加載。這就是人們常說的雙親委託,即把類加載的請求委託給parent。


但是...,這裏需要注意一下


對於Web應用的類加載,和上面的雙親委託是有區別的。


   

   主流的Java Web服務器(也就是Web容器) ,如Tomcat、Jetty、WebLogicWebSphere 或其他筆者沒有列舉的服務器,都實現了自己定義的類加載器(一般都不止一個)。因爲一個功能健全的 Web容器,要解決如下幾個問題:

   1)部署在同一個Web容器上 兩個Web應用程序使用的Java類庫可以實現相互隔離。這是最基本的需求,兩個不同的應用程序可能會依賴同一個第三方類庫的不同版本,不能要求一個類庫在一個服務器中只有一份,服務器應當保證兩個應用程序的類庫可以互相獨立使用。

   2)部署在同一個Web容器上 兩個Web應用程序所使用的Java類庫可以互相共享 。這個需求也很常見,例如,用戶可能有10個使用spring 組織的應用程序部署在同一臺服務器上,如果把10Spring分別存放在各個應用程序的隔離目錄中,將會是很大的資源浪費——這主要倒不是浪費磁盤空間的問題,而是指類庫在使用時都要被加載到Web容器的內存,如果類庫不能共享,虛擬機的方法區就會很容易出現過度膨脹的風險

   3)Web容器需要儘可能地保證自身的安全不受部署的Web應用程序影響。目前,有許多主流的Java Web容器自身也是使用Java語言來實現的。因此,Web容器本身也有類庫依賴的問題,一般來說,基於安全考慮,容器所使用的類庫應該與應用程序的類庫互相獨立。

   4)支持JSP應用的Web容器,大多數都需要支持 HotSwap功能。我們知道,JSP文件最終要編譯成Java Class才能由虛擬機執行,但JSP文件由於其純文本存儲的特性,運行時修改的概率遠遠大於第三方類庫或程序自身的Class文件 。而且ASP、PHP 和JSP這些網頁應用也把修改後無須重啓作爲一個很大的“優勢”來看待 ,因此“主流”的Web容器都會支持JSP生成類的熱替換 ,當然也有“非主流”的,如運行在生產模式(Production Mode)下的WebLogic服務器默認就不會處理JSP文件的變化。

   由於存在上述問題,在部署Web應用時,單獨的一個Class Path就無法滿足需求了,所以各種 Web容都“不約而同”地提供了好幾個Class Path路徑供用戶存放第三方類庫,這些路徑一般都以“lib”或“classes ”命名。被放置到不同路徑中的類庫,具備不同的訪問範圍和服務對象,通常,每一個目錄都會有一個相應的自定義類加載器去加載放置在裏面的Java類庫 。現在,就以Tomcat 容器爲例,看一看Tomcat具體是如何規劃用戶類庫結構和類加載器的。

   在Tomcat目錄結構中,有3組目錄(“/common/*”、“/server/*”和“/shared/*”)可以存放Java類庫,另外還可以加上Web 應用程序自身的目錄“/WEB-INF/*” ,一共4組,把Java類庫放置在這些目錄中的含義分別如下:

   ①放置在/common目錄中:類庫可被Tomcat所有的 Web應用程序共同使用

   ②放置在/server目錄中:類庫可被Tomcat使用,對所有的Web應用程序都不可見。

   ③放置在/shared目錄中:類庫可被所有的Web應用程序共同使用,但對Tomcat自己不可見。

   ④放置在/WebApp/WEB-INF目錄中:類庫僅僅可以被此Web應用程序使用,對 Tomcat和其他Web應用程序都不可見。

   爲了支持這套目錄結構,並對目錄裏面的類庫進行加載和隔離,Tomcat自定義了多個類加載器,這些類加載器按照經典的雙親委派模型來實現,其關係如下圖所示。





   上圖中灰色背景的3個類加載器是JDK默認提供的類加載器,這3個加載器的作用已經介紹過了。而CommonClassLoader、CatalinaClassLoader、SharedClassLoader和WebappClassLoader則是Tomcat自己定義的類加載器,它們分別加載/common/*、/server/*、/shared/*和/WebApp/WEB-INF/*中的Java類庫。其中WebApp類加載器和Jsp類加載器通常會存在多個實例每一個Web應用程序對應一個WebApp類加載器每一個JSP文件對應一個Jsp類加載器

   從圖中的委派關係中可以看出,CommonClassLoader能加載的類都可以被Catalina ClassLoader和SharedClassLoader使用,而CatalinaClassLoader和Shared  ClassLoader自己能加載的類則與對方相互隔離。WebAppClassLoader可以使用SharedClassLoader加載到的類,但各個WebAppClassLoader實例之間相互隔離。而JasperLoader的加載範圍僅僅是這個JSP文件所編譯出來的那一個.Class文件,它出現的目的就是爲了被丟棄:Web容器檢測到JSP文件被修改時,會替換掉目前的JasperLoader的實例,並通過再建立一個新的Jsp類加載器來實現JSP文件的HotSwap功能。

   對於Tomcat的6.x版本,只有指定了tomcat/conf/catalina.properties配置文件的server.loader和share.loader項後纔會真正建立Catalina ClassLoader和Shared ClassLoader的實例,否則在用到這兩個類加載器的地方都會用Common ClassLoader的實例代替,而默認的配置文件中沒有設置這兩個loader項,所以Tomcat 6.x順理成章地把/common、/server和/shared三個目錄默認合併到一起變成一個/lib目錄,這個目錄裏的類庫相當於以前/common目錄中類庫的作用。這是Tomcat設計團隊爲了簡化大多數的部署場景所做的一項改進,如果默認設置不能滿足需要,用戶可以通過修改配置文件指定server.loader和share.loader的方式重新啓用Tomcat 5.x的加載器架構

    Tomcat加載器的實現清晰易懂,並且採用了官方推薦的“正統”的使用類加載器的方式。如果讀者閱讀完上面的案例後,能完全理解Tomcat設計團隊這樣佈置加載器架構的用意,那說明已經大致掌握了類加載器“主流”的使用方式,那麼筆者不妨再提一個問題讓讀者思考一下:前面曾經提到過一個場景,如果有10個Web應用程序都是用Spring來進行組織和管理的話,可以把Spring放到Common或Shared目錄下讓這些程序共享。Spring要對用戶程序的類進行管理,自然要能訪問到用戶程序的類,而用戶的程序顯然是放在/WebApp/WEB-INF目錄中的,那麼被CommonClassLoader或SharedClassLoader加載的Spring如何訪問並不在其加載範圍內的用戶程序呢?如果研究過虛擬機類加載器機制中的雙親委派模型,相信讀者可以很容易地回答這個問題。

  分析:如果按主流的雙親委派機制,顯然無法做到讓父類加載器加載的類 訪問子類加載器加載的類,上面在類加載器一節中提到過通過線程上下文方式傳播類加載器。

  答案是使用線程上下文類加載器來實現的,使用線程上下文加載器,可以讓父類加載器請求子類加載器去完成類加載的動作。看spring源碼發現,spring加載類所用的Classloader是通過Thread.currentThread().getContextClassLoader()來獲取的,而當線程創建時會默認setContextClassLoader(AppClassLoader)即線程上下文類加載器被設置爲 AppClassLoaderspring中始終可以獲取到這個AppClassLoader(  Tomcat裏就是WebAppClassLoader)子類加載器來加載bean ,以後任何一個線程都可以通過 getContextClassLoader()獲取到WebAppClassLoadergetbean  


本篇博文內容取材自《深入理解Java虛擬機:JVM高級特性與最佳實踐》

微信公衆號【Java技術江湖】一位阿里 Java 工程師的技術小站。作者黃小斜,專注 Java 相關技術:SSM、SpringBoot、MySQL、分佈式、中間件、集羣、Linux、網絡、多線程,偶爾講點Docker、ELK,同時也分享技術乾貨和學習經驗,致力於Java全棧開發!(關注公衆號後回覆”Java“即可領取 Java基礎、進階、項目和架構師等免費學習資料,更有數據庫、分佈式、微服務等熱門技術學習視頻,內容豐富,兼顧原理和實踐,另外也將贈送作者原創的Java學習指南、Java程序員面試指南等乾貨資源)


                     9eedaaa588bef997bef63a7160fa349134bdb78c



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章