柚子快報邀請碼778899分享:java Tomcat基礎(chǔ)詳解
柚子快報邀請碼778899分享:java Tomcat基礎(chǔ)詳解
第一篇:Tomcat基礎(chǔ)篇
lecture:鄧澎波
一、構(gòu)建Tomcat源碼環(huán)境
??工欲善其事必先利其器,為了學(xué)好Tomcat源碼,我們需要先在本地構(gòu)建一個Tomcat的運行環(huán)境。
1.源碼環(huán)境下載
源碼有兩種下載方式:
1.1 官網(wǎng)下載
https://tomcat.apache.org/
1.2 GitHub下載
當(dāng)然你也可以通過GitHub來拉取源代碼
https://github.com/apache/tomcat
2.Maven環(huán)境搭建
2.1 環(huán)境準備
打開IEDA導(dǎo)入項目,然后在項目中創(chuàng)建一個新的pom.xml文件,里面的內(nèi)容為:
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
然后設(shè)置項目為Maven項目,選中pom.xml文件,鼠標右點。選擇 Add as Maven Project .
在右側(cè)出現(xiàn)的Maven菜單中選擇編譯項目(compile)
2.2 項目啟動
編譯成功后進入 Bootstrap中,啟動main方法
出現(xiàn)如下提示,說明啟動成功,只是中文亂碼了
2.3 解決中文亂碼問題
中文亂碼問題的解決方案,修改兩處地方即可
1.修改org.apache.jasper.compiler.Localizer#getMessage(java.lang.String)方法
public static String getMessage(String errCode) {
String errMsg = errCode;
try {
if (bundle != null) {
errMsg = bundle.getString(errCode);
}
} catch (MissingResourceException e) {
}
try{
errMsg = new String(errMsg.getBytes("ISO-8859-1"),"UTF-8");
}catch (UnsupportedEncodingException e){
e.printStackTrace();
}
return errMsg;
}
2.修改org.apache.tomcat.util.res.StringManager#getString(java.lang.String)
重啟服務(wù)
啟動正常,但是訪問的時候出現(xiàn)了問題
2.4 解決不支持JSP的問題
啟動成功后,在訪問首頁的時候,出現(xiàn)了500錯誤,而且提示 無法為JSP編譯類。
原因是無法編譯jsp。解決也很簡單,按照下面步驟操作即可
上面的報錯解決方式,可以在org.apache.catalina.startup.ContextConfig類中的configureStart方法中,添加一下JSP解析器初始化即可
context.addServletContainerInitializer(new JasperInitializer(), null);
重啟服務(wù):訪問搞定
到此Tomcat的源碼環(huán)境我們就已經(jīng)準備好了,接下來就可以開始我們的Tomcat源碼之旅了!!!
二、Tomcat源碼結(jié)構(gòu)介紹
??在分析Tomcat源碼之前,我們先來看下Tomcat源碼的結(jié)構(gòu)組成,這樣會更加的有利于我們更好的來分析源碼。
1.項目源碼結(jié)構(gòu)
我們先從源碼結(jié)構(gòu)開始。Tomcat 服務(wù)器相關(guān)的代碼在 java 文件夾下面,后面我們在進入這個文件夾去分析:
之前如何手動在Tomcat中部署過項目的話,這塊應(yīng)該會比較清楚點。
2.Tomcat源碼結(jié)構(gòu)
Tomcat 源碼位于 java 文件夾下面。這個java文件夾中的每個包的作用,我們簡單的來介紹下,后面在分析核心源碼的時候會重點講解。
我們可以看到在java目錄下,分為了兩個結(jié)構(gòu),一個是javax另一個是org.apache這兩個包
2.1 javax
在javax中保存的是新的JavaEE規(guī)范??梢跃唧w來看看每個目錄的作用。
模塊作用說明annotationannotation 這個模塊的作用是定義了一些公用的注解,避免在不同的規(guī)范中定義相同的注解。ejbejb是個古老的傳說,我們不管el在jsp中可以使用EL表達式,這么模塊解析EL表達式的mail和郵件相關(guān)的規(guī)范persistence持久化相關(guān)的security和安全相關(guān)的內(nèi)容servlet這個指定的是Servlet的開發(fā)規(guī)范,Tomcat本質(zhì)上就是一個實現(xiàn)了Servlet規(guī)范的一個容器,Servlet定義了服務(wù)端處理Http請求和響應(yīng)的方式(規(guī)范)websocket定義了使用 websocket 協(xié)議的服務(wù)端和客戶端 APIxml.ws定義了基于 SOAP 協(xié)議的 xml 方式的 web 服務(wù)
2.2 org.apache
org.apache這個包是Tomcat的源碼包,也是針對上面的JavaEE規(guī)范的部分實現(xiàn),Tomcat的本質(zhì)就是對JavaEE的某些規(guī)范的實現(xiàn)合集,首先肯定實現(xiàn)了Servlet規(guī)范
模塊作用catalinacatalina是Tomcat的核心模塊,里面完整的實現(xiàn)了Servlet規(guī)范,Tomcat啟動的主方法也在里面,后面我們分析的重點。coyotetomcat 的核心代碼,負責(zé)將網(wǎng)絡(luò)請求轉(zhuǎn)化后和 Catalina 進行通信。el這個是上面javax中的el規(guī)范的實現(xiàn)jasper主要負責(zé)把jsp代碼轉(zhuǎn)換為java代碼。juli日志相關(guān)的工具naming命名空間相關(guān)的內(nèi)容tomcat各種輔助工具,包括 websocket 的實現(xiàn)。
3.Tomcat模塊設(shè)計
連接器的作用:
連接器功能· 監(jiān)聽網(wǎng)絡(luò)端口。接受網(wǎng)絡(luò)連接請求。根據(jù)具體應(yīng)用層協(xié)議(http/ajp)解析字節(jié)流,生成統(tǒng)一的Tomcat Request對象。將Tomcat Request對象轉(zhuǎn)成標準的ServletRequest。調(diào)用Servlet容器,得到ServletResponse。將ServletResponse轉(zhuǎn)成Tomcat Response對象。將Tomcat Response轉(zhuǎn)成網(wǎng)絡(luò)字節(jié)流。將響應(yīng)字節(jié)流寫回給瀏覽器。
三、Tomcat的架構(gòu)設(shè)計
1.Servlet規(guī)范
1.1 Servlet作用講解
??Servlet是JavaEE規(guī)范中的一種,主要是為了擴展Java作為Web服務(wù)的功能,統(tǒng)一定義了對應(yīng)的接口,比如Servlet接口,HttpRequest接口,HttpResponse接口,F(xiàn)ilter接口。然后由具體的服務(wù)廠商來實現(xiàn)這些接口功能,比如Tomcat,jetty等。
?&ems;在規(guī)范里面并不會有具體的實現(xiàn)。可以自行看下源碼,而在Servlet規(guī)范中規(guī)定了一個http請求到來的執(zhí)行處理流程:對應(yīng)的服務(wù)器容器會接收到對應(yīng)的Http請求,然后解析該請求,然后創(chuàng)建對應(yīng)的Servlet實例,調(diào)用對應(yīng)init方法來完成初始化,把請求的相關(guān)信息封裝為HttpServletRequest對象來調(diào)用Servlet的service方法來處理請求,然后通過HttpServletResponse封裝響應(yīng)的信息交給容器,響應(yīng)給客戶端。
1.2 Servlet核心API
??我們再來回顧下Servlet中的核心API,這塊對我們更好的掌握Tomcat的內(nèi)容還是非常有幫助的。
API描述ServletConfig獲取servlet初始化參數(shù)和servletContext對象。ServletContext在整個Web應(yīng)用的動態(tài)資源之間共享數(shù)據(jù)。ServletRequest封裝Http請求信息,在請求時創(chuàng)建。ServletResponse封裝Http響應(yīng)信息,在請求時創(chuàng)建。
ServletConfig:
??容器在初始化servlet時,為該servlet創(chuàng)建一個servletConfig對象,并將這個對象通過init()方法來傳遞并保存在此Servlet對象中。核心作用:
獲取初始化信息;獲取ServletContext對象。
ServletContext
??一個項目只有一個ServletContext對象,可以在多個Servlet中來獲取這個對象,使用它可以給多個Servlet傳遞數(shù)據(jù),該對象在Tomcat啟動時就創(chuàng)建,在Tomcat關(guān)閉時才會銷毀!作用是在整個Web應(yīng)用的動態(tài)資源之間共享數(shù)據(jù)。
??在實際的Servlet開發(fā)中,我們會實現(xiàn)HttpServlet接口,在該接口中會實現(xiàn)GenericServlet,而在GenericServlet會實現(xiàn)ServiceConfig接口,從而可以獲取ServletContext容器對象
所以在Servlet中我們可以很容易的獲取到ServletContext對象,從而完成對應(yīng)的操作。
public class ServletTwoImpl extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
// 1、參數(shù)傳遞
ServletContext servletContext = this.getServletContext() ;
String value = String.valueOf(servletContext.getAttribute("name")) ;
System.out.println("value="+value);
// 2、獲取初始化參數(shù)
String userName= servletContext.getInitParameter("user-name") ;
System.out.println("userName="+userName);
// 3、獲取應(yīng)用信息
String servletContextName = servletContext.getServletContextName() ;
System.out.println("servletContextName="+servletContextName);
// 4、獲取路徑
String pathOne = servletContext.getRealPath("/") ;
String pathTwo = servletContext.getRealPath("/WEB-INF/") ;
System.out.println("pathOne="+pathOne+";pathTwo="+pathTwo);
response.getWriter().print("執(zhí)行:doGet; value:"+value);
}
}
1.3 ServletRequest
??HttpServletRequest接口繼承ServletRequest接口,用于封裝請求信息,該對象在用戶每次請求servlet時創(chuàng)建并傳入servlet的service()方法,在該方法中,傳入的servletRequest將會被強制轉(zhuǎn)化為HttpservletRequest 對象來進行HTTP請求信息的處理。核心作用:
獲取請求報文信息;獲取網(wǎng)絡(luò)連接信息;獲取請求域?qū)傩孕畔ⅰ?/p>
1.4 ServletResponse
??HttpServletResponse繼承自ServletResponse,封裝了Http響應(yīng)信息??蛻舳嗣總€請求,服務(wù)器都會創(chuàng)建一個response對象,并傳入給Servlet.service()方法。核心作用:
設(shè)置響應(yīng)頭信息;發(fā)送狀態(tài)碼;設(shè)置響應(yīng)正文;重定向;
2.Tomcat的設(shè)計
??通過上面Servlet規(guī)范的介紹,其實我們發(fā)下我們要實現(xiàn)Servlet規(guī)范的話,很重要的就得提供一個服務(wù)容器來獲取請求,解析封裝數(shù)據(jù),并調(diào)用Servlet實例相關(guān)的方法。也就是如下圖中的部分
??這塊的內(nèi)容其實就是Tomcat,具體的我們來看看。
2.1 什么是Tomcat
??Tomcat是一個容器,用于承載Servlet,那么我們說Tomcat就是一個實現(xiàn)了部分J2EE規(guī)范的服務(wù)器。J2 EE和Jakarta EE(Eclipse基金會)這兩是啥?用于Tomcat10以后都是Jakarta EE,而9之前就是J2EE.
2.2 Tomcat的架構(gòu)結(jié)構(gòu)
??我們通過上面的分析,知道Tomcat是一個Servlet規(guī)范的實現(xiàn),要接收請求和響應(yīng)請求,那么具體是如何實現(xiàn)的呢?這塊我們可以通過conf下的server.xml得出對應(yīng)的結(jié)論。
??server.xml是Tomcat中最重要的配置文件,server.xml 的每一個元素都對應(yīng)了Tomcat 中的一個組件 ;通過對xml文件中元素的配置,可以實現(xiàn)對Tomcat中各個組件的控制。因此,學(xué)習(xí)server.xml文件的配置,對于了解和使用Tomcat至關(guān)重要.
官方文檔:https://tomcat.apache.org/tomcat-8.5-doc/config/server.html
maxThreads="150" minSpareThreads="4"/> connectionTimeout="20000" redirectPort="8443" /> port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /> resourceName="UserDatabase"/> unpackWARs="true" autoDeploy="true"> prefix="localhost_access_log" suffix=".txt" pattern="%h %l %u %t "%r" %s %b" /> 極簡模式 梳理出的結(jié)構(gòu) 對應(yīng)的每個組件的作用。 2.3 組件分類 ??官網(wǎng)其實對上面的組件也做了分類: 頂級元素: Server:是整個配置文件的根元素Service:代表一個Engine元素以及一組與之相連的Connector元素 連接器: 代表了外部客戶端發(fā)送請求到特定Service的接口;同時也是外部客戶端從特定Service接收響應(yīng)的接口。 容器: ??容器的作用是處理Connector接收進來的請求,并產(chǎn)生對應(yīng)的響應(yīng),Engine,Host和Context都是容器,他們不是平行關(guān)系,而是父子關(guān)系。 每個組件的作用: Engine:可以處理所有請求Host:可以處理發(fā)向一個特定虛擬主機的所有請求Context:可以處理一個特定Web應(yīng)用的所有請求 核心組件的串聯(lián)關(guān)系: ??當(dāng)客戶端請求發(fā)送過來后其實是通過這些組件相互之間配合完成了對應(yīng)的操作。 Server元素在最頂層,代表整個Tomcat容器;一個Server元素中可以有一個或多個Service元素Service在Connector和Engine外面包了一層,把它們組裝在一起,對外提供服務(wù)。一個Service可以包含多個Connector,但是只能包含一個Engine;Connector接收請求,Engine處理請求。Engine、Host和Context都是容器,且Engine包含Host,Host包含Context。每個Host組件代表Engine中的一個虛擬主機;每個Context組件代表在特定Host上運行的一個Web應(yīng)用. 整體Tomcat的運行架構(gòu)圖 四、Tomcat生命周期 ??在上篇文章中我們看到了Tomcat架構(gòu)中的核心組件,而且各個組件都有各自的作用,各司其職,而且相互之間也有對應(yīng)的父子關(guān)系,那么這些對象的創(chuàng)建,調(diào)用,銷毀等操作是怎么處理呢? ??也就是在Tomcat中的組件的對象生命周期是怎么管理的呢?針對這個問題,在Tomcat中設(shè)計了Lifecycle接口來統(tǒng)一管理Tomcat中的核心組件的生命周期,所以本文我們就系統(tǒng)的來介紹下Lifecycle接口的設(shè)計 1、LifeCycle接口設(shè)計 ??為了統(tǒng)一管理Tomcat中的核心組件的生命周期,而專門設(shè)計了LifeCycle接口來統(tǒng)一管理,我們來看看在LifeCycle接口中聲明了哪些內(nèi)容。 1.1 生命周期的方法 ??在LifeCycle中聲明了和生命周期相關(guān)的方法,包括init(),start(),stop(),destory()等方法。 ??在聲明的方法執(zhí)行的過程中會涉及到對應(yīng)的狀態(tài)的轉(zhuǎn)換,在LifeCycle接口的頭部文檔中很清楚的說了。 1.2 相關(guān)的狀態(tài)處理 ??通過上圖我們可以很清楚的看到相關(guān)的方法執(zhí)行會涉及到的相關(guān)狀態(tài)的轉(zhuǎn)換,比如init()會從New這個狀態(tài)開始,然后會進入 INITIALIZING 和 INITIALIZED 等。因為這塊涉及到了對應(yīng)的狀態(tài)轉(zhuǎn)換,在Lifecycle中聲明了相關(guān)的狀態(tài)和事件的生命周期字符串。 public static final String BEFORE_START_EVENT = "before_start"; public static final String AFTER_START_EVENT = "after_start"; public static final String STOP_EVENT = "stop"; public static final String BEFORE_STOP_EVENT = "before_stop"; public static final String AFTER_STOP_EVENT = "after_stop"; public static final String AFTER_DESTROY_EVENT = "after_destroy"; public static final String BEFORE_DESTROY_EVENT = "before_destroy"; /** * The LifecycleEvent type for the "periodic" event. * 周期性事件(后臺線程定時執(zhí)行一些事情,比如:熱部署、熱替換) */ public static final String PERIODIC_EVENT = "periodic"; public static final String CONFIGURE_START_EVENT = "configure_start"; public static final String CONFIGURE_STOP_EVENT = "configure_stop"; 在LifecycleState中建立了對應(yīng)關(guān)系 ??針對特定的事件就會有相關(guān)的監(jiān)聽器來監(jiān)聽處理。在Lifecycle中定義了相關(guān)的處理方法。 public void addLifecycleListener(LifecycleListener listener); public LifecycleListener[] findLifecycleListeners(); public void removeLifecycleListener(LifecycleListener listener); ??通過方法名稱我們就能很清楚該方法的相關(guān)作用,就不過程介紹了。然后來看下對應(yīng)的監(jiān)聽器和事件接口的對應(yīng)設(shè)計。 2.監(jiān)聽器和事件的設(shè)計 ??接下來看下LifecycleListener的設(shè)計。其實代碼非常簡單。 public interface LifecycleListener { /** * Acknowledge the occurrence of the specified event. * 觸發(fā)監(jiān)聽器后要執(zhí)行邏輯的方法 * @param event LifecycleEvent that has occurred */ public void lifecycleEvent(LifecycleEvent event); } ??然后來看下事件的接口 public final class LifecycleEvent extends EventObject { private static final long serialVersionUID = 1L; /** * Construct a new LifecycleEvent with the specified parameters. * * @param lifecycle Component on which this event occurred * @param type Event type (required) * @param data Event data (if any) */ public LifecycleEvent(Lifecycle lifecycle, String type, Object data) { super(lifecycle); // 向上轉(zhuǎn)型,可接受一切實現(xiàn)了生命周期的組件 this.type = type; this.data = data; } /** * The event data associated with this event. * 攜帶的額外的數(shù)據(jù),傳遞給監(jiān)聽器的數(shù)據(jù) */ private final Object data; /** * The event type this instance represents. * 事件類型 */ private final String type; /** * @return the event data of this event. */ public Object getData() { return data; } /** * @return the Lifecycle on which this event occurred. */ public Lifecycle getLifecycle() { return (Lifecycle) getSource(); } /** * @return the event type of this event. */ public String getType() { return this.type; } } ??也是非常簡單,不過多的贅述。 3.LifecycleBase ??通過上面的介紹我們可以看到在Tomcat中設(shè)計了Lifecycle和LifecycleListener和LifecycleEvent來管理核心組件的生命周期,那么我們就需要讓每一個組件都實現(xiàn)相關(guān)的接口。這時你會發(fā)現(xiàn)交給子類的工作量其實是比較大的,不光要完成各個組件的核心功能,還得實現(xiàn)生命周期的相關(guān)處理,耦合性很強,這時在Tomcat中給我們提供了一個LifecycleBase的抽象類,幫助我們實現(xiàn)了很多和具體業(yè)務(wù)無關(guān)的處理,來簡化了具體組件的業(yè)務(wù)。 3.1 事件處理 ??在上面的接口設(shè)計中對于監(jiān)聽對應(yīng)的事件處理是沒有實現(xiàn)的,在LifecycleBase把這塊很好的實現(xiàn)了,我們來看下。首先定義了一個容器來存儲所有的監(jiān)聽器 // 存儲了所有的實現(xiàn)了LifecycleListener接口的監(jiān)聽器 private final List ??同時提供了觸發(fā)監(jiān)聽的相關(guān)的方法,綁定了對應(yīng)的事件。 /** * Allow sub classes to fire {@link Lifecycle} events. * 監(jiān)聽器觸發(fā)相關(guān)的事件 * @param type Event type 事件類型 * @param data Data associated with event. */ protected void fireLifecycleEvent(String type, Object data) { LifecycleEvent event = new LifecycleEvent(this, type, data); for (LifecycleListener listener : lifecycleListeners) { listener.lifecycleEvent(event); } } ??已經(jīng)針對Listener相關(guān)的處理方法 // 添加監(jiān)聽器 @Override public void addLifecycleListener(LifecycleListener listener) { lifecycleListeners.add(listener); } // 查找所有的監(jiān)聽并轉(zhuǎn)換為了數(shù)組類型 @Override public LifecycleListener[] findLifecycleListeners() { return lifecycleListeners.toArray(new LifecycleListener[0]); } // 移除某個監(jiān)聽器 @Override public void removeLifecycleListener(LifecycleListener listener) { lifecycleListeners.remove(listener); } 3.2 生命周期方法 ??在LifecycleBase中最核心的還是實現(xiàn)了Lifecycle中的生命周期方法,以init方法為例我們來看。 /** * 實現(xiàn)了 Lifecycle 中定義的init方法 * 該方法和對應(yīng)的組件的狀態(tài)產(chǎn)生的關(guān)聯(lián) * @throws LifecycleException */ @Override public final synchronized void init() throws LifecycleException { if (!state.equals(LifecycleState.NEW)) { // 無效的操作 只有狀態(tài)為 New 的才能調(diào)用init方法進入初始化 invalidTransition(Lifecycle.BEFORE_INIT_EVENT); } try { // 設(shè)置狀態(tài)為初始化進行中....同步在方法中會觸發(fā)對應(yīng)的事件 setStateInternal(LifecycleState.INITIALIZING, null, false); initInternal(); // 交給子類具體的實現(xiàn) 初始化操作 // 更新狀態(tài)為初始化完成 同步在方法中會觸發(fā)對應(yīng)的事件 setStateInternal(LifecycleState.INITIALIZED, null, false); } catch (Throwable t) { handleSubClassException(t, "lifecycleBase.initFail", toString()); } } 源碼解析: 我們看到首先會判斷當(dāng)前對象的state狀態(tài)是否為NEW,因為init方法只能在NEW狀態(tài)下才能開始初始化如果1條件滿足則會更新state的狀態(tài)為 INITIALIZED 同時會觸發(fā)這個事件然后initInternale()方法會交給子類具體去實現(xiàn),等待子類處理完成后會把狀態(tài)更新為 INITIALIZED。 我們可以進入setStateInternal方法查看最后的關(guān)鍵代碼: // .... this.state = state; // 更新狀態(tài) // 根據(jù)狀態(tài)和事件的綁定關(guān)系獲取對應(yīng)的事件 String lifecycleEvent = state.getLifecycleEvent(); if (lifecycleEvent != null) { // 發(fā)布對應(yīng)的事件 fireLifecycleEvent(lifecycleEvent, data); } ??可以看到和對應(yīng)的事件關(guān)聯(lián)起來了。init方法的邏輯弄清楚后,你會發(fā)現(xiàn)start方法,stop方法,destory方法的處理邏輯都是差不多的,可自行觀看。而對應(yīng)的 initInternal()方法的邏輯我們需要在 Server Service Engine Connector等核心組件中再看,這個我們會結(jié)合Tomcat的啟動流程來帶領(lǐng)大家一起查看。下一篇給大家介紹。 五、Tomcat的啟動核心流程 ??前面給大家介紹了Tomcat中的生命周期的設(shè)計,掌握了這塊對于我們分析Tomcat的核心流程是非常有幫助的,也就是我們需要創(chuàng)建相關(guān)的核心組件,比如Server,Service肯定都繞不開生命周期的方法。 1.啟動的入口 ??你可以通過腳本來啟動Tomcat服務(wù)(startup.bat),但如果你看過腳本的命令,你會發(fā)現(xiàn)最終調(diào)用的還是Bootstrap中的main方法,所以我們需要從main方法來開始 ??然后我們?nèi)タ磎ain方法中的代碼,我們需要重點關(guān)注的方法有三個 bootstrap.init()方法load()方法start()方法 ??也就是在這三個方法中會完成Tomcat的核心操作。 2.init方法 ??我們來看下init方法中的代碼,非核心的我們直接去掉 public void init() throws Exception { // 創(chuàng)建相關(guān)的類加載器 initClassLoaders(); // 省略部分代碼... // 通過反射創(chuàng)建了 Catalina 類對象 Class> startupClass = catalinaLoader .loadClass("org.apache.catalina.startup.Catalina"); // 創(chuàng)建了 Catalina 實例 Object startupInstance = startupClass.getConstructor().newInstance(); // 省略部分代碼... String methodName = "setParentClassLoader"; Class> paramTypes[] = new Class[1]; paramTypes[0] = Class.forName("java.lang.ClassLoader"); Object paramValues[] = new Object[1]; paramValues[0] = sharedLoader; // 把 sharedLoader 設(shè)置為了 commonLoader的父加載器 Method method = startupInstance.getClass().getMethod(methodName, paramTypes); method.invoke(startupInstance, paramValues); // Catalina 實例 賦值給了 catalinaDaemon catalinaDaemon = startupInstance; } 首先是調(diào)用了initClassLoaders()方法,這個方法會完成對應(yīng)的ClassLoader的創(chuàng)建,這個比較重要,后面專門寫一篇文章來介紹。通過反射的方式創(chuàng)建了Catalina的類對象,并通過反射創(chuàng)建了Catalina的實例設(shè)置了類加載器的父子關(guān)系用過成員變量catalinaDaemon記錄了我們創(chuàng)建的Catalina實例 ??這個是通過bootstrap.init()方法我們可以獲取到的有用的信息。然后我們繼續(xù)往下面看。 3.load方法 ??然后我們來看下load方法做了什么事情,代碼如下: private void load(String[] arguments) throws Exception { // Call the load() method String methodName = "load"; // load方法的名稱 Object param[]; Class> paramTypes[]; if (arguments==null || arguments.length==0) { paramTypes = null; param = null; } else { paramTypes = new Class[1]; paramTypes[0] = arguments.getClass(); param = new Object[1]; param[0] = arguments; } // catalinaDaemon 就是在 init中創(chuàng)建的 Catalina 對象 Method method = catalinaDaemon.getClass().getMethod(methodName, paramTypes); if (log.isDebugEnabled()) { log.debug("Calling startup class " + method); } // 會執(zhí)行 Catalina的load方法 method.invoke(catalinaDaemon, param); } ??上面的代碼非常簡單,通過注釋我們也可以看出該方法的作用是調(diào)用 Catalina的load方法。所以我們還需要加入到Catalina的load方法中來查看,代碼同樣比較長,只留下關(guān)鍵代碼 public void load() { if (loaded) { return; // 只能被加載一次 } loaded = true; initDirs(); // 廢棄的方法 // Before digester - it may be needed initNaming(); // 和JNDI 相關(guān)的內(nèi)容 忽略 // Create and execute our Digester // 創(chuàng)建并且執(zhí)行我們的 Digester 對象 Server.xml Digester digester = createStartDigester(); // 省略掉了 Digester文件處理的代碼 getServer().setCatalina(this); // Server對象綁定 Catalina對象 getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile()); getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile()); // Stream redirection initStreams(); // 省略掉了部分代碼... getServer().init(); // 完成 Server Service Engine Connector等組件的init操作 } 把上面的代碼簡化后我們發(fā)現(xiàn)這個Load方法其實也是蠻簡單的,就做了兩件事。 通過Apache下的Digester組件完成了Server.xml文件的解析通過getServer().init() 方法完成了Server,Service,Engin,Connector等核心組件的初始化操作,這塊和前面的LifecycleBase呼應(yīng)起來了。 ??如果生命周期的內(nèi)容不清楚,請看上一篇文章的介紹。 4.start方法 ??最后我們來看下start方法的代碼。 public void start() throws Exception { if (catalinaDaemon == null) { init(); // 如果 catalinaDaemon 為空 初始化操作 } // 獲取的是 Catalina 中的 start方法 Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null); // 執(zhí)行 Catalina 的start方法 method.invoke(catalinaDaemon, (Object [])null); } ??上面的代碼邏輯也很清楚,就是通過反射的方式調(diào)用了Catalina對象的start方法。所以進入Catalina的start方法中查看。 public void start() { if (getServer() == null) { load(); // 如果Server 為空 重新 init 相關(guān)的組件 } if (getServer() == null) { log.fatal("Cannot start server. Server instance is not configured."); return; } // Start the new server 關(guān)鍵方法--->啟動Server try { getServer().start(); } catch (LifecycleException e) { // 省略... } // 省略... // Register shutdown hook 注冊關(guān)閉的鉤子 if (useShutdownHook) { // 省略... } if (await) { await(); stop(); } } ??通過上面的代碼我們可以發(fā)現(xiàn)核心的代碼還是getServer.start()方法,也就是通過Server對象來嵌套的調(diào)用相關(guān)注解的start方法。 5.核心流程的總結(jié) 我們可以通過下圖來總結(jié)下Tomcat啟動的核心流程 ??從圖中我們可以看到Bootstrap其實沒有做什么核心的事情,主要還是Catalina來完成的。 柚子快報邀請碼778899分享:java Tomcat基礎(chǔ)詳解 參考鏈接
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。