tomcat的啟動從bootstrap的main方法開始,在main方法中主要是做了三件事,調用init方法初始化自己,調用catalinaDaemon對象
的setAwait方法設定它的await屬性為true,最後調用自己的start方法。
首先看看init方法:
public void init()
throws Exception
{
// Set Catalina path
setCatalinaHome();
setCatalinaBase();
//初始化三個類加載器
initClassLoaders();
//線程構造後将從它的父線程中繼承相應的上下文類加載器. 如果在整個應用中你不做任何特殊設定,
//所有的線程将都以系統類加載器(system classloader)作為自己的線程上下文類加載器.
//這裡從Bootstrap構造的線程都會繼承改線程的上下文類加載器
Thread.currentThread().setContextClassLoader(catalinaLoader);
//加載conf下的catalina.properties配置檔案下的common.loader和server.loader所指定的類。因為server.loader為空時
//catalinaLoader和commonLoader是指向的同一對象,不為空時commonLoader是catalinaLoader的父類。
SecurityClassLoad.securityClassLoad(catalinaLoader);
// Load our startup class and call its process() method
if (log.isDebugEnabled())
log.debug("Loading startup class");
Class<?> startupClass =
catalinaLoader.loadClass
("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.newInstance();
// Set the shared extensions class loader
if (log.isDebugEnabled())
log.debug("Setting startup class properties");
String methodName = "setParentClassLoader";
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Class.forName("java.lang.ClassLoader");
Object paramValues[] = new Object[1];
paramValues[0] = sharedLoader;
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues);
catalinaDaemon = startupInstance;
}
這裡的主要内容就是調用了initClassLoaders();初始化了commonLoader,catalinaLoader,sharedLoader三個類加載器,它們是bootstrap的
成員變量。并通過SecurityClassLoad.securityClassLoad(catalinaLoader)這步代碼來加載conf/catalina.properties檔案下common.loader,
server.loader,shared.loade三個屬性指定的jar包和class。securityClassLoad會通過一系列環境變量替換等處理來找到相關的類并加載它們。
最後一段代碼初始化Catalina對象,再設定它的父類加載器為sharedLoader。這裡的一些疑問稍後再說。
先看看initClassLoaders()方法:
initClassLoaders源碼:
private void initClassLoaders() {
try {
commonLoader = createClassLoader("common", null);//1,建立commonLoader類加載器
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);//2,
sharedLoader = createClassLoader("shared", commonLoader);//3,
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}
第一步是通過common關鍵字初始化了一個commonLoader對象。
進到createClassLoader方法中:
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals("")))
return parent;
value = replace(value);
........
ClassLoader classLoader = ClassLoaderFactory.createClassLoader
(repositories, parent);
........
return classLoader;
}
這裡應該很清楚,CatalinaProperties.getProperty(name + ".loader");就是擷取配置檔案裡的屬性的方法。value = replace(value)替換環境變量
等一系列的轉換擷取了一些類路徑的url最後将這些url傳給ClassLoaderFactory.createClassLoader方法。CommonclassLoader和其他兩個類加載器都
是通過該方法擷取的,它們是一個StandardClassLoader執行個體。StandardClassLoader隻是簡單的從URLClassLoader繼承,并實作了
StandardClassLoaderMBean接口來實作JMX監控。是以可以認為這三個類加載器就是URLClassLoader對象,
createClassLoader(repositories, parent),其實就是調用了URLClassLoader的構造方法,傳入類路徑的URL集合和父加載器。如果打開
conf/catalina.properties配置檔案,server.loader,shared.loade預設為空。從createClassLoader方法中if判斷就可以知道
commonLoader == catalinaLoader == sharedLoader。
這裡先說一點設定這三個類加載器的用意,以後再用源碼證明。commonLoader被用來設定為catalinaLoader和sharedLoader的父加載器。
catalinaLoader用來加載tomcat自身程式所需要的類。sharedLoader用來加載一些webapp目錄下的程式共享的class,并被WebappClassLoader
(使用者web程式的類加載器)設定為父類(從這裡就可以知道web程式的類是怎麼實作互相隔離,但又可以共享某些jar包的。因為類加載器在加載
類時會先讓父加載器去加載,父加載器找不到的話就自己來加載,在這裡sharedLoader和WebappClassLoader都有不同的加載路徑,所有的
WebappClassLoader都繼承自sharedLoader,是以他們共享了sharedLoader的加載路徑)。
再來看看SecurityClassLoad.securityClassLoad(catalinaLoader):
public static void securityClassLoad(ClassLoader loader)
throws Exception {
if( System.getSecurityManager() == null ){
return;
}
loadCorePackage(loader);
loadCoyotePackage(loader);
loadLoaderPackage(loader);
loadRealmPackage(loader);
loadServletsPackage(loader);
loadSessionPackage(loader);
loadUtilPackage(loader);
loadValvesPackage(loader);
loadJavaxPackage(loader);
loadConnectorPackage(loader);
loadTomcatPackage(loader);
}
可以進每個方法看下,它是加載了指定的一些類,注意這些類名是指定的,如果你放了自己的jar包在裡面,那麼它的加載就不是catalinaLoader完成的。
最後就是初始化catalinaDaemon對象了,從最後一段代碼可以看出catalinaDaemon被指派為org.apache.catalina.startup.Catalina的執行個體,
并且通過反射調用調用了setParentClassLoader方法,參數是catalinaLoader。
這裡設定父加載器的用意是什麼?下面是這個方法的源碼
public void setParentClassLoader(ClassLoader parentClassLoader) {
this.parentClassLoader = parentClassLoader;
}
方法很簡單,看來問題不在這。在來看看Catalina的另一個方法:
public ClassLoader getParentClassLoader() {
if (parentClassLoader != null) {
return (parentClassLoader);
}
return ClassLoader.getSystemClassLoader();
}
其實這裡parentClassLoader屬性不是說加載Catalina的類加載器的父類加載器,它隻是一個普通的屬性。
在來看看哪裡調用了getParentClassLoader,在StandardServer中有這樣一個方法:
@Override
public ClassLoader getParentClassLoader() {
if (parentClassLoader != null)
return (parentClassLoader);
if (catalina != null) {
return (catalina.getParentClassLoader());
}
return (ClassLoader.getSystemClassLoader());
}
然後他的下級元件StandardService:
@Override
public ClassLoader getParentClassLoader() {
if (parentClassLoader != null)
return (parentClassLoader);
if (server != null) {
return (server.getParentClassLoader());
}
return (ClassLoader.getSystemClassLoader());
}
與之對應的都有一個setParentClassLoader方法。
engin容器也有這個方法。最後在WebappLoader中有個如下的方法:
private WebappClassLoader createClassLoader()
throws Exception {
Class<?> clazz = Class.forName(loaderClass);
WebappClassLoader classLoader = null;
if (parentClassLoader == null) {
parentClassLoader = container.getParentClassLoader();
}
Class<?>[] argTypes = { ClassLoader.class };
Object[] args = { parentClassLoader };
Constructor<?> constr = clazz.getConstructor(argTypes);
classLoader = (WebappClassLoader) constr.newInstance(args);
return classLoader;
}
在這個方法中調用了org.apache.catalina.loader.WebappClassLoader的構造方法将容器container的parentClassLoader的屬性最為classLoader的
父類加載器。這裡可以得出結論從 Server 》 service 》 engin(container) 》沒一個級别都在嘗試設定parentClassLoader的值,而parentClassLoader
将作為WebappClassLoader的父類加載器。也就是說parentClassLoader是針對WebappClassLoader而言的,而每一級别都在嘗試修改它。·
現在已經介紹完了bootstrap的init方法,下一步是來介紹setAwait(true)方法的作用。通過反射設定了Catalina的await屬性為true。Catalina成功啟動後
會通過檢查該值來調用它的成員變量server的await方法:
public void start() {
........
if (await) {
await();
stop();
}
}
public void await() {
getServer().await();
}
Catalina的成員變量server是一個StandardServer對象,通過調用該對象的await方法,會讓他一直監控server.xml配置檔案中的這段配置:
<Server port="8005" shutdown="SHUTDOWN">,所定義的端口發送過來的指令。
最後看看start方法:
public void start()
throws Exception {
if( catalinaDaemon==null ) init();
Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
method.invoke(catalinaDaemon, (Object [])null);
}
start最終通過反射調用了catalinaDaemon(也就是Catalina)的start方法。(這裡看到的多處位置是通過反射調用catalinaDaemon的方法,
因為catalinaDaemon是一個object的引用指向的Catalina對象所有它隻能調用object對象的方法,想要調用Catalina的方法隻能通過反射。)
總結:bootstrap的主要任務就是初始化commonLoader,catalinaLoader,sharedLoader,通過catalinaLoader加載所需要的類,
初始化catalinaDaemon對象,調用它的start方法。
疑問:bin/bootstrap.jar 和lib/catalina.jar中都有bootstrap,腳本調用的是bootstrap.jar中的main方法,bootstrap在初始三個類加載器後,
加載類時并沒有加載org.apache.catalina.startup包下的類,也就是說雖然兩個jar包都含有bootstrap類,但隻是系統的類加載器加載了
bin/bootstrap.jar裡的bootstrap。而其他lib 目錄下的所有的類都是有bootstrap裡面的三個類加載器加載的。不清楚為什麼這樣做。