Tomcat原始碼研究之web.xml中元素的載入順序
本文主所關注的是
web.xml
檔案各類元素的解析載入順序。
2018/11/15 因為CSDN改良了markdown語法,進行重新排版。
1. 前言
-
等不及的可以直接拖到最後看總結。
-
在Tomcat中,
web.xml
對應於Tomcat中的WebXml
類,web.xml
檔案裡解析出來的內容由Tomcat中的的WebXml
類來承載; -
具體的解析過程可以參見
ContextConfig
類的parseWebXml
方法。 -
WebXml
類中的configureContext(Context)
方法以Context
為引數,使用WebXml
類中儲存的解析資料對Context
進行填充。這裡舉個例子:其中就有這樣一端程式碼:// servletMappings中 <servlet-mapping> 的鍵值對 for (Entry<String, String> entry : servletMappings.entrySet()) { // 將鍵值對新增到Context, 讓Context可以掌控對映處理關係 context.addServletMapping(entry.getKey(), entry.getValue()); }
-
時機: 在監聽到
Lifecycle.CONFIGURE_START_EVENT
事件後,WebXml
將自身儲存的資訊注入到Context中 —— (關於這個事件,可以在下面的startInternal
-
以下就是執行順序了:
ContextConfig
類監聽到Lifecycle.CONFIGURE_START_EVENT
事件ContextConfig
類呼叫自身的configureStart()
方法,進而呼叫自身的webConfig()
方法 ,最後在將${CATALINA_BASE}/conf/web.xml
配置資訊融入自身後,呼叫WebXml.configureContext(context)
來將解析到配置資訊注入到Context例項中。
2. 細節
web.xml
檔案的解析是在Context啟動時參與的。所以我們將注意力集中到Context啟動時的順序上來。
Tomcat中的Context啟動時的順序是這樣的(原始碼位於:StandardContext
實現的startInternal
)
- 解析
web.xml
(通過觸發Lifecycle.CONFIGURE_START_EVENT
事件, 讓ContextConfig
類去完成解析web.xml工作, 解析出來的資訊由WebXml
承載, 並且由WebXml
例項將它們注入到Context
中); 注意這裡是同步回撥 ; 具體細節可以參見ContextConfig
類中的原始碼。- 其中一個重要環節就是解析
web.xml
(原始碼位置:ContextConfig.webConfig()
)。 - 另外一個重要環節就是讀取lib下的JAR中所有的
META/resources
裡的靜態資源(原始碼位置:ContextConfig.processResourceJARs()
)。
- 其中一個重要環節就是解析
- 往**
ServletContext
例項中注入<context-param>
引數** - 回撥Servlet3.0的
ServletContainerInitializers
介面實現類;ServletContainerInitializers
介面實現類是在ContextConfig
類中的processServletContainerInitializers();
方法解析到的。 - 觸發
Listener
事件(beforeContextInitialized, afterContextInitialized); 這裡只會觸發ServletContextListener
介面型別的 ; 例如Spring-web中的ContextLoaderListener
就實現了ServletContextListener
介面型別 - 初始化
Filter
, 呼叫其init
方法 - 載入 **啟動時即載入的servlet **
-
(Load-on-startup元素在web應用啟動的時候指定了servlet被載入的順序,它的值必須是一個整數。如果它的值是一個負整數或是這個元素不存在,那麼容器會在該servlet被呼叫的時候,載入這個servlet。如果值是正整數或零,容器在配置的時候就載入並初始化這個servlet,容器必須保證值小的先被載入。如果值相等,容器可以自動選擇先載入誰。 )
-
另外在
WebXml
類中configureContext
方法中可以看到這樣一行// servletMappings中 for (Entry<String, String> entry : servletMappings.entrySet()) { // <servlet-mapping>的匹配關係 context.addServletMapping(entry.getKey(), entry.getValue()); }
-
3. 原始碼解析之StandardContext.startInternal
// StandardContext實現的startInternal
protected synchronized void startInternal() throws LifecycleException {
// 略
// Sets the configured property to false.
// 代表配置工作還未完成
setConfigured(false);
boolean ok = true;
// 略
// Sets a loader
// 設定本Context的ClassLoader層級結構
if (getLoader() == null) {
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
}
// Initialize character set mapper
getCharsetMapper();
// Post work directory
postWorkDirectory();
// 略
// 1. -----------------------------------------------
// Notify our interested LifecycleListeners
// 此事件將被ContextConfig監聽到; 參見《Tomcat原始碼研究之ContextConfig.md》中的 configureStart() 方法講解,
// 1. 其中一個重要環節就是解析web.xml
// 2. 另外一個重要環節就是讀取lib下的JAR中所有的META/resources裡的靜態資源
fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
// Start our child containers, if not already started
for (Container child : findChildren()) {
if (!child.getState().isAvailable()) {
child.start();
}
}
// 略
// Start the Valves in our pipeline (including the basic),
// if any
if (pipeline instanceof Lifecycle) {
((Lifecycle) pipeline).start();
}
// 略
try {
// 2. -----------------------------------------------
// Set up the context init params
// 這裡會將從web.xml解析出來的<context-param> 引數通過呼叫setInitParameter方法注入到ServletContext中
// 外界可以通過 getInitParameter 方法取到它們.
mergeParameters();
// 3. -----------------------------------------------
// Call ServletContainerInitializers
// 啟動Servlet3.0的ServletContainerInitializers介面實現類
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
initializers.entrySet()) {
try {
entry.getKey().onStartup(entry.getValue(),
getServletContext());
} catch (ServletException e) {
log.error(sm.getString("standardContext.sciFail"), e);
ok = false;
break;
}
}
// 4. -----------------------------------------------
// Configure and call application event listeners
// 觸發監聽的listener
if (ok) {
// 啟動listener,觸發事件beforeContextInitialized, afterContextInitialized
if (!listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
}
// 略
// 5. -----------------------------------------------
// 初始化Filter
// Configure and call application filters
if (ok) {
// 這裡初始化每個定義的Filter, 呼叫它們的init方法
// 每個Filter在Tomcat內部對應一個FilterDef, 而ApplicationFilterConfig則實現了FilterConfig
// ApplicationFilterConfig中有FilterDef欄位, 而FilterDef內部沒有ApplicationFilterConfig欄位
if (!filterStart()) {
log.error(sm.getString("standardContext.filterFail"));
ok = false;
}
}
// 6. -----------------------------------------------
// Load and initialize all "load on startup" servlets
// 載入 啟動時即載入的servlet
// 在loadOnStartup 方法裡你可以看到 loadOnStartup 屬性小於0的就不會被載入
// 這loadOnStartup方法裡面呼叫的是Wrapper類的load方法; 這裡就會載入wrapper所包裝的servlet.
//
//
// 參見規則org.apache.catalina.startup.WebRuleSet類的addRuleInstances方法 ; 注意這個方法裡的digester目標是WebXml例項; 所以addRuleInstances方法裡的宣告的"addServlet"方法就是定義在WebXml中.
if (ok) {
if (!loadOnStartup(findChildren())){
log.error(sm.getString("standardContext.servletFail"));
ok = false;
}
}
// Start ContainerBackgroundProcessor thread
super.threadStart();
} finally {
// Unbinding thread
unbindThread(oldCCL);
}
// 略
// Reinitializing if something went wrong
if (!ok) {
setState(LifecycleState.FAILED);
} else {
// 觸發事件
setState(LifecycleState.STARTING);
}
}
4. 總結
讓我們回顧下載入順序,以便處理一些奇怪需求能做到心中有數。
-
讀取lib下的JAR中所有的
META/resources
裡的靜態資源。 -
往
ServletContext
例項中注入<context-param>
引數。 -
回撥 Servlet3.0的
ServletContainerInitializer
介面實現類 ; 注意ServletContainerInitializer
介面實現類是在ContextConfig.processServletContainerInitializers();
方法解析到的。相關的例子有SpringMVC中的SpringServletContainerInitializer
,以及Log4j2中的Log4jServletContainerInitializer
。以SPI的方式檢索。 -
觸發
Listener
事件(beforeContextInitialized
,afterContextInitialized
); 這裡只會觸發ServletContextListener
介面型別的 ; 例如Spring-web中的ContextLoaderListener
就實現了ServletContextListener
介面型別。 -
初始化
Filter
, 呼叫其init
方法。 -
載入 - 啟動時即載入的
Servlet
(Load-on-startup 屬性為正或零時,且 值小的先被載入)。 -
以上總結為一句話就是 : Web容器載入順序為
ServletContext ~ context-param ~ ServletContainerInitializer ~ listener ~ filter ~ servlet
。 -
對於
Filter
,其執行順序是由在web.xml
宣告的<filter-mapping>
順序,而不是<filter>
的宣告順序。<!-- 例如這裡, 下面的testFilter就不會執行--> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>testFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>