Tomcat 7 中 web 應用加載原理(三)Listener、Filter、Servlet 的加載和調用

前一篇文章分析到了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

這 303 行能夠講的東西有不少,爲了避免偏離本文主題只抽出與如今要討論的問題相關的代碼來分析。

第 125 行會發佈一個CONFIGURE_START_EVENT事件,按前一篇博文所述,這裏即會觸發對 web.xml 的解析。第 20五、206 行設置實例管理器爲 DefaultInstanceManager(這個類在後面談實例構造時會用到)。第 237 行會調用 listenerStart 方法,第 255 行調用了 filterStart 方法,第 263 行調用了 loadOnStartup 方法,這三處調用即觸發 Listener、Filter、Servlet 真正對象的構造,下面逐個分析這些方法。spa

listenerStart 方法的完整代碼較長,這裏僅列出與 Listenner 對象構造相關的代碼: 3d

先從 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 對象。code

看下 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 方法的代碼:

由於要支持 Servlet 3 的新特性及各類異常處理,這段代碼顯得比較長。關注重點第 42 行,這裏會調用 StandardWrapper 的 allocate 方法,再也不貼出這個方法的代碼,須要提醒的是在 allocate 方法中可能會調用 loadServlet() 方法,這就是上一段提到的請求訪問 web 應用而第一次調用該 Servlet 時再加載並初始化 Servlet 。

第 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 方法。

相關文章
相關標籤/搜索