在研究Tomcat之前,一般是借用現(xiàn)有的UML工具分析Tomcat整體結(jié)構(gòu),但要分析Tomcat的流程就必須從分析Tomcat的StartUp入手。Tomcat的啟動(dòng)是從解析bat文件開始,bat文件最終調(diào)用org.apache.catalina.startup.Bootstrap開始類的加載。
一.Tomcat的ClassLoader:
TOMCAT自己的類載入器(ClassLoader)
+---------------------------+
| Bootstrap |
| | |
| System |
| | |
| Common |
| / \ |
| Catalina Shared |
+---------------------------+
其中:
- Bootstrap - 載入JVM自帶的類和$JAVA_HOME/jre/lib/ext/*.jar
- System
1.載入$CATALINA_HOME/bin/bootstrap.jar 初始化Tomcat,執(zhí)行Main方法
2. $JAVA_HOME/lib/tools.jar Sun的工具類,包括編譯Jsp為Servlet的工具類
- Common 這個(gè)目錄下的類雖然對(duì)TOMCAT和所有的WEB APP都可見.但是Web App的類不應(yīng)該
放在這個(gè)目錄下,所有未打包的Class都在$CATALINA_HOME/common/classes下,所
有打包的jar都在
$CATALINA_HOME/commons/endorsed和$CATALINA_HOME/common/lib下,默認(rèn)情況會(huì)
包含以下幾個(gè)包:
1. jndi.jar JNDI API接口,這個(gè)包僅在Java1.2時(shí)候裝入,1.3以后的版本JDK已
自動(dòng)裝入.
2. naming-common.jar JNDI接口實(shí)現(xiàn)類,Tomcat用這些類在內(nèi)存中使用Context.
3. naming-resources.jar JNDI實(shí)現(xiàn),Tomcat用它們定位Web App的靜態(tài)資源.
4. servlet.jar Servlet,Jsp API
5. xerces.jar XML解析器,特定的Web App可以在自己的/WEB-INF/lib 中覆蓋.
- Catalina 裝入Tomcat實(shí)現(xiàn)所有接口的類,這些類對(duì)Web App是完全不可見的,所有未打包的類在
$CATALINA_HOME/server/classes所有jar包在$CATALINA_HOME/server/lib下.一
般情況該ClassLoader將Load下面幾個(gè)包:
1. catalina.jar Servlet容器的Tomcat實(shí)現(xiàn)包
2. jakarta-regexp-X.Y.jar 正則表達(dá)式,請(qǐng)求過濾時(shí)使用
3. servlets-xxxxx.jar Servlet支持包
4. tomcat-coyote.jar Tomcat的Coyote連接實(shí)現(xiàn)包
5. tomcat-jk.jar Web Server綁定包,允許Tomcat綁定Apache等作為Web Server
6. tomcat-jk2.jar 功能同上
7. tomcat-util.jar Tomcat工具類,可能被一些Connector用到
8. tomcat-warp.jar 用于Apache Server包
- Shared 載入所有WEB APP都可見的類,對(duì)TOMCAT不可見. 所有未打包的類在
$CATALINA_HOME/shared/classes所有jar包在$CATALINA_HOME /lib下.
默認(rèn)情況包含下面幾個(gè)包:
1. jasper-compiler.jar Jsp編譯器,編譯Jsp為Servlet
2. jasper-runtime.jar Jsp(已編譯成Servlet)運(yùn)行支持包
3. naming-factory.jar 支持Web App使用JNDI的封裝包
-WebAppX Web App ClassLoader,當(dāng)Web App被部署是該ClassLoader被創(chuàng)建.所有class都在
WEB-INF/classes下,所有jar在WEB-INF/lib下.
特別注意WEB APP自己的ClassLoader的實(shí)現(xiàn)與眾不同:
它先試圖從WEB APP自己的目錄里載入,如果失敗則請(qǐng)求父ClassLoader的代理
這樣可以讓不同的WEB APP之間的類載入互不干擾.另,Tomcat Server使用的是Catalina
ClassLoader,一般的Web App使用的是WebApp ClassLoader.
二. org.apache.catalina.startup.Bootstrap
該類是Tomcat的執(zhí)行入口點(diǎn),我們著重分析下面兩個(gè)方法:
1. initClassLoaders,創(chuàng)建ClassLoader層次.
private void initClassLoaders() {
try {
ClassLoaderFactory.setDebug(debug);
//創(chuàng)建common ClassLoader,沒有父ClassLoader
commonLoader = createClassLoader("common", null);
//創(chuàng)建catalina ClassLoader,父ClassLoader為common
catalinaLoader = createClassLoader("server", commonLoader);
//創(chuàng)建shared ClassLoader, 父ClassLoader為common
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
log("Class loader creation threw exception", t);
System.exit(1);
}
}
2. createClassLoader,負(fù)責(zé)具體的創(chuàng)建工作
在$CATALINA_HOME/conf/catalina.properties中定義了common, server, shared
ClassLoader載入類的路徑及一些包的安全權(quán)限.
//common載入類的路徑
common.loader=${catalina.home}/common/classes,
${catalina.home}/common/endorsed/*.jar,${catalina.home}/common/lib/*.jar
//server載入類的路徑
server.loader=${catalina.home}/server/classes,
${catalina.home}/server/lib/*.jar
//shared載入類的路徑
shared.loader=${catalina.base}/shared/classes,
${catalina.base}/shared/lib/*.jar
/**
*param name:Load Name
*param parent:父Loader
*classLoader的資源分三種:
*1.未打包的classes,一般是一個(gè)目錄
*2.打包的jar目錄
*3.網(wǎng)絡(luò)資源,一般是網(wǎng)上的一個(gè)jar包 (Applet經(jīng)常用到這樣的loader)
*/
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
//從catalina.properties中取得改Loader的配置信息
String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals("")))
return parent;
//classes目錄
ArrayList unpackedList = new ArrayList();
//jar目錄
ArrayList packedList = new ArrayList();
//網(wǎng)絡(luò)路徑指定的包
ArrayList urlList = new ArrayList();
StringTokenizer tokenizer = new StringTokenizer(value, ",");
//當(dāng)前Loader該裝載的類
while (tokenizer.hasMoreElements()) {
String repository = tokenizer.nextToken();
// Check for a JAR URL repository
try {
//如果是網(wǎng)絡(luò)路徑追加url
urlList.add(new URL(repository));
continue;
} catch (MalformedURLException e) {
// Ignore
}
// 本地路徑
boolean packed = false;
//${catalina.home}
if (repository.startsWith(CATALINA_HOME_TOKEN)) {
repository = getCatalinaHome()
+ repository.substring(CATALINA_HOME_TOKEN.length());
//${catalina.base}
} else if (repository.startsWith(CATALINA_BASE_TOKEN)) {
repository = getCatalinaBase()
+ repository.substring(CATALINA_BASE_TOKEN.length());
}
/**經(jīng)過上述操作,把catalina.properties里的路徑替換成絕對(duì)路徑*/
//如果是jar文件路徑
if (repository.endsWith("*.jar")) {
packed = true;
repository = repository.substring
(0, repository.length() - "*.jar".length());
}
if (packed) {
packedList.add(new File(repository));
} else {
unpackedList.add(new File(repository));
}
}
File[] unpacked = (File[]) unpackedList.toArray(new File[0]);
File[] packed = (File[]) packedList.toArray(new File[0]);
URL[] urls = (URL[]) urlList.toArray(new URL[0]);
//調(diào)用Factory的方法創(chuàng)建ClassLoader
return ClassLoaderFactory.createClassLoader
(unpacked, packed, urls, parent);
}
三. ClassLoaderFactory
ClassLoaderFactory是用于創(chuàng)建ClassLoader的工廠類,這個(gè)類比較簡(jiǎn)單.
//參數(shù)含義不再說明,參看上面的分析
public static ClassLoader createClassLoader(File unpacked[],
File packed[],
URL urls[],
ClassLoader parent)
throws Exception {
if (debug >= 1)
log("Creating new class loader");
// Construct the "class path" for this class loader
ArrayList list = new ArrayList();
// 通過class目錄構(gòu)造file協(xié)議的url,并追加的list
if (unpacked != null) {
for (int i = 0; i < unpacked.length; i++) {
File file = unpacked[i];
if (!file.exists() || !file.canRead())
continue;
if (debug >= 1)
log(" Including directory or JAR "
+ file.getAbsolutePath());
URL url = new URL("file", null,
file.getCanonicalPath() + File.separator);
list.add(url.toString());
}
}
//取出所有jar目錄里的jar文件,逐一構(gòu)造url,并追加的list
if (packed != null) {
for (int i = 0; i < packed.length; i++) {
File directory = packed[i];
if (!directory.isDirectory() || !directory.exists() ||
!directory.canRead())
continue;
String filenames[] = directory.list();
for (int j = 0; j < filenames.length; j++) {
String filename = filenames[j].toLowerCase();
if (!filename.endsWith(".jar"))
continue;
File file = new File(directory, filenames[j]);
if (debug >= 1)
log(" Including jar file " + file.getAbsolutePath());
URL url = new URL("file", null,
file.getCanonicalPath());
list.add(url.toString());
}
}
}
//追加網(wǎng)絡(luò)路徑的資源
if (urls != null) {
for (int i = 0; i < urls.length; i++) {
list.add(urls[i].toString());
}
}
//調(diào)用StandardClassLoader創(chuàng)建實(shí)際的Loader
String array[] = (String[]) list.toArray(new String[list.size()]);
StandardClassLoader classLoader = null;
if (parent == null)
classLoader = new StandardClassLoader(array);
else
classLoader = new StandardClassLoader(array, parent);
classLoader.setDelegate(true);
return (classLoader);
}
四. StandardClassLoader
StandardClassLoader繼承了URLClassLoader, URLClassLoader類具有從硬盤目錄裝載類,或從本地或遠(yuǎn)程裝載jar文件的能力.這個(gè)類也實(shí)現(xiàn)了Reloader接口,提供了自動(dòng)重新裝載類的功能.我們主要看這個(gè)類以下及個(gè)方法:
1. 構(gòu)造函數(shù)StandardClassLoader
/**
* @param repositories url數(shù)組,見上分析
* @param parent 父loader
*/
public StandardClassLoader(String repositories[], ClassLoader parent) {
//調(diào)用父類的構(gòu)造函數(shù)
//父類的構(gòu)造函數(shù)將用轉(zhuǎn)換后的repositories生成URLClassPath的實(shí)例
//ucp是類成員變量ucp=new URLClassPath(urls);
// URLClassPath是sun的擴(kuò)展包,無法繼續(xù)跟蹤
super(convert(repositories), parent);
this.parent = parent;
this.system = getSystemClassLoader();
securityManager = System.getSecurityManager();
if (repositories != null) {
for (int i = 0; i < repositories.length; i++)
//處理url
addRepositoryInternal(repositories[i]);
}
}
2. addRepositoryInternal
/**
* @param repository 要處理的url
*/
protected void addRepositoryInternal(String repository) {
URLStreamHandler streamHandler = null;
String protocol = parseProtocol(repository);
if (factory != null)
streamHandler = factory.createURLStreamHandler(protocol);
// 當(dāng)前url是指向本地或網(wǎng)路的jar文件,驗(yàn)證jar的正確性
//下面的代碼看似無用其實(shí)是在驗(yàn)證jar文件的正確性,如果jar文件錯(cuò)誤拋異常中止執(zhí)行.
if (!repository.endsWith(File.separator) && !repository.endsWith("/")) {
JarFile jarFile = null;
try {
Manifest manifest = null;
//jar協(xié)議
if (repository.startsWith("jar:")) {
URL url = new URL(null, repository, streamHandler);
JarURLConnection conn =
(JarURLConnection) url.openConnection();
conn.setAllowUserInteraction(false);
conn.setDoInput(true);
conn.setDoOutput(false);
conn.connect();
jarFile = conn.getJarFile();
//file協(xié)議
} else if (repository.startsWith("file://")) {
jarFile = new JarFile(repository.substring(7));
//file
} else if (repository.startsWith("file:")) {
jarFile = new JarFile(repository.substring(5));
//本地路徑的jar文件
} else if (repository.endsWith(".jar")) {
URL url = new URL(null, repository, streamHandler);
URLConnection conn = url.openConnection();
JarInputStream jis =
new JarInputStream(conn.getInputStream());
manifest = jis.getManifest();
//其他情況均為錯(cuò)誤
} else {
throw new IllegalArgumentException
("addRepositoryInternal: Invalid URL '" +
repository + "'");
&