Tomcat類加載機制
類加載機制概述
與很多服務器應用一樣,Tomcat 也安裝了各種類加載器(那就是實現了 java.lang.ClassLoader 的類)。借助類加載器,容器的不同部分以及運行在容器上的 Web 應用就可以訪問不同的倉庫(保存著可使用的類和資源)。這個機制實現了 Servlet 規范 2.4 版(尤其是 9.4 節和 9.6 節)里所定義的功能。
在 Java 環境中,類加載器的布局結構是一種父子樹的形式。通常,類加載器被請求加載一個特定的類或資源時,它會先把這一請求委托給它的父類加載器,只有(一個或多個)父類加載器無法找到請求的類或資源時,它才開始查看自身的倉庫。注意,Web 應用的類加載器模式跟這個稍有不同,下文將詳細介紹,但基本原理是一樣。
當 Tomcat 啟動后,它就會創建一組類加載器,這些類加載器被布局成如下圖所示這種父子關系,父類加載器在子類加載器之上:
Bootstrap
|
System
|
Common-
/ \
Webapp1 Webapp2 ...
接下來,通過幾節內容來詳細說明每一個類加載器的特點,其中還將講解這些加載器可使用的類和資源的來源。
類加載器定義
如上圖所示,Tomcat 在初始化時會創建如下這些類加載器:
- Bootstrap 這種類加載器包含 JVM 所提供的基本的運行時類,以及來自系統擴展目錄($JAVA_HOME/jre/lib/ext)里 JAR 文件中的類。注意:在有些 JVM 的實現中,它的作用不僅僅是類加載器,或者它可能根本不可見(作為類加載器)。
- System 這種類加載器通常是根據 CLASSPATH 環境變量內容進行初始化的。所有的這些類對于 Tomcat 內部類以及 Web 應用來說都是可見的。不過,標準的 Tomcat 啟動腳本($CATALINA_HOME/bin/catalina.sh 或 %CATALINA_HOME%\bin\catalina.bat)完全忽略了 CLASSPATH 環境變量自身的內容,相反從下列倉庫來構建系統類加載器:
- $CATALINA_HOME/bin/bootstrap.jar 包含用來初始化 Tomcat 服務器的 main() 方法,以及它所依賴的類加載器實現類
- $CATALINA_BASE/bin/tomcat-juli.jar 或 $CATALINA_HOME/bin/tomcat-juli.jar 日志實現類。其中包括了對 java.util.logging API 的功能增強類(Tomcat JULI),以及對 Tomcat 內部使用的 Apache Commons 日志庫的包重命名副本。詳情參看 Tomcat 日志文檔。
如果 *$CATALINA_BASE/bin*?中存在 `tomcat-juli.jar`,
就不會使用 *$CATALINA_HOME/bin*?中的那一個。它有助于日志的特定配置。 ??
- $CATALINA_HOME/bin/commons-daemon.jar Apache Commons Daemon 項目的類。該 JAR 文件并不存在于由 catalina.bat 或 catalina.sh 腳本所創建的 CLASSPATH 中,而是引用自 bootstrap.jar 的清單文件。
- Common 這種類加載器包含更多的額外類,它們對于Tomcat 內部類以及所有 Web 應用都是可見的。
通常,應用類不會放在這里。該類加載器所搜索的位置定義在 $CATALINA_BASE/conf/catalina.properties 的 common.loader 屬性中。默認的設置會搜索下列位置(按照列表中的上下順序)。
- $CATALINA_BASE/lib 中的解包的類和資源
- $CATALINA_BASE/lib 中的 JAR 文件。
- $CATALINA_HOME/lib 中的解包類和資源
- $CATALINA_HOME/lib 中的 JAR 文件。
默認,它包含以下這些內容:
- annotations-api.jar JavaEE 注釋類。
- catalina.jar Tomcat 的 Catalina servlet 容器部分的實現。
- catalina-ant.jar Tomcat Catalina Ant 任務。
- catalina-ha.jar 高可用性包。
- catalina-storeconfig.jar
- catalina-tribes.jar 組通信包
- ecj-*.jar Eclipse JDT Java 編譯器
- el-api.jar EL 3.0 API
- jasper.jar Tomcat Jasper JSP 編譯器與運行時
- jasper-el.jar Tomcat Jasper EL 實現
- jsp-api.jar JSP 2.3 API
- servlet-api.jar Servlet 3.1 API
- tomcat-api.jar Tomcat 定義的一些接口
- tomcat-coyote.jar Tomcat 連接器與工具類。
- tomcat-dbcp.jar 數據庫連接池實現,基于 Apache Commons Pool 的包重命名副本和 Apache Commons DBCP。
- tomcat-i18n-**.jar 包含其他語言資源束的可選 JAR。因為默認的資源束也可以包含在每個單獨的 JAR 文件中,所以如果不需要國際化信息,可以將其安全地移除。
- tomcat-jdbc.jar 一個數據庫連接池替代實現,又被稱作 Tomcat JDBC 池。詳情參看JDBC 連接池文檔。
- tomcat-util.jar Apache Tomcat 多種組件所使用的常用類。
- tomcat-websocket.jar WebSocket 1.1 實現
- websocket-api.jar WebSocket 1.1 API
- WebappX 為每個部署在單個 Tomcat 實例中的 Web 應用創建的類加載器。你的 Web 應用的 /WEB-INF/classes 目錄中所有的解包類及資源,以及 /WEB-INF/lib 目錄下 JAR 文件中的所有類及資源,對于該應用而言都是可見的,但對于其他應用來說則不可見。
如上所述,Web 應用類加載器背離了默認的 Java 委托模式(根據 Servlet 規范 2.4 版的 9.7.2 Web Application Classloader一節中提供的建議)。當某個請求想從 Web 應用的 WebappX 類加載器中加載類時,該類加載器會先查看自己的倉庫,而不是預先進行委托處理。There are exceptions。JRE 基類的部分類不能被重寫。對于一些類(比如 J2SE 1.4+ 的 XML 解析器組件),可以使用 J2SE 1.4 支持的特性。最后,類加載器會顯式地忽略所有包含 Servlet API 類的 JAR 文件,所以不要在 Web 應用包含任何這樣的 JAR 文件。Tomcat 其他的類加載器則遵循常用的委托模式。
因此,從 Web 應用的角度來看,加載類或資源時,要查看的倉庫及其順序如下:
- JVM 的 Bootstrap 類
- Web 應用的 /WEB-INF/classes 類
- Web 應用的 /WEB-INF/lib/*.jar 類
- System 類加載器的類(如上所述)
- Common 類加載器的類(如上所述)
如果 Web 應用類加載器配置有 ,則順序變為:
- JVM 的 Bootstrap 類
- System 類加載器的類(如上所述)
- Common 類加載器的類(如上所述)
- Web 應用的 /WEB-INF/classes 類
- Web 應用的 /WEB-INF/lib/*.jar 類
XML解析器和 Java
從 Java 1.4 版起,JRE 就包含了一個 JAXP API 的副本和一個 XML 解析器。這對希望使用自己的 XML 解析器的應用產生了一定的影響。
在過去的 Tomcat 中,你只需在 Tomcat 庫中簡單地換掉 XML 解析器,就能改變所有 Web 應用使用的解析器。但對于現在版本的 Java 而言,這一技術并沒有效果,因為通常的類加載器委托進程往往會優先選擇 JDK 內部的實現。
Java 支持一種叫做“授權標準覆蓋機制”,從而能夠替換在 JCP 之外創建的 API(例如 W3C 的 DOM 和 SAX)。它還可以用于更新 XML 解析器實現。關于此機制的詳情,請參看 http://docs.oracle.com/javase/1.5.0/docs/guide/standards/index.html為了利用該機制,Tomcat 在啟動容器的命令行中包含了系統屬性設置 -Djava.endorsed.dirs=$JAVA_ENDORSED_DIRS。該選項的默認值為 $CATALINA_HOME/endorsed。但要注意,這個 endorsed 目錄并非默認創建的。
安全管理器下運行
當在安全管理器下運行時,類被允許加載的位置也是基于策略文件中的內容,詳情可查看 安全管理器文檔。