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

打破雙親委派模型

JNDI

JNDI 的理解

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

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

要了解JNDI的做用,咱們可以從「假設不用JNDI咱們如何作?用了JNDI後咱們又將如何作?」這個問題來探討。 java

沒有JNDI的作法: mysql

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

Java代碼  收藏代碼web

  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,可以很是快開發出對應的應用程序。 spring

沒有JNDI的作法存在的問題: 
一、數據庫server名稱MyDBServer 、username和口令均可能需要改變,由此引起JDBC URL需要改動; 
二、數據庫可能改用別的產品,如改用DB2或者Oracle,引起JDBC驅動程序包和類名需要改動; 
三、隨着實際使用終端的添加,原配置的鏈接池參數可能需要調整; 
四、...... sql

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

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

由此,就有了JNDI。

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

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

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

詳細操做例如如下(以JBoss爲例): 
一、配置數據源 
在JBoss 的 D:jboss420GAdocsexamplesjca 文件夾如下。有很是多不一樣數據庫引用的數據源定義模板。

將當中的 mysql-ds.xml 文件Copy到你使用的server下,如 D:jboss420GAserverdefaultdeploy。 
改動 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及密碼等。 

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

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 的承諾,而且通過一些計劃和預先考慮。這個承諾是全然可以實現的。 

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

三、JNDI 在j2ee系統中的角色是「交換機」,是J2EE組件在執行時間接地查找其它組件、資源或服務的通用機制。 
四、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 、JDBC、JCE、 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-29四、JSR-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中常見的那個ClassNotFoundException和NoClassDefFoundError就是類加載器告訴咱們的。

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、WebLogic、WebSphere 或其餘筆者沒有列舉的服務器,都實現了本身定義的類加載器(通常都不止一個)。由於一個功能健全的 Web容器,要解決以下幾個問題:

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

   2)部署在同一個Web容器上 的兩個Web應用程序所使用的Java類庫能夠互相共享 。這個需求也很常見,例如,用戶可能有10個使用spring 組織的應用程序部署在同一臺服務器上,若是把10份Spring分別存放在各個應用程序的隔離目錄中,將會是很大的資源浪費——這主要倒不是浪費磁盤空間的問題,而是指類庫在使用時都要被加載到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),即線程上下文類加載器被設置爲 AppClassLoader,spring中始終能夠獲取到這個AppClassLoader( 在 Tomcat裏就是WebAppClassLoader)子類加載器來加載bean ,之後任何一個線程均可以經過 getContextClassLoader()獲取到WebAppClassLoader來getbean 了 。

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

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

                     9eedaaa588bef997bef63a7160fa349134bdb78c

相關文章
相關標籤/搜索