前言
Tomcat遵循J2EE規(guī)范,實(shí)現(xiàn)了Web容器。很多有關(guān)web的書籍和文章都離不開對Tomcat的分析,初學(xué)者可以從Tomcat的實(shí)現(xiàn)對J2EE有更深入的了解。此外,Tomcat還根據(jù)Java虛擬機(jī)規(guī)范實(shí)現(xiàn)了經(jīng)典的雙親委派模式的類加載體系。本文基于Tomcat7.0的Java源碼,對其類加載體系進(jìn)行分析。
概述
首先簡單介紹下Java虛擬機(jī)規(guī)范中提到的主要類加載器;
- Bootstrap Loader:啟動類加載器主要加載的是JVM自身需要的類,這個類加載使用C++語言實(shí)現(xiàn)的,是虛擬機(jī)自身的一部分,它負(fù)責(zé)將 <JAVA_HOME>/lib路徑下的核心類庫或-Xbootclasspath參數(shù)指定的路徑下的jar包加載到內(nèi)存中,注意必由于虛擬機(jī)是按照文件名識別加載jar包的,如rt.jar,如果文件名不被虛擬機(jī)識別,即使把jar包丟到lib目錄下也是沒有作用的(出于安全考慮,Bootstrap啟動類加載器只加載包名為java、javax、sun等開頭的類)。
- Extended Loader:擴(kuò)展類加載器是指Sun公司(已被Oracle收購)實(shí)現(xiàn)的sun.misc.Launcher$ExtClassLoader類,由Java語言實(shí)現(xiàn)的,是Launcher的靜態(tài)內(nèi)部類,它負(fù)責(zé)加載<JAVA_HOME>/lib/ext目錄下或者由系統(tǒng)變量-Djava.ext.dir指定位路徑中的類庫,開發(fā)者可以直接使用標(biāo)準(zhǔn)擴(kuò)展類加載器。
- AppClass Loader: 也稱應(yīng)用程序加載器是指 Sun公司實(shí)現(xiàn)的sun.misc.Launcher$AppClassLoader。它負(fù)責(zé)加載系統(tǒng)類路徑j(luò)ava -classpath或-D java.class.path 指定路徑下的類庫,也就是我們經(jīng)常用到的classpath路徑,開發(fā)者可以直接使用系統(tǒng)類加載器,一般情況下該類加載是程序中默認(rèn)的類加載器,通過ClassLoader#getSystemClassLoader()方法可以獲取到該類加載器。
根據(jù)java虛擬機(jī)的雙親委派模式的原則,類加載器在加載一個類時,首先交給父類加載器加載,層層往上直到Bootstrap Loader。也就是一個類最先由Bootstrap Loader加載,如果沒有加載到,則交給下一層的類加載器加載,如果沒有加載到,則依次層層往下,直到最下層的類加載器。這也就是說,凡是能通過父一級類加載器加載到的類,對于子類也是可見的。因此可以利用雙親委派模式的特性,使用類加載器對不同路徑下的jar包或者類進(jìn)行環(huán)境隔離。
然后用一張圖片來展示Tomcat的類加載體系:
這里結(jié)合之前對雙親委派模式的類加載過程的描述,對上圖所示類加載體系進(jìn)行介紹:
ClassLoader:Java提供的類加載器抽象類,用戶自定義的類加載器需要繼承實(shí)現(xiàn)
commonLoader:Tomcat最基本的類加載器,加載路徑中的class可以被Tomcat容器本身以及各個Webapp訪問;
catalinaLoader:Tomcat容器私有的類加載器,加載路徑中的class對于Webapp不可見;
sharedLoader:各個Webapp共享的類加載器,加載路徑中的class對于所有Webapp可見,但是對于Tomcat容器不可見;
WebappClassLoader:各個Webapp私有的類加載器,加載路徑中的class只對當(dāng)前Webapp可見;
源碼分析
commonLoader、catalinaLoader和sharedLoader在Tomcat容器初始化的一開始,即調(diào)用Bootstrap的init方法時創(chuàng)建。catalinaLoader會被設(shè)置為Tomcat主線程的線程上下文類加載器,并且使用catalinaLoader加載Tomcat容器自身容器下的class。Bootstrap的init方法的部分代碼見代碼清單1。
代碼清單1 Bootstrap的init方法的部分實(shí)現(xiàn) :
/**
* Initialize daemon.
*/
public void init()
throws Exception
{
// Set Catalina path
setCatalinaHome();
setCatalinaBase();
initClassLoaders();
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
// 省略后邊的代碼
代碼清單1中,我們首先關(guān)注initClassLoaders方法的實(shí)現(xiàn),見代碼清單2.initClassLoaders方法用來初始化commonLoader、catalinaLoader、sharedLoader。
代碼清單2 initClassLoaders方法的實(shí)現(xiàn):
private void initClassLoaders() {
try {
commonLoader = createClassLoader("common", null);
if( commonLoader == null ) {
// no config file, default to this loader - we might be in a 'single' env.
commonLoader=this.getClass().getClassLoader();
}
catalinaLoader = createClassLoader("server", commonLoader);
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}
從代碼清單2中看到創(chuàng)建類加載器是通過調(diào)用createClassLoader方法實(shí)現(xiàn)的,createClassLoader的實(shí)現(xiàn)見代碼清單3.
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals("")))
return parent;
ArrayList<String> repositoryLocations = new ArrayList<String>();
ArrayList<Integer> repositoryTypes = new ArrayList<Integer>();
int i;
StringTokenizer tokenizer = new StringTokenizer(value, ",");
while (tokenizer.hasMoreElements()) {
String repository = tokenizer.nextToken();
// Local repository
boolean replace = false;
String before = repository;
while ((i=repository.indexOf(CATALINA_HOME_TOKEN))>=0) {
replace=true;
if (i>0) {
repository = repository.substring(0,i) + getCatalinaHome()
+ repository.substring(i+CATALINA_HOME_TOKEN.length());
} else {
repository = getCatalinaHome()
+ repository.substring(CATALINA_HOME_TOKEN.length());
}
}
while ((i=repository.indexOf(CATALINA_BASE_TOKEN))>=0) {
replace=true;
if (i>0) {
repository = repository.substring(0,i) + getCatalinaBase()
+ repository.substring(i+CATALINA_BASE_TOKEN.length());
} else {
repository = getCatalinaBase()
+ repository.substring(CATALINA_BASE_TOKEN.length());
}
}
if (replace && log.isDebugEnabled())
log.debug("Expanded " + before + " to " + repository);
// Check for a JAR URL repository
try {
new URL(repository);
repositoryLocations.add(repository);
repositoryTypes.add(ClassLoaderFactory.IS_URL);
continue;
} catch (MalformedURLException e) {
// Ignore
}
if (repository.endsWith("*.jar")) {
repository = repository.substring
(0, repository.length() - "*.jar".length());
repositoryLocations.add(repository);
repositoryTypes.add(ClassLoaderFactory.IS_GLOB);
} else if (repository.endsWith(".jar")) {
repositoryLocations.add(repository);
repositoryTypes.add(ClassLoaderFactory.IS_JAR);
} else {
repositoryLocations.add(repository);
repositoryTypes.add(ClassLoaderFactory.IS_DIR);
}
}
String[] locations = repositoryLocations.toArray(new String[0]);
Integer[] types = repositoryTypes.toArray(new Integer[0]);
ClassLoader classLoader = ClassLoaderFactory.createClassLoader
(locations, types, parent);
// Retrieving MBean server
MBeanServer mBeanServer = null;
if (MBeanServerFactory.findMBeanServer(null).size() > 0) {
mBeanServer = MBeanServerFactory.findMBeanServer(null).get(0);
} else {
mBeanServer = ManagementFactory.getPlatformMBeanServer();
}
// Register the server classloader
ObjectName objectName =
new ObjectName("Catalina:type=ServerClassLoader,name=" + name);
mBeanServer.registerMBean(classLoader, objectName);
return classLoader;
}
createClassLoader方法的執(zhí)行步驟如下:
- 獲取各個類加載器相應(yīng)的資源配置文件(分別為common.loader、server.loader、shared.loader),從中獲取類資源路徑的配置信息;
- 解析類資源路徑下的各個資源位置和類型,也包括對jar資源的檢查;
- 調(diào)用ClassLoaderFactory.createClassLoader(locations, types, parent)方法創(chuàng)建ClassLoader;
- 將ClassLoader注冊到JMX服務(wù)中。
我們回頭看看代碼清單1中的SecurityClassLoad.securityClassLoad(catalinaLoader)的實(shí)現(xiàn),見代碼清單4.這說明加載Tomcat容器本身的類資源的確是使用catalinaLoader來完成的。
代碼清單4 securityClassLoad的實(shí)現(xiàn):
public static void securityClassLoad(ClassLoader loader)
throws Exception {
if( System.getSecurityManager() == null ){
return;
}
loadCorePackage(loader);
loadLoaderPackage(loader);
loadSessionPackage(loader);
loadUtilPackage(loader);
loadJavaxPackage(loader);
loadCoyotePackage(loader);
loadTomcatPackage(loader);
}
securityClassLoad方法主要加載Tomcat容器所需的class,包括:
Tomcat核心class,即org.apache.catalina.core路徑下的class;
org.apache.catalina.loader.WebappClassLoader$PrivilegedFindResourceByName;
Tomcat有關(guān)session的class,即org.apache.catalina.session路徑下的class;
Tomcat工具類的class,即org.apache.catalina.util路徑下的class;
javax.servlet.http.Cookie;
Tomcat處理請求的class,即org.apache.catalina.connector路徑下的class;
Tomcat其它工具類的class,也是org.apache.catalina.util路徑下的class;
我們以加載Tomcat核心class的loadCorePackage方法為例,其實(shí)現(xiàn)見代碼清單5所示。
代碼清單5 loadCorePackage的實(shí)現(xiàn):
private final static void loadCorePackage(ClassLoader loader)
throws Exception {
String basePackage = "org.apache.catalina.";
loader.loadClass
(basePackage +
"core.ApplicationContextFacade$1");
loader.loadClass
(basePackage +
"core.ApplicationDispatcher$PrivilegedForward");
loader.loadClass
(basePackage +
"core.ApplicationDispatcher$PrivilegedInclude");
loader.loadClass
(basePackage +
"core.AsyncContextImpl");
loader.loadClass
(basePackage +
"core.AsyncContextImpl$AsyncState");
loader.loadClass
(basePackage +
"core.AsyncContextImpl$DebugException");
loader.loadClass
(basePackage +
"core.AsyncContextImpl$1");
loader.loadClass
(basePackage +
"core.AsyncContextImpl$2");
loader.loadClass
(basePackage +
"core.AsyncListenerWrapper");
loader.loadClass
(basePackage +
"core.ContainerBase$PrivilegedAddChild");
loader.loadClass
(basePackage +
"core.DefaultInstanceManager$1");
loader.loadClass
(basePackage +
"core.DefaultInstanceManager$2");
loader.loadClass
(basePackage +
"core.DefaultInstanceManager$3");
loader.loadClass
(basePackage +
"core.DefaultInstanceManager$4");
loader.loadClass
(basePackage +
"core.DefaultInstanceManager$5");
loader.loadClass
(basePackage +
"core.ApplicationHttpRequest$AttributeNamesEnumerator");
}
至此,有關(guān)commonLoader、catalinaLoader和sharedLoader三個類加載器的初始化以及使用catalinaLoader加載Tomcat容器自身類資源的內(nèi)容已經(jīng)介紹完了,WebappClassLoader下次補(bǔ)上。