- tags: tomcat
- categories:筆記
- date: 2017-03-09 21:02:08
前幾天初步通過源代碼看看并且學習了JDK的類加載器相關內容。其實在java的生態系統平臺內,類加載器是至關重要的組件吧,因為設計到了OOP的對象抽象class的加載問題。若是連OOP的基礎class都加載不了,那也不要說再干些其他的事情了。所以從另一方面來就說明了它的重要性,以至于不同的其他軟件基金會例如apache中tomcat,我們經常使用的tomcat貓,就有自己的類加載機制。不過tomcat的類加載器與jdk自己的雙親委托模型的類加載可是有不同的,而且tomcat中對類加載的使用也是值得我們去深入了解了解的。這次,我想從基于我使用了挺久的tomcat貓看看,它的類加載器大致是如何工作的,核心的東西主要有那些呢?就來個對貓貓的初步探索吧,應該能給自己對這只貓的理解更細致吧。
在開始呢,其實tomcat的源代碼挺多的,內部細節挺多的,apache力量真是讓人欽佩。造出了很多有意思,有用的輪子。給我們這些用輪子的人,能養家糊口,工作需要,學習需要等等。對于我這個java方向的碼農,當然大apache啦。所以,就根據自己的理解和參考一些網上大神的理解自己也倒騰倒騰tomcat的類加載器方面的內容,也不枉我經常使用它。對他其實也是一知半解,都是在使用層次上理解它,它的內部的東西真是不甚了解,對我來說挺神秘的。所以,我想慢慢通過自己對它內部的重要組件的學習,剖析來揭開它的神秘面紗。↖(▔▽▔)↗ (這里使用的是tomcat7源代碼)
在學習tomcat的類加載之前,必須要明確一個概念或者說的基本環境:tomcat服務器本身和部署在其之上的應用之間類關系,tomcat服務器自身啟動,也是依賴與jdk的,也是需要通過自己的類加載加載它所需要的很多類;在tomcat啟動成功之后,我們每次部署的應用也是需要加載自己所需要的類的,以及每個web應用當然都會加載自己所需要的類。所以,不同的類加載器,就將這些tomcat自己啟動運行需要的類,單個應用需要的類,應用之間共享的基礎類庫資源進行了規劃與管理。
tomcat的類加載器有哪些?
要想知道類加載器相關的內容,當然要知道除了在jdk基礎上的三種類加載(Bootstrap,ext,appclassLoader),它有那些自定義的類加載呢?也只有在知道tomcat有那些自己定義的類加載的前提下,才能知道它們是如何聯系的,具體是如何在tomcat運行中加載類的呢?與jdk的JVM加載類過程有什么不同么?
其實,想要知道tomcat的類加載很簡單,就是調用getClassLoader和getParent等方法就夠了。寫個簡單的servlet,然后對類加載器信息進行在瀏覽器頁面輸出即可。具體的例子可以參考下面:(多了寫無關的格式輸出,為了看起來更直觀)
/*ClassLoaderServlet.java*/
public class ClassLoaderServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws Exception{
response.setContentType("text/html");
response.setCharacterEncoding("utf8");
PrintWriter out = response.getWriter();
ClassLoader loader = getClass().getClassLoader();
String qname = getClass().getName();
out.println("<!DOCTYPE \">");
out.println("<HTML>");
out.println(" <HEAD><TITLE>Tomtcat ClassLoader</TITLE></HEAD>");
out.println(" <BODY>");
out.println("當前類:"+qname.substring(qname.lastIndexOf(".")+1)+"的類加載器是:"+loader.getClass().getName()+"</br>");
out.println(loader+"</br>");
out.println("-----------------"+"</br>");
while(loader != null){
ClassLoader tmp = loader;
loader = loader.getParent();
if(loader != null){
out.println(tmp.getClass().getName()+"的父類加載器是:"+ loader.getClass().getName()+"</br>");
}else{
out.println(tmp.getClass().getName()+"的父類加載器是:"+ loader+"</br>");
}
out.println("-----------------"+"</br>");
}
out.println("<hr>");
out.println(" </BODY>");
out.println("</HTML>");
out.flush();
out.close();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws Exception{
// TODO Auto-generated method stub
super.doGet(req, resp);
}
}
在web.xml中進行簡單的servlet配置,通過瀏覽器訪問即可:
<servlet>
<servlet-name>ClassLoaderServlet</servlet-name>
<servlet-class>servlet.classloader.ClassLoaderServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ClassLoaderServlet</servlet-name>
<url-pattern>/servlet/ClassLoaderServlet</url-pattern>
</servlet-mapping>
最后可以得到tomcat的類加載信息:可以看到除了jdk基礎的三層類加載器結構,其實下面還是有例如StandardClassLoader,WebappClassLoader,CommonClassLoader,ShareClassLoader,ServerClassLoader等自定義類加載器的。
當前類:ClassLoaderServlet的類加載器是:org.apache.catalina.loader.WebappClassLoader
WebappClassLoader context: /springmvcdemo delegate: false repositories: /WEB-INF/classes/ ----------> Parent Classloader: org.apache.catalina.loader.StandardClassLoader@38d1258b
-----------------
org.apache.catalina.loader.WebappClassLoader的父類加載器是:org.apache.catalina.loader.StandardClassLoader
-----------------
org.apache.catalina.loader.StandardClassLoader的父類加載器是:sun.misc.Launcher$AppClassLoader
-----------------
sun.misc.Launcher$AppClassLoader的父類加載器是:sun.misc.Launcher$ExtClassLoader
-----------------
sun.misc.Launcher$ExtClassLoader的父類加載器是:null
-----------------
## 所以,根據上述的servlet程序,可以看到tomcat中的類加載器的層級結構如下:
## 但是要注意,這個類結構其實與tomcat官方文檔的類加載結構有些區別。那具體有什么區別或者聯系?
BootStrapClassLoader
|
ExtClassLoader
|
AppClassLoader
|
StandardClassLoader
|
WebappClassLoader
其實,在tomcat官方文檔中,說明了tomcat7的類加載結構如下圖:(每個層次的類加載器也有說明)
注意每層的類加載器與JVM的概念并不是完全的一樣的。但是tomcat所有自定義的類加載都是繼承AppClassLoader這個系統類加載器的。
Bootstrap --也不完全是JVM的bootstrap
|
System --也不完全是JVM的appClassLoader的概念
|
common
/ \
Webapp1 Webapp2....
對于每個層次的類加載是這么說明的:(英文翻譯有點菜,肯定有差異啦,建議英語好的可以自己看英文去啦,可以點擊官網查看官方tomcat7類加載器說明](http://tomcat.apache.org/tomcat-7.0-doc/class-loader-howto.html))
那CATALINA_HOME和CATALINA_BASE有什么區別呢?總的來說,區別就是在設置tomcat多實例的時候。在單實例時候,也就是我們經常使用的狀態,這兩個變量指向的都是tomcat的安裝目錄(包含完整的bin,conf,webapps,logs,temp,work等等文件夾); 多實例也就是在一個主tomcat下(可被公有使用),配置多個私有tomcat實例,每個實例都包含完整的私有目錄,如conf,webapps,work,logs目錄。總結來說,CATALINA_HOME是安裝目錄,CATALINA_BASE是每個tomcat實例的工作根目錄
tomcat單實例與多實例實踐
CATALINA_HOME與CATALINA_BASE
Bootstrap:
這個類加載用于加載JVM運行提供的基礎類以及拓展路徑($JAVA_HOME/jre/lib/ext)類庫中的類。(自己口水話:其實也就是為tomcat自身服務器的啟動運行以及應用中用到的一些JDK基礎類庫中的類進行加載)-
System:
這個類加載器通常是用來初始化那些在CLASSPATH環境變量路徑上的變量內容的,在這類庫路徑上的所有類對于tomcat服務器本身以及服務器上部署的web應用程序來說都是可見的。然而,對于標準的tomcat容器啟動時的腳本($CATALINA_HOME/bin/catalina.sh 或者%CATALINA_HOME%\bin\catalina.bat)來說,這些啟動腳本總是忽略CLASSPATH路徑上的內容變量,轉而讀取其他路徑上的類庫來構建system類加載。eg:我們通常如果在tomcat安裝目錄中啟動tomcat服務器,都是運行startup.bat或者startup.sh,其實這些腳本內部主要工作也就是調用catalina.bat來啟動服務器。看看startup.bat中部分windows腳本,也就是讀取tomcat主目錄,查找catalina.bat腳本,若是存在,則執行該腳本啦:
if exist "%CATALINA_HOME%\bin\catalina.bat" goto okHome
echo The CATALINA_HOME environment variable is not defined correctly
echo This environment variable is needed to run this program
goto end
:okHome
set "EXECUTABLE=%CATALINA_HOME%\bin\catalina.bat" --設置執行腳本文件路徑
rem Check that target executable exists
if exist "%EXECUTABLE%" goto okExec --腳本存在,則調到執行子函數
然后我們可以看看catalina.bat中對于CLASSPATH環境變量的設置:(截取部分相關內容)
rem Ensure that any user defined CLASSPATH variables are not used on startup,
rem but allow them to be specified in setenv.bat, in rare case when it is needed.
set CLASSPATH=
rem Add on extra jar file to CLASSPATH
rem Note that there are no quotes as we do not want to introduce random
rem quotes into the CLASSPATH
if "%CLASSPATH%" == "" goto emptyClasspath
set "CLASSPATH=%CLASSPATH%;"
:emptyClasspath
set "CLASSPATH=%CLASSPATH%%CATALINA_HOME%\bin\bootstrap.jar"
set "CLASSPATH=%CLASSPATH%;%CATALINA_BASE%\bin\tomcat-juli.jar"
goto juliClasspathDone
:juliClasspathHome
set "CLASSPATH=%CLASSPATH%;%CATALINA_HOME%\bin\tomcat-juli.jar"
:juliClasspathDone
從腳本代碼中,確實可以看到,CLASSPATH變量在這個類加載階段是要讀取bootstrap.jar和tomcat-juli.jar兩個類庫文件的。
system類加載器的讀取路徑包括以下幾個:
- $CATALINA_HOME/bin/bootstrap.jar : 這個類庫包含了在啟動tomcat服務器時,用于初始化服務器配置的main()方法中所依賴的所有類的實現。(main方法內,調用了bootstrap.init()方法,也就是bootstrap.jar時tomcat服務器啟動階段的核心類庫,不能加載這個類庫的話,tomcat服務器啟動失敗)
看了看bootstrap.jar中目錄結構如下:
bootstrap.jar
├─loader
│ StandardClassLoader.class
│ StandardClassLoaderMBean.class
│
├─security
│ SecurityClassLoad.class
│
└─startup
Bootstrap.class --主要有個init()方法,用于初始化CatalinaHome,CatalinaBase,配置線程類加載器,讀取了catalina.properties文件加載commonLader,catalinaLoader,sharedLoader等類加載器。該類包含了tomcat服務器生命周期服務的所有方法。
catalina.properties
CatalinaProperties.class
ClassLoaderFactory.class
Tool.class
$CATALINA_HOME/bin/tomcat-juli.jar或者$CATALINA_BASE/bin/tomcat-juli.jar: 哎呀,這個java類庫包,就是用來強化tomcat日志相關方面的。不要小看日志,日志可是很重要的東西哦。從上面catalina.bat腳本也看到了CLASSPATH配置了tomcat-juli.jar包。
$CATALINA_HOME/bin/commons-daemon.jar: 這個類庫不在catalina.bat|.sh腳本構建的CLASSPATH變量內,而是與bootstrap.jar類庫引用相關。在tomcat服務啟動時相關守護線程的管理。
public final class Bootstrap
{
private static Bootstrap daemon = null;
private Object catalinaDaemon = null;
...
public static void main(String[] args)
{
if (daemon == null)
{
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
daemon = bootstrap;
} ....
}
-
Common
該類加載器加載那些可被tomcat服務器自身和部署的所有web應用均可見(可以調用)的類。通常一般不會修改或者用其他類庫來替換Common加載的原有類庫。該類加載器加載類庫的搜索路徑時被定義在catalina.properties($CATALINA_BASE/conf/catalina.properties)文件中的common.loader屬性值中。common.loader默認的類庫搜索路徑會按照以下排列順序進行加載:
A。加載$CATALINA_BASE/lib中未被打包的類和資源。
B。加載$CATALINA_BASE/lib中所有jar類庫。
C。加載$CATALINA_HOME/lib中未被打包的類文件與資源。
D。加載$CATALINA_HOME/lib中所有jar類庫包
至于那些lib下的每個jar包的資源有什么作用可以查看官方文檔,有詳細說明。
我們可以查看catalina.properties文件中common.loader屬性,看看有那些默認的配置:
#$CATALINA_BASE/conf/catalina.properties
common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar
-
WebappX
這個類加載器被創建是用來加載tomcat服務器單實例上每個應用自己所需要的類。(注意每個實例之間的類默認是互不可見的),該加載器加載的類庫不僅包括每個web應用下/WEB-INF/classes目錄下的所有.class類和資源文件,還包括web應用下/WEB-INF/lib目錄下所有的jar類庫。這兩個路徑下的類字節碼和jar類庫對于每個單應用程序都是可見的,但是對于同個實例上其他web應用是不可見的。
通常web應用目錄大概如下:(每個web應用編譯后的java文件到classes目錄和lib目錄在WEB-INF同一級子目錄,不過classes文件加載類在lib之前)
├─common
│ ├─ace
│ ├─css
│ ├─image
│ ├─js
│ └─jsp
├─demo
├─jsp
├─META-INF
└─WEB-INF
├─classes
│ └─demo
│ └─classloader
└─lib
在知道了tomcat有那些類加載之后,我們下一步,就可以探索它們之間的,自己內部的加載器有什么聯系和它們是如何協同來加載class的,與jdk的雙親委托加載有什么不同和相同點?
內部類加載關系與加載類過程?
根據官網上所說的,將tomcat類加載器級別籠統的分為Bootstrap,System,Common,WebappX級別。也是按照父子級別從左往右形成層級關系。那么tomcat服務器上的每個web應用程序是如何加載一個類呢?還是雙親委托模型么?tomcat程序的加載類的過程與JVM加載類的雙親委托機制是有些差異的,我們就要關注這個差異點。
類加載路徑順序?
當tomcat應用中,在處理加載某個類的請求時候,默認情況下,首先處理該請求的是WebappX級別層次的類加載器。在WebappX類加載器加載類的時候,首先會從自己的應用的類庫路徑中查找,而不是將該請求委托給自己的父級類加載器來處理[這里的父子級別類加載器只是限于tomcat自定義的類加載]。
但是,在WebappX加載類這個階段,也是有例外的情況。也就是屬于JRE類庫中的基類是不能被覆蓋重寫的(eg:在web應用中定義了java.lang.Object類,由于是WebappX類加載器先加載,那當然不能將該自定義同名的class類將JRE的java.lang.Object類給替換覆蓋了)。
而其他某些類,例如J2SE1.4后的解析XML組件類庫或者其他非JRE基礎類可以被重寫(關于XML Parsers與java JDK之間的實現封裝關系可以自己去查看)。
特別的,在web應用中任何包含servlet api類的jar文件都會被該類加載器顯式的忽略掉,這就意味著不必要將這些類型的jars文件放置在web應用類庫中。在web應用加載某個類的時候,除了WebappX類加載器可以首先嘗試加載該類,其余的類加載器都會按照JVM中的雙親委托模型來加載該類。
因此,根據web應用程序的目錄結構,在web應用中加載某個類或者資源的時候,類加載器查找類將會按照下面的路徑順序來查找:(tomcat服務器正常啟動后
)
- JVM內bootstrap 基礎類庫(對應JVM中的BootstrapClassLoader,ExtClassLoader)。
- 每個web應用項目下/WEB-INF/classes目錄下的所有類 (相當于WebappX級別加載器)
- 每個web應用項目下/WEB-INF/lib/*.jar所有jar庫包 (相當于WebappX級別加載器)
- System 類加載器加載的類庫(上面所說的)
- Common 類加載加載的類庫(上面所說的)
但是,這個路徑順序也是可以通過配置修改的,我們可以看到上面ClassLoaderServlet在輸出類加載的時候,注意到輸出WebappClassLoader屬性:
WebappClassLoader context: /springmvcdemo delegate: false repositories: /WEB-INF/classes/
----------> Parent Classloader: org.apache.catalina.loader.StandardClassLoader@38d1258b
注意到,有個delegate:false字段,就是是否代理的意思。可以看到,在沒有使用代理情況下,類加載器是按照上述路徑查找的。反過來想想若是強制顯式的使用代理配置,類查找路徑將會有影響的。在web應用中,可以通過設置Loader組件配置<Loader delegate="true"/>
來委托查找類,配置詳情
順序將會修改成下面順序:
- JVM內bootstrap 基礎類庫。
- System 類加載器加載的類(上面所說的)
- Common 類加載加載的類(上面所說的)
- 每個web應用項目下/WEB-INF/lib/*.jar所有jar庫包 (相當于WebappX級別加載器)
- 每個web應用項目下/WEB-INF/classes目錄下的所有類 (相當于WebappX級別加載器)
可以看到完全是與JVM的雙親委托模型加載機制一樣了,最后才是自定義的WebappX來加載類。
tomcat的web應用class加載過程?
這一點,其實通過tomcatd的WebappClassLoader.java源代碼可以看出:
/**
* Should this class loader delegate to the parent class loader
* <strong>before</strong> searching its own repositories (i.e. the
* usual Java2 delegation model)? If set to <code>false</code>,
* this class loader will search its own repositories first, and
* delegate to the parent only if the class or resource is not
* found locally. Note that the default, <code>false</code>, is
* the behavior called for by the servlet specification.
*/
protected boolean delegate = false;
想要具體看看tomcat的web應用上class是如何加載,那么可以通過查看WebappClassLoader.java源代碼來分析分析了, 加載類的loadClass方法如下:
/**
* Load the class with the specified name, searching using the following
* algorithm until it finds and returns the class. If the class cannot
* be found, returns <code>ClassNotFoundException</code>.
* <ul>
* <li>Call <code>findLoadedClass(String)</code> to check if the
* class has already been loaded. If it has, the same
* <code>Class</code> object is returned.</li>
* <li>If the <code>delegate</code> property is set to <code>true</code>,
* call the <code>loadClass()</code> method of the parent class
* loader, if any.</li>
* <li>Call <code>findClass()</code> to find this class in our locally
* defined repositories.</li>
* <li>Call the <code>loadClass()</code> method of our parent
* class loader, if any.</li>
* </ul>
* If the class was found using the above steps, and the
* <code>resolve</code> flag is <code>true</code>, this method will then
* call <code>resolveClass(Class)</code> on the resulting Class object.
*
* @param name Name of the class to be loaded
* @param resolve If <code>true</code> then resolve the class
*
* @exception ClassNotFoundException if the class was not found
*/
@SuppressWarnings("sync-override")
@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLockInternal(name)) {
Class<?> clazz = null;
//注意,這里是查找的本地tomcat應用的查找class緩存
//調用的是本類中自定義的方法
// (0) Check our previously loaded local class cache
clazz = findLoadedClass0(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return (clazz);
}
//調用的是java.lang.ClassLoader的JVM的查找class緩存
// (0.1) Check our previously loaded class cache
clazz = findLoadedClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return (clazz);
}
// (0.2) Try loading the class with the system class loader, to prevent
// the webapp from overriding J2SE classes
try {
//可以看到這里是因為webappClassLoader首先加載web應用class類的緣故,
//從而會判斷class是不是JVMSE中的基礎類庫中類。防止覆蓋基礎類實現。
clazz = j2seClassLoader.loadClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
// Ignore
}
boolean delegateLoad = delegate || filter(name);
/*先判斷是否設置了delegate屬性,設置為true,那么就會完全按照JVM的"雙親委托"機制流程加載類。
*若是默認的話,是先使用WebappClassLoader自己處理加載類的。
*當然了,若是委托了,使用雙親委托亦沒有加載到class實例,那還是最后使用WebappClassLoader加載。
*/
// (1) Delegate to our parent if requested
if (delegateLoad) {
try {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
// (2) Search local repositories
try {
//若是沒有委托,則默認會首次使用WebappClassLoader來加載類。通過自定義findClass定義處理類加載規則。
clazz = findClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
// Ignore
}
//若是WebappClassLoader在/WEB-INF/classes,/WEB-INF/lib下還是查找不到class,
//那么無條件強制委托給System,Common類加載器去查找該類。
// (3) Delegate to parent unconditionally
if (!delegateLoad) {
try {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
}
throw new ClassNotFoundException(name);
}
###這里注意到上面的j2seClassLoader.loadClass其實就是調用JVM中雙親類加載機制來確保J2SE基礎類不被破環,
###與上述的類查找步驟第一步bootstrap基礎類庫是想吻合的。
public WebappClassLoaderBase(ClassLoader parent) {
...
ClassLoader j = String.class.getClassLoader();
if (j == null) {
j = getSystemClassLoader(); //調用JVM的java.lang.ClassLoader的方法得到appClassLoader
while (j.getParent() != null) {
j = j.getParent(); //得到ExtClassLoader
}
}
this.j2seClassLoader = j;
...
}
哎呀,這個過程與JVM的類加載器過程差不多,就不用畫流程圖了。這里也要知道,在查找某個class緩存時候,先是找的tomcat應用自己在啟動時候加載的類資源緩存,若是查找不到了,才找JVM的class緩存。也要注意某些重要的方法有override
重寫關鍵字,對于我們理解它們關系還是有幫助的。類加載器之間的關系和各自職責也差不多了解了,最后,在看看webappClassLoader是如何加載某個class的吧。具體的細節,就是在上述代碼最后的findClass方法中。
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
// Ask our superclass to locate this class, if possible
// (throws ClassNotFoundException if it is not found)
Class<?> clazz = null;
try {
if (hasExternalRepositories && searchExternalFirst) {
try {
/*又試圖先調用父類URLClassLoader的findClass方法,URLClassLoader確實有該方法,
*該方法是用來通過URLClassPath查找name對應的符合URL規范的class類路徑,讀取該類字節碼,
*在調用defineClass方法定義對應class實例。(注意super與getParent可是有很大的不同)
*/
clazz = super.findClass(name);
} catch(ClassNotFoundException cnfe) {
// Ignore - will search internal repositories next
} catch(AccessControlException ace) {
log.warn("WebappClassLoaderBase.findClassInternal(" + name
+ ") security exception: " + ace.getMessage(), ace);
throw new ClassNotFoundException(name, ace);
} catch (RuntimeException e) {
if (log.isTraceEnabled())
log.trace(" -->RuntimeException Rethrown", e);
throw e;
}
}
if ((clazz == null)) { //父類無法加載該類
try {
/*加載當前web應用所有資源路徑下的類字節文件,就是處理ResourceEnty,path,class等等東西
*最后也都是在找到class二進制字節流后,使用defineClass方法來定義class實例。
*/
clazz = findClassInternal(name);
} catch(ClassNotFoundException cnfe) {
if (!hasExternalRepositories || searchExternalFirst) {
throw cnfe;
}
} catch(AccessControlException ace) {
log.warn("WebappClassLoaderBase.findClassInternal(" + name
+ ") security exception: " + ace.getMessage(), ace);
throw new ClassNotFoundException(name, ace);
} catch (RuntimeException e) {
if (log.isTraceEnabled())
log.trace(" -->RuntimeException Rethrown", e);
throw e;
}
}
if ((clazz == null) && hasExternalRepositories && !searchExternalFirst) {
try {
clazz = super.findClass(name);
} catch(AccessControlException ace) {
log.warn("WebappClassLoaderBase.findClassInternal(" + name
+ ") security exception: " + ace.getMessage(), ace);
throw new ClassNotFoundException(name, ace);
} catch (RuntimeException e) {
if (log.isTraceEnabled())
log.trace(" -->RuntimeException Rethrown", e);
throw e;
}
}
if (clazz == null) {
if (log.isDebugEnabled())
log.debug(" --> Returning ClassNotFoundException");
throw new ClassNotFoundException(name);
}
} catch (ClassNotFoundException e) {
if (log.isTraceEnabled())
log.trace(" --> Passing on ClassNotFoundException");
throw e;
}
return (clazz);
}
其實,總的來說呢,tomcat在啟動時候,就會大致創建以下幾種類加載器:
-
Bootstrap類加載器
:
加載JVM啟動所需的類,以及標準擴展類(位于jre/lib/ext下) -
System tomcat系統類加載器
:
加載tomcat啟動的類,比如bootstrap.jar,tomcat-guli.jar。通常在catalina.bat或者catalina.sh中指定。位于CATALINA_HOME/bin下。 -
Common tomcat服務通用類加載器
:
加載tomcat自身使用以及部署在其上面的應用程序通用的一些類,加載路徑可以在catalina.properties中common.loader屬性配置。 -
WebappClassLoader tomcat應用類加載器
:
每個應用在部署后,都會創建一個唯一的類加載器。該類加載器會加載位于 WEB-INF/lib下的jar文件中的class 和 WEB-INF/classes下的class文件。
當在應用程序中,需要使用某個類的時候,tomcat的類加載器加載類的過程如下:(在默認未設置委托情況下)
Bootstrap(包含JVM的bootstrapClassLoader,extClassLoader加載的所有基礎的,依賴的類)
--->/WEB-INF/Classes/*.class
--->/WEB-INF/lib/*.jar
---> System tomcat(與JVM中的System類加載器appClassLoader不同)加載的相關類集合 (%CATALINA_HOME%\bin\catalina.bat)
---> Common加載的相關類集合(%CATALINA_HOME%\conf\catalina.properties)
不同web應用間的類共享與隔離?
在講完了tomcat的類加載器相關知識,就來說說如何利用類加載器的加載機制以及查找類路徑特點,如何將不同的類在不同的應用程序之間共享或者隔離?。
其實隔離的話,不同的tomcat上應用的類在編譯部署之后都會在/WEB-INF/classes/目錄下,和/WEB-INF/lib/目錄下的jar庫一樣,兩者在天生設計上就是屬于每個單個應用自己可見,其他應用不可見的。
除了這兩個目錄下處于隔離的類庫,我們主要還是要看看如何在同一個tomcat實例上,不同的應用如何共享一些公共的類庫?
如何讓tomcat多實例之間使用共享的類庫?。主要就是看看catalina.properties文件,該文件位于$CATALINA_HOME|BASE/conf
。catalina.properties配置文件里面對于類庫的定義細節。
#
#
# List of comma-separated paths defining the contents of the "common"
# classloader. Prefixes should be used to define what is the repository type.
# Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute.
# If left as blank,the JVM system loader will be used as Catalina's "common"
# loader.
# Examples:
# "foo": Add this folder as a class repository
# "foo/*.jar": Add all the JARs of the specified folder as class
# repositories
# "foo/bar.jar": Add bar.jar as a class repository
common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar
#
# List of comma-separated paths defining the contents of the "server"
# classloader. Prefixes should be used to define what is the repository type.
# Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute.
# If left as blank, the "common" loader will be used as Catalina's "server"
# loader.
server.loader=
#
# List of comma-separated paths defining the contents of the "shared"
# classloader. Prefixes should be used to define what is the repository type.
# Path may be relative to the CATALINA_BASE path or absolute. If left as blank,
# the "common" loader will be used as Catalina's "shared" loader.
# Please note that for single jars, e.g. bar.jar, you need the URL form
# starting with file:.
shared.loader=
可以看見,有三種類加載器可以設置,默認三者配置都是相同的,即都與common.loader的key值相同。關于三者具體代表什么,可以參考此篇文章Tomcat ClassLoader機制介紹,
common.loader加載器是server.loader,shared.loader兩個類加載器的父類加載器,可以通過$CATALINA_HOME/bin/bootstrap.jar中可以看出:
public final class Bootstrap
{
...
protected ClassLoader commonLoader = null;
protected ClassLoader catalinaLoader = null;
protected ClassLoader sharedLoader = null;
private void initClassLoaders()
{
try
{
this.commonLoader = createClassLoader("common", null);
if (this.commonLoader == null)
{
this.commonLoader = getClass().getClassLoader();
}
this.catalinaLoader = createClassLoader("server", this.commonLoader);
this.sharedLoader = createClassLoader("shared", this.commonLoader);
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
...
}
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception
{
...
}
...
}
那這三個類加載器對我們應用之間的類庫之間共享有什么作用?又該如何使用呢?
通常來說,common.loader
和server.loader
都是tomcat服務器自身級別上面的類庫設置,我們在日常開發中使用不是太多。若是我們任意修改這兩個文件,對于不同實例或者同實例不同應用程序之間都會有或多或少的影響,如版本沖突,加載不到類等。其實關于多實例的這三個類加載器的使用,自己也還沒有實踐過,也不能妄下定論。日后經過實踐之后,再來總結和修改。但是,單實例tomcat上多項目共享類庫倒是可以通過shared.loader來配置,也有不少好處。
若是我們在單個tomcat上(catalina_home與catalina_base相同),部署著多個web項目,我們想設置一些對于所有web項目都可見,可訪問,即共享。我們可以catalina.properties的shared.loader配置:
shared.loader=shared.loader=${catalina.base}/shared/lib,${catalina.base}/shared/lib/A.jar,b.jar,*.jar...
這就需要我們在在$catalina_home|base目錄下創建shared目錄或者是shared/lib目錄,并將所有想要共享的jar庫包,添加到該目錄下。那么每個應用都能加載獲取到該位置的class。在類加載器:common class loader ---> server class loader ---> shared class loader
過程,最后可以獲取到指定的共享類。這個路徑既可以是相對路徑,也可以是絕對路徑,只要路徑中URL指向的jar庫能獲取到class字節碼即可。(其實也可以在common.loader上配置,但是在common上配置,感覺侵入式的力度大了些。<( ̄︶ ̄)> )
共享類庫的好處:
- 可以避免各個web項目之間重復加載相同的jar包,對JVM中存放class信息的內存區壓力增大。這些沒必要的開銷可以減少。
- 也可以提高tomcat的啟動速度,因為減少每個web項目重復加載jar的時間,tomcat的reload速度得到提升。
....
好了,自己對tomcat的類加載器的理解寫下來,但是自己對tomcat的理解還不夠深,理解會有偏差。該片文章主要用于自己記錄筆記并且供日后修改。僅供參考。