专栏名称: 预流
敲代码的
目录
相关文章推荐
51好读  ›  专栏  ›  预流

Tomcat 7 中 web 应用加载原理(三)Listener、Filter、Servlet 的加载和调用

预流  · 掘金  ·  · 2018-02-09 01:57

正文

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

这 303 行可以讲的东西有很多,为了不偏离本文主题只抽出与现在要讨论的问题相关的代码来分析。

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







请到「今天看啥」查看全文