前言
Tomcat遵循J2EE規(guī)范粥血,實現(xiàn)了Web容器湿滓。很多有關web的書籍和文章都離不開對Tomcat的分析诗良,初學者可以從Tomcat的實現(xiàn)對J2EE有更深入的了解糠亩。此外虐骑,Tomcat還根據(jù)Java虛擬機規(guī)范實現(xiàn)了經典的雙親委派模式的類加載體系。本文基于Tomcat7.0的Java源碼赎线,對其類加載體系進行分析廷没。
概述
首先簡單介紹下Java虛擬機規(guī)范中提到的主要類加載器;
- Bootstrap Loader:啟動類加載器主要加載的是JVM自身需要的類垂寥,這個類加載使用C++語言實現(xiàn)的颠黎,是虛擬機自身的一部分,它負責將 <JAVA_HOME>/lib路徑下的核心類庫或-Xbootclasspath參數(shù)指定的路徑下的jar包加載到內存中滞项,注意必由于虛擬機是按照文件名識別加載jar包的狭归,如rt.jar,如果文件名不被虛擬機識別蓖扑,即使把jar包丟到lib目錄下也是沒有作用的(出于安全考慮唉铜,Bootstrap啟動類加載器只加載包名為java、javax律杠、sun等開頭的類)潭流。
- Extended Loader:擴展類加載器是指Sun公司(已被Oracle收購)實現(xiàn)的sun.misc.Launcher$ExtClassLoader類,由Java語言實現(xiàn)的柜去,是Launcher的靜態(tài)內部類灰嫉,它負責加載<JAVA_HOME>/lib/ext目錄下或者由系統(tǒng)變量-Djava.ext.dir指定位路徑中的類庫,開發(fā)者可以直接使用標準擴展類加載器嗓奢。
- AppClass Loader: 也稱應用程序加載器是指 Sun公司實現(xiàn)的sun.misc.Launcher$AppClassLoader讼撒。它負責加載系統(tǒng)類路徑java -classpath或-D java.class.path 指定路徑下的類庫,也就是我們經常用到的classpath路徑,開發(fā)者可以直接使用系統(tǒng)類加載器根盒,一般情況下該類加載是程序中默認的類加載器钳幅,通過ClassLoader#getSystemClassLoader()方法可以獲取到該類加載器。
根據(jù)java虛擬機的雙親委派模式的原則炎滞,類加載器在加載一個類時敢艰,首先交給父類加載器加載,層層往上直到Bootstrap Loader册赛。也就是一個類最先由Bootstrap Loader加載钠导,如果沒有加載到,則交給下一層的類加載器加載森瘪,如果沒有加載到牡属,則依次層層往下,直到最下層的類加載器扼睬。這也就是說逮栅,凡是能通過父一級類加載器加載到的類,對于子類也是可見的痰驱。因此可以利用雙親委派模式的特性证芭,使用類加載器對不同路徑下的jar包或者類進行環(huán)境隔離。
然后用一張圖片來展示Tomcat的類加載體系:
這里結合之前對雙親委派模式的類加載過程的描述担映,對上圖所示類加載體系進行介紹:
ClassLoader:Java提供的類加載器抽象類废士,用戶自定義的類加載器需要繼承實現(xiàn)
commonLoader:Tomcat最基本的類加載器,加載路徑中的class可以被Tomcat容器本身以及各個Webapp訪問蝇完;
catalinaLoader:Tomcat容器私有的類加載器官硝,加載路徑中的class對于Webapp不可見;
sharedLoader:各個Webapp共享的類加載器短蜕,加載路徑中的class對于所有Webapp可見氢架,但是對于Tomcat容器不可見;
WebappClassLoader:各個Webapp私有的類加載器朋魔,加載路徑中的class只對當前Webapp可見岖研;
源碼分析
commonLoader、catalinaLoader和sharedLoader在Tomcat容器初始化的一開始警检,即調用Bootstrap的init方法時創(chuàng)建孙援。catalinaLoader會被設置為Tomcat主線程的線程上下文類加載器,并且使用catalinaLoader加載Tomcat容器自身容器下的class扇雕。Bootstrap的init方法的部分代碼見代碼清單1拓售。
代碼清單1 Bootstrap的init方法的部分實現(xiàn) :
/**
* Initialize daemon.
*/
public void init()
throws Exception
{
// Set Catalina path
setCatalinaHome();
setCatalinaBase();
initClassLoaders();
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
// 省略后邊的代碼
代碼清單1中,我們首先關注initClassLoaders方法的實現(xiàn)镶奉,見代碼清單2.initClassLoaders方法用來初始化commonLoader础淤、catalinaLoader崭放、sharedLoader。
代碼清單2 initClassLoaders方法的實現(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)建類加載器是通過調用createClassLoader方法實現(xiàn)的鸽凶,createClassLoader的實現(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í)行步驟如下:
- 獲取各個類加載器相應的資源配置文件(分別為common.loader币砂、server.loader、shared.loader)玻侥,從中獲取類資源路徑的配置信息道伟;
- 解析類資源路徑下的各個資源位置和類型,也包括對jar資源的檢查使碾;
- 調用ClassLoaderFactory.createClassLoader(locations, types, parent)方法創(chuàng)建ClassLoader;
- 將ClassLoader注冊到JMX服務中祝懂。
我們回頭看看代碼清單1中的SecurityClassLoad.securityClassLoad(catalinaLoader)的實現(xiàn)票摇,見代碼清單4.這說明加載Tomcat容器本身的類資源的確是使用catalinaLoader來完成的。
代碼清單4 securityClassLoad的實現(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有關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方法為例耗溜,其實現(xiàn)見代碼清單5所示。
代碼清單5 loadCorePackage的實現(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");
}
至此省容,有關commonLoader抖拴、catalinaLoader和sharedLoader三個類加載器的初始化以及使用catalinaLoader加載Tomcat容器自身類資源的內容已經介紹完了,WebappClassLoader下次補上腥椒。