這是我們深入理解tomcat的第五篇文章玻蝌,按照我們的思路,這次我們本應(yīng)該區(qū)分析tomcat的連接器組件词疼,但樓主思前想后俯树,覺得連接器組件不能只是紙上談兵,需要深入源碼贰盗,但樓主本能的認(rèn)為我們應(yīng)該先分析tomcat的啟動過程许饿,以能夠和我們上一篇文章深入理解 Tomcat(四)Tomcat 類加載器之為何違背雙親委派模型
相銜接。因?yàn)閱宇惣虞d器的核心代碼就在啟動過程中舵盈,所以陋率,我決定先分析tomcat的啟動過程,結(jié)合源碼了解tomcat的類加載器如何實(shí)現(xiàn)秽晚,以徹底了解tomcat的類加載器翘贮。
因?yàn)門omcat 的啟動過程非常復(fù)雜,因此樓主將啟動過程拆開分析爆惧,不能像之前說的按照啟動過程分析了狸页,否則,文章篇幅過長扯再,條理也會變得不清晰芍耘,Tomcat的啟動過程包括了初始化容器的生命周期,還涉及到JMX的管理熄阻,還有我們現(xiàn)在分析的類加載器斋竞,因此,我們必須換個維度分析秃殉。
再一個坝初,因?yàn)檫B接器和容器緊緊關(guān)聯(lián),連接器的作用就是分析http請求钾军,所以鳄袍,樓主覺得我們之前的計(jì)劃可能需要變更一下,我們將在分析完生命周期和類加載器之后將結(jié)合源碼分析連接器和容器吏恭,以了解tomcat的核心組件在接受HTTP請求后如何運(yùn)行拗小。
所以,今天樱哼,我們的任務(wù)就是debug tomcat 源碼哀九,分析tomcat啟動過程的每一步操作剿配。在看這篇文章之前希望同學(xué)們看看我們的第四篇分析tomcat的文章以了解tomcat的類加載器。
下面是這篇文章的目錄結(jié)構(gòu):
1. 啟動tomcat阅束,進(jìn)入main方法
2. 進(jìn)入 init 方法
3. 進(jìn)入 setCatalinaHome 方法
4. 進(jìn)入 setCatalinaBase 方法
5. 接下來則是類加載器大顯身手的時候了. 進(jìn)入 intiClassLoaders 方法
6. 進(jìn)入 createClassLoader 方法
7. 我們回到 initClassLoaders 方法中來
8. 再回到 init 方法中, 類加載器初始化結(jié)束, 接下來干嘛?
9. 設(shè)置完線程上下文類加載器之后做什么呢? 進(jìn)入 securityClassLoad 方法
10. 進(jìn)入 loadCorePackage 方法
11. 回到 init 方法
12. 尋找 WebAppClassLoader呼胚, 進(jìn)入 startInternal 方法
13. 進(jìn)入 createClassLoader 方法
14. tomcat 類加載結(jié)構(gòu)體系創(chuàng)建完畢
1. 啟動tomcat,進(jìn)入main方法
我們打開我們之前clone下的Tomcat-Source-Code 源碼息裸,找到Bootstrap 類蝇更,找到main方法,在451行打上斷點(diǎn)界牡,啟動main方法簿寂,開始我們的調(diào)試:可以看到樓主已經(jīng)寫了很多的注釋漾抬,因?yàn)闃侵饕呀?jīng)debug過了.
我們看代碼宿亡,首先判斷”守護(hù)“對象是否為null,肯定為null了纳令,然后進(jìn)入if 塊挽荠, 創(chuàng)建一個默認(rèn)構(gòu)造器的Bootstrap對象,有一行注釋// Don't set daemon until init() has completed
, 說不要在init方法完成之前設(shè)置daemon 變量平绩,因?yàn)楹竺娴暮芏嗖襟E都依賴該變量圈匆,所以必須初始化結(jié)束后才能設(shè)置值,再繼續(xù)看捏雌,進(jìn)入init方法:
2. 進(jìn)入 init 方法
該方法注釋:Initialize daemon.
表明要初始化該守護(hù)程序跃赚,也就是這個變量Bootstrap:
我們看看該方法,首先setCatalinaHome()
方法性湿,也就是我們啟動虛擬機(jī)的時候設(shè)置的 VM 參數(shù):
我們進(jìn)入該方法看看
3. 進(jìn)入 setCatalinaHome 方法
很明顯,我們設(shè)置過 Catalina.home , 所以獲取classpath下的catalina.home 的值不為null纬傲,所以直接return, 如果不為null肤频,則從項(xiàng)目根目錄下獲取boostrap的jar包叹括。如果存在,則設(shè)置上一級目錄為 catalina.home, 如果不存在,則設(shè)置項(xiàng)目根目錄為 catalina.home.這就是 setCatalinaHome 方法的邏輯.
4. 進(jìn)入 setCatalinaBase 方法
下一步是執(zhí)行 setCatalinaBase 方法, 也是一樣能獲取catalina.base
, 直接 return,如果不存在, 則設(shè)置catalina.home
為catalina.base
, 如果catalina.home
也是空,那么則設(shè)置項(xiàng)目根目錄為catatlina.base
.
5. 接下來則是類加載器大顯身手的時候了. 進(jìn)入 intiClassLoaders 方法
intiClassLoaders(), 初始化多個類加載器. 我們進(jìn)入該方法查看具體邏輯:
首先,創(chuàng)建一個 common 類加載器, 父類加載器為 null, 注意: 如果是 java 推薦的類加載器機(jī)制, 父類加載器應(yīng)該是系統(tǒng)類加載器或者是擴(kuò)展類加載器, 所以這明顯違背了類加載器的雙親委派模型. 好, 我們繼續(xù)看 tomcat, 我們進(jìn)入 creatClassLoader 方法, 看看是如何實(shí)現(xiàn)的(該方法很長, 我們關(guān)注重要的邏輯):
6. 進(jìn)入 createClassLoader 方法
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
// 從/org/apache/catalina/startup/catalina.properties 中獲取該 key 的配置
// common.loader 對應(yīng)的 Value=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar
String value = CatalinaProperties.getProperty(name + ".loader");
// 如果不存在(默認(rèn)存在), 返回 null
if ((value == null) || (value.equals(""))){
return parent;
}
// 使用環(huán)境變量對應(yīng)的目錄替換字符串
value = replace(value);
//Repository是ClassLoaderFactory 中的一個靜態(tài)內(nèi)部類, 有2個屬性, location, type, 表示某個位置的某種類型的文件
List<Repository> repositories = new ArrayList<Repository>();
/*
省略部分代碼, 此部分代碼是處理value 變量并獲取文件位置和類型
*/
// 根據(jù)給定的路徑數(shù)組前去加載給定的 class 文件,
// StandardClassLoader 繼承了 java.net.URLClassLoader ,使用URLClassLoader的構(gòu)造器構(gòu)造類加載器.
// 根據(jù)父類加載器是否為 null, URLClassLoader將啟用不同的構(gòu)造器.
// 總之, common 類加載器沒有指定父類加載器,違背雙親委派模型
ClassLoader classLoader = ClassLoaderFactory.createClassLoader// 創(chuàng)建類加載器, 如果parent 是0,則
(repositories, parent);
// Retrieving MBean server // 注冊到 JMX 中管理 bean, 這個我們后面說
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;
}
雖然上面的代碼寫了注釋,但是我們還是梳理一下邏輯:
- 從
/org/apache/catalina/startup/catalina.properties
配置文件中獲取 lib 倉庫和 jar 包位置. 如果key (分別為common.loader、server.loader宵荒、shared.loader)所對應(yīng)的value 不存在則返回父類加載器. 默認(rèn)情況有.則繼續(xù)下面的邏輯. - 處理從配置文件中獲取的字符串, 得到j(luò)ar 包的位置.
- 使用
ClassLoaderFactory.createClassLoader(repositories, parent)
方法, 該方法使用一個繼承自java.net.URLClassLoader
廢棄的StandardClassLoader
類加載 jar 處理后得到的文件, 實(shí)際上調(diào)用的是URLClassLoader的構(gòu)造方法, 下面代碼是 crateClassLoader 方法中最后的邏輯, array 中是 jar 包的地址,根據(jù)父類加載器是否為 null 調(diào)用StandardClassLoader
不同的構(gòu)造方法. - 將ClassLoader 注冊到JMX服務(wù)中(這里涉及到生命周期的內(nèi)容汁雷,我們按下不表,后面再說)报咳。
下面的是StandardClassLoader
的構(gòu)造方法,可以看到,實(shí)際上只是使用URLClassLoader 的構(gòu)造方法:
而 URLClassLoader 將會調(diào)用父類 SecureClassLoader 的構(gòu)造方法, 而 SecureClassLoader 將會調(diào)用 ClassLoader 的構(gòu)造方法, 從而完成一個類加載器的初始化. 可謂不易.
7. 我們回到 initClassLoaders 方法中來
執(zhí)行完commonLoader = createClassLoader("common", null);
, 接下里就會判斷返回的類加載器是否為 null, 什么時候會為 null 呢? 找不到配置文件中的 key 的時候或者 key 對應(yīng)的 value 為空的時候回返回 null, 如果返回 null, 那么就設(shè)置默認(rèn)的類加載器為 common 類加載器.
繼續(xù)初始化 catalinaloader 和 sharedLoader 兩個類加載器, 同樣調(diào)用 createClassLoader 方法, 不同的是, 他們的父類加載器不是 null, 而是上面的 common 類加載器. 我們看看他們進(jìn)入這個方法的時候回怎么樣?我們 debug 看一下:
可以看到兩個獲取到都是空, 所以他們直接返回父類加載器, 也就是說, 他們?nèi)齻€使用的是同一個類加載器.
8. 再回到 init 方法中, 類加載器初始化結(jié)束, 接下來干嘛?
將 catalinaLoader 類加載器設(shè)置為當(dāng)前線程上下文類加載器. 并設(shè)置線程安全類加載器.同時檢查是否安全, 如果不安全,則直接結(jié)束.
9. 設(shè)置完線程上下文類加載器之后做什么呢? 進(jìn)入 securityClassLoad 方法
可以看到厅篓,該類時加載Tomcat容器中類資源伙菊,傳遞的ClassLoader時catalinaLoader,也就是說,Tomcat容器的類資源都是catalinaLoader加載完成的叼耙。
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野哭;
10. 進(jìn)入 loadCorePackage 方法
我們以加載Tomcat核心class的loadCorePackage方法為例,我們看源碼實(shí)現(xiàn):
private static final void loadCorePackage(ClassLoader loader)
throws Exception {
final String basePackage = "org.apache.catalina.core.";
loader.loadClass
(basePackage +
"AccessLogAdapter");
loader.loadClass
(basePackage +
"ApplicationContextFacade$1");
loader.loadClass
(basePackage +
"ApplicationDispatcher$PrivilegedForward");
loader.loadClass
(basePackage +
"ApplicationDispatcher$PrivilegedInclude");
loader.loadClass
(basePackage +
"AsyncContextImpl");
loader.loadClass
(basePackage +
"AsyncContextImpl$DebugException");
loader.loadClass
(basePackage +
"AsyncContextImpl$1");
loader.loadClass
(basePackage +
"AsyncContextImpl$PrivilegedGetTccl");
loader.loadClass
(basePackage +
"AsyncContextImpl$PrivilegedSetTccl");
loader.loadClass
(basePackage +
"AsyncListenerWrapper");
loader.loadClass
(basePackage +
"ContainerBase$PrivilegedAddChild");
loader.loadClass
(basePackage +
"DefaultInstanceManager$1");
loader.loadClass
(basePackage +
"DefaultInstanceManager$2");
loader.loadClass
(basePackage +
"DefaultInstanceManager$3");
loader.loadClass
(basePackage +
"DefaultInstanceManager$AnnotationCacheEntry");
loader.loadClass
(basePackage +
"DefaultInstanceManager$AnnotationCacheEntryType");
loader.loadClass
(basePackage +
"ApplicationHttpRequest$AttributeNamesEnumerator");
}
我們可以看到幻件,catalinaClassLoader 加載了該包下的類拨黔。根據(jù)我們之前的理解:catalinaClassLoader 加載的類是Tomcat容器私有的類加載器,加載路徑中的class對于Webapp不可見绰沥。
11. 回到 init 方法
首先打印一波日志, 然后使用類 catalinaLoader 類加載器加載 org.apache.catalina.startup.Catalina
類, 接著創(chuàng)建該類的一個對象, 名為 startupInstance, 意為"啟動對象實(shí)例", 然后使用反射調(diào)用該實(shí)例的setParentClassLoader 方法, 參數(shù)為 sharedLoader, 表示該實(shí)例的父類加載器為 sharedLoader.
最后, 設(shè)置 catalinaDaemon 為該實(shí)例篱蝇。
12. 尋找 WebAppClassLoader, 進(jìn)入 startInternal 方法
我們通過源碼知道了初始化了commonClassLoader, catalinaClassLoader, sharedLoader徽曲,但是零截,我們想起我們上一篇文章的圖,好像少了點(diǎn)什么秃臣?
WebAppClassLoader呢涧衙?
我們知道, WebAppClassLoaser 是各個Webapp私有的類加載器奥此,加載路徑中的class只對當(dāng)前Webapp可見弧哎,那么他是如何初始化的呢?WebAppClassLoaser 的初始化時間和這3個類加載器初始化的時間不同得院,由于WebAppClassLoaser 和Context 緊緊關(guān)聯(lián)傻铣,因此咋初始化
org.apache.catalina.core.StandardContext 會一起初始化 WebAppClassLoader, 該類中startInternal方法含有初始化類加載器的邏輯祥绞,核心源碼如下:
@Override
protected synchronized void startInternal() throws LifecycleException {
if (getLoader() == null) {
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
}
if ((loader != null) && (loader instanceof Lifecycle)) {
((Lifecycle) loader).start();
}
}
首先創(chuàng)建 WebAppClassLoader 非洲, 然后 setLoader(webappLoader),再調(diào)用start方法蜕径,該方法是個模板方法两踏,內(nèi)部有 startInternal 方法用于子類去實(shí)現(xiàn), 我們看WebAppClassLoader的startInternal 方法核心實(shí)現(xiàn):
@Override
protected void startInternal() throws LifecycleException {
classLoader = createClassLoader();
classLoader.setResources(container.getResources());
classLoader.setDelegate(this.delegate);
classLoader.setSearchExternalFirst(searchExternalFirst);
if (container instanceof StandardContext) {
classLoader.setAntiJARLocking(
((StandardContext) container).getAntiJARLocking());
classLoader.setClearReferencesStatic(
((StandardContext) container).getClearReferencesStatic());
classLoader.setClearReferencesStopThreads(
((StandardContext) container).getClearReferencesStopThreads());
classLoader.setClearReferencesStopTimerThreads(
((StandardContext) container).getClearReferencesStopTimerThreads());
classLoader.setClearReferencesHttpClientKeepAliveThread(
((StandardContext) container).getClearReferencesHttpClientKeepAliveThread());
}
for (int i = 0; i < repositories.length; i++) {
classLoader.addRepository(repositories[i]);
}
}
13. 進(jìn)入 createClassLoader 方法
首先classLoader = createClassLoader();
創(chuàng)建類加載器兜喻,并且設(shè)置其資源路徑為當(dāng)前Webapp下某個context的類資源梦染。最后我們看看createClassLoader的實(shí)現(xiàn):
/**
* Create associated classLoader.
*/
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;
}
這里的loaderClass 是 字符串 org.apache.catalina.loader.WebappClassLoader
, 首先通過反射實(shí)例化classLoader。現(xiàn)在我們知道了, WebappClassLoader 是在 StandardContext 初始化的時候?qū)嵗呐潦叮沧C明了WebappClassLoader 和 Context 息息相關(guān)泛粹。
14. omcat 類加載結(jié)構(gòu)體系創(chuàng)建完畢
至此,我們的Tomcat 類加載結(jié)構(gòu)體系創(chuàng)建完畢肮疗。真TMD復(fù)雜熬фⅰ!N被酢们衙! 不過,請記住碱呼, 閱讀源碼是提高我們水平最快速的手段蒙挑。源碼中大師們的設(shè)計(jì)模式和各種高級用法。會讓我們功力大增愚臀。繼續(xù)加油吧R涫础!懊悯!