前一篇文章分析到了org.apache.catalina.deploy.WebXml
類的 configureContext 方法,可以看到在這個方法中通過各種 setXXX、addXXX 方法的呼叫,使得每個應用中的 web.xml 檔案的解析後將應用內部的表示 Servlet、Listener、Filter 的配置資訊與表示一個 web 應用的 Context 物件關聯起來。
這裡列出 configureContext 方法中與 Servlet、Listener、Filter 的配置資訊設定相關的呼叫程式碼:
for (FilterDef filter : filters.values()) {
if (filter.getAsyncSupported() == null) {
filter.setAsyncSupported("false");
}
context.addFilterDef(filter);
}
for (FilterMap filterMap : filterMaps) {
context.addFilterMap(filterMap);
}
複製程式碼
這是設定 Filter 相關配置資訊的。
for (String listener : listeners) {
context.addApplicationListener(
new ApplicationListener(listener, false));
}
複製程式碼
這是給應用新增 Listener 的。
for (ServletDef servlet : servlets.values()) {
Wrapper wrapper = context.createWrapper();
// Description is ignored
// Display name is ignored
// Icons are ignored
// jsp-file gets passed to the JSP Servlet as an init-param
if (servlet.getLoadOnStartup() != null) {
wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
}
if (servlet.getEnabled() != null) {
wrapper.setEnabled(servlet.getEnabled().booleanValue());
}
wrapper.setName(servlet.getServletName());
Map<String,String> params = servlet.getParameterMap();
for (Entry<String, String> entry : params.entrySet()) {
wrapper.addInitParameter(entry.getKey(), entry.getValue());
}
wrapper.setRunAs(servlet.getRunAs());
Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs();
for (SecurityRoleRef roleRef : roleRefs) {
wrapper.addSecurityReference(
roleRef.getName(), roleRef.getLink());
}
wrapper.setServletClass(servlet.getServletClass());
MultipartDef multipartdef = servlet.getMultipartDef();
if (multipartdef != null) {
if (multipartdef.getMaxFileSize() != null &&
multipartdef.getMaxRequestSize()!= null &&
multipartdef.getFileSizeThreshold() != null) {
wrapper.setMultipartConfigElement(new MultipartConfigElement(
multipartdef.getLocation(),
Long.parseLong(multipartdef.getMaxFileSize()),
Long.parseLong(multipartdef.getMaxRequestSize()),
Integer.parseInt(
multipartdef.getFileSizeThreshold())));
} else {
wrapper.setMultipartConfigElement(new MultipartConfigElement(
multipartdef.getLocation()));
}
}
if (servlet.getAsyncSupported() != null) {
wrapper.setAsyncSupported(
servlet.getAsyncSupported().booleanValue());
}
wrapper.setOverridable(servlet.isOverridable());
context.addChild(wrapper);
}
for (Entry<String, String> entry : servletMappings.entrySet()) {
context.addServletMapping(entry.getKey(), entry.getValue());
}
複製程式碼
這段程式碼是設定 Servlet 的相關配置資訊的。
以上是在各個 web 應用的 web.xml 檔案中(如果是 servlet 3,還會包括將這些配置資訊放在類的註解中,所以解析 web.xml 檔案之前可能會存在各個 web.xml 檔案資訊的合併步驟,這些動作的程式碼在前一篇文章中講 ContextConfig 類的 webConfig 方法中)的相關配置資訊的設定,但需要注意的是,這裡僅僅是將這些配置資訊儲存到了 StandardContext 的相應例項變數中,真正在一次請求訪問中用到的 Servlet、Listener、Filter 的例項並沒有構造出來,以上方法呼叫僅構造了代表這些例項的封裝類的例項,如 StandardWrapper、ApplicationListener、FilterDef、FilterMap。
那麼一個 web 應用中的 Servlet、Listener、Filter 的例項究竟在什麼時候構造出來的呢?答案在org.apache.catalina.core.StandardContext
類的 startInternal 方法中:
第 125 行會釋出一個CONFIGURE_START_EVENT
事件,按前一篇博文所述,這裡即會觸發對 web.xml 的解析。第 205、206 行設定例項管理器為 DefaultInstanceManager(這個類在後面談例項構造時會用到)。第 237 行會呼叫 listenerStart 方法,第 255 行呼叫了 filterStart 方法,第 263 行呼叫了 loadOnStartup 方法,這三處呼叫即觸發 Listener、Filter、Servlet 真正物件的構造,下面逐個分析這些方法。
listenerStart 方法的完整程式碼較長,這裡僅列出與 Listenner 物件構造相關的程式碼:
先從 Context 物件中取出例項變數 applicationListeners(該變數的值在 web.xml 解析時設定),第 12 行通過呼叫instanceManager.newInstance(listener.getClassName())
,前面在看 StandardContext 的 startInternal 方法第 205 行時看到 instanceManager 被設定為 DefaultInstanceManager 物件,所以這裡實際會執行 DefaultInstanceManager 類的 newInstance 方法:
public Object newInstance(String className) throws IllegalAccessException, InvocationTargetException, NamingException, InstantiationException, ClassNotFoundException {
Class<?> clazz = loadClassMaybePrivileged(className, classLoader);
return newInstance(clazz.newInstance(), clazz);
}
複製程式碼
所以instanceManager.newInstance(listener.getClassName())
這段程式碼的作用是取出 web.xml 中配置的 Listener 的 class 配置資訊,從而構造實際配置的 Listener 物件。
看下 filterStart 方法:
這段程式碼看起來很簡單,取出 web.xml 解析時讀到的 filter 配置資訊,在第 17 行呼叫 ApplicationFilterConfig 的構造方法: 預設情況下 filterDef 中是沒有 Filter 物件的,所以會呼叫第 12 行 getFilter 方法: 與 Listener 的物件構造類似,都是通過呼叫getInstanceManager().newInstance
方法。當然,按照 Servlet 規範,第 13 行還會呼叫 Filter 的 init 方法。
看下 loadOnStartup 方法:
在 web 應用啟動時將會載入配置了 load-on-startup 屬性的 Servlet。第 24 行,呼叫了 StandardWrapper 類的 load 方法: 在第 2 行 loadServlet 方法中與上面的 Listener 和 Filter 物件構造一樣呼叫instanceManager.newInstance
來構造 Servlet 物件,與 Filter 類似在第 5 行呼叫 Servlet 的 init 方法。
當然這種載入只是針對配置了 load-on-startup 屬性的 Servlet 而言,其它一般 Servlet 的載入和初始化會推遲到真正請求訪問 web 應用而第一次呼叫該 Servlet 時,下面會看到這種情況下程式碼分析。
以上分析的 web 應用啟動後這些物件的載入情況,接下來分析一下一次請求訪問時,相關的 Filter、Servlet 物件的呼叫。
在之前的《Tomcat 7 的一次請求分析》系列文章中曾經分析了一次請求如何與容器中的 Engine、Host、Context、Wrapper 各級元件匹配,並在這些容器元件內部的管道中流轉的。在該系列第四篇文章最後提到,一次請求最終會執行與它最適配的一個 StandardWrapper 的基礎閥org.apache.catalina.core.StandardWrapperValve
的 invoke 方法。當時限於篇幅沒繼續往下分析,這裡接著這段來看看請求的流轉。看下 invoke 方法的程式碼:
第 87 到 91 行會構造一個過濾器鏈( filterChain )用於執行這一次請求所經過的相應 Filter ,第 111 和第 128 行會呼叫該 filterChain 的 doFilter 方法:
在該方法最後呼叫了 internalDoFilter 方法: 概述一下這段程式碼,第 6 到 60 行是執行過濾器鏈中的各個過濾器的 doFilter 方法,例項變數n
表示過濾器鏈中所有的過濾器,pos
表示當前要執行的過濾器。其中第 7 行取出當前要執行的 Filter,之後將pos
加 1,接著第 30 行執行 Filter 的 doFilter 方法。一般的過濾器實現中在最後都會有這一句:
FilterChain.doFilter(request, response);
複製程式碼
這樣就又回到了 filterChain 的 doFilter 方法,形成了一個遞迴呼叫。要注意的是,filterChain 物件內部的 pos 是不斷加的,所以假如過濾器鏈中的各個 Filter 的 doFilter 方法都執行完之後將會執行到第 63 行,在接下來的第 92 行、第 95 行即呼叫 Servlet 的 service 方法。