前一篇文章分析到了org.apache.catalina.deploy.WebXml
類的 configureContext 方法,能夠看到在這個方法中經過各類 setXXX、addXXX 方法的調用,使得每一個應用中的 web.xml 文件的解析後將應用內部的表示 Servlet、Listener、Filter 的配置信息與表示一個 web 應用的 Context 對象關聯起來。web
這裏列出 configureContext 方法中與 Servlet、Listener、Filter 的配置信息設置相關的調用代碼:apache
for (FilterDef filter : filters.values()) {
if (filter.getAsyncSupported() == null) {
filter.setAsyncSupported("false");
}
context.addFilterDef(filter);
}
for (FilterMap filterMap : filterMaps) {
context.addFilterMap(filterMap);
}
複製代碼
這是設置 Filter 相關配置信息的。bash
for (String listener : listeners) {
context.addApplicationListener(
new ApplicationListener(listener, false));
}
複製代碼
這是給應用添加 Listener 的。app
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 的相關配置信息的。jsp
以上是在各個 web 應用的 web.xml 文件中(若是是 servlet 3,還會包括將這些配置信息放在類的註解中,因此解析 web.xml 文件以前可能會存在各個 web.xml 文件信息的合併步驟,這些動做的代碼在前一篇文章中講 ContextConfig 類的 webConfig 方法中)的相關配置信息的設置,但須要注意的是,這裏僅僅是將這些配置信息保存到了 StandardContext 的相應實例變量中,真正在一次請求訪問中用到的 Servlet、Listener、Filter 的實例並無構造出來,以上方法調用僅構造了表明這些實例的封裝類的實例,如 StandardWrapper、ApplicationListener、FilterDef、FilterMap。post
那麼一個 web 應用中的 Servlet、Listener、Filter 的實例究竟在何時構造出來的呢?答案在org.apache.catalina.core.StandardContext
類的 startInternal 方法中: ui
第 125 行會發佈一個CONFIGURE_START_EVENT
事件,按前一篇博文所述,這裏即會觸發對 web.xml 的解析。第 20五、206 行設置實例管理器爲 DefaultInstanceManager(這個類在後面談實例構造時會用到)。第 237 行會調用 listenerStart 方法,第 255 行調用了 filterStart 方法,第 263 行調用了 loadOnStartup 方法,這三處調用即觸發 Listener、Filter、Servlet 真正對象的構造,下面逐個分析這些方法。spa
listenerStart 方法的完整代碼較長,這裏僅列出與 Listenner 對象構造相關的代碼: 3d
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 對象。code
看下 filterStart 方法:
getInstanceManager().newInstance
方法。固然,按照 Servlet 規範,第 13 行還會調用 Filter 的 init 方法。
看下 loadOnStartup 方法:
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 方法:
n
表示過濾器鏈中全部的過濾器,
pos
表示當前要執行的過濾器。其中第 7 行取出當前要執行的 Filter,以後將
pos
加 1,接着第 30 行執行 Filter 的 doFilter 方法。通常的過濾器實現中在最後都會有這一句:
FilterChain.doFilter(request, response);
複製代碼
這樣就又回到了 filterChain 的 doFilter 方法,造成了一個遞歸調用。要注意的是,filterChain 對象內部的 pos 是不斷加的,因此假如過濾器鏈中的各個 Filter 的 doFilter 方法都執行完以後將會執行到第 63 行,在接下來的第 92 行、第 95 行即調用 Servlet 的 service 方法。