Tomcat源码深析之web.xml组件的处理
Tomcat源码深析之web.xml组件的处理
Float_Luuu 发表于2年前
Tomcat源码深析之web.xml组件的处理
  • 发表于 2年前
  • 阅读 1704
  • 收藏 26
  • 点赞 2
  • 评论 0

腾讯云 十分钟定制你的第一个小程序>>>   

摘要: 这篇文章主要是带着读者通过分析Tomcat的源码,深入了解Tomcat对web.xml配置的组件的的处理,文章内容主要包括Tomcat对上下文参数(contextParams),过滤器(Filters),应用监听器(listeners)以及Servlet的加载,初始化等等。

    这篇文章主要是带着读者通过分析Tomcat的源码,深入了解Tomcat对web.xml配置的组件的的处理,文章内容主要包括Tomcat对上下文参数(contextParams),过滤器(Filters),应用监听器(listeners)以及Servlet的加载,初始化等等。

    在Java Web开发中我们对web.xml这个配置文件并不陌生,也对web.xml中配置的常用组件很了解,我所指的即过滤器、监听器、Servlet三大组件。包括他们的加载顺序,初始化顺序,我相信这对于所有Java Web开发者来说是一定要掌握的基础知识。这也是开发Web中间件会经常用到的,随便列举一些例子:Spring、Struts、UrlRewrite、等等。

    下面跟着博主一起来通过扒一扒Tomcat的源码来深入了解一下他们的相关知识吧,这比概念上去了解更深刻一些。

一、web.xml的解析

这个部分可以在类ContextConfig类中找到相关源码,Context即代表Servlet的上下文。里面有个protected的方法webConfig,这个方法里面主要做下面事情:

  1. 扫描应用打包的所有Jar来检索Jar包里面的web.xml配置并解析,放入内存。

  2. 对这些已经检索到的web配置进行排序。

  3. 基于SPI机制查找ServletContainerInitializer的实现,写web中间件的同学注意了,了解SPI以及                           ServletContainerInitializer机制这对于你来说可能是一个很好的知识点。

  4. 处理/WEB-INF/classes下面的类的注解,某个版本Servlet支持注解方式的配置,可以猜测相关事宜就是在这里干          的。

  5. 处理Jar包中的注解类。

  6. 将web配置按照一定规则合并到一起。

  7. 应用全局默认配置,还记得Tomcat包下面的conf文件夹下面有个web.xml配置文件吧。

  8. 将JSP转换为Servlet,这让我想起了若干年前对JSP的理解。

  9. 将web配置应用到Servlet上下文,也即Servlet容器。

  10. 将配置信息保存起来以供其他组件访问,使得其他组件不需要再次重复上面的步骤去获取配置信息了。

  11. 检索Jar包中的静态资源。

  12. 将ServletContainerInitializer配置到上下文。

在上面这些步骤中,本片文章关系的入口在第9步,即Tomcat是如何将Web配置应用到上下文的。

二、根据web.xml配置装配Servlet上下文

我们跟着WebXml的configureContext进入方法的实现,这里我按顺序摘抄几个源码片段并说明:

for (Entry<String, String> entry : contextParams.entrySet()) {
    context.addParameter(entry.getKey(), entry.getValue());
}
for (FilterDef filter : filters.values()) {
    if (filter.getAsyncSupported() == null) {
        filter.setAsyncSupported("false");
    }
    context.addFilterDef(filter);
}
for (FilterMap filterMap : filterMaps) {
    context.addFilterMap(filterMap);
}
for (String listener : listeners) {
    context.addApplicationListener(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());
}

从上面的代码我们至少可以总结下面值得注意的两点:

  1. Servlet容器对上下文参数、监听器、过滤器、Servlet的装配顺序为:上下文参数->过滤器->监听器->Servlet。

  2. Servlet支持容器启动时加载、是否异步配置以及配置覆盖。

三、组件的初始化

下面转入StandardContext这个类,StandardContext是Servlet上下文的标准实现,标准实现在Tomcat里面有一个系列,包括StandardServer、StandardService、StandardEngine、StandardHost等等,这些都是Tomcat不同级别的容器的标准实现。

我们可以直接定位到startInternal这个方法的实现,我们看下我们关系的部分步骤:

  1. 第一个是(Set up the context init params),这里我就不翻译了。

  2. 解析来的是Call ServletContainerInitializer,这里是值得web中间件开发者注意的,我们可以通过自定义ServletContainerInitializer服务来做一些组件初始化之前的事情,如在这个环节动态装配组件?获取容器上下文?

  3. Configure and call application event listeners,包括下面的error信息(Error listenerStart)这里是很重要的一步,有经验的开发者肯定会对这个error信息有点熟悉,应用起不来?呵呵……,提一下熟悉Spring的同学都知道ContextLoaderListener这个东西,Spring就是将对Spring容器的初始化工作放在的这个监听器里面实现的,包括对Spring配置文件的解析,容器初始化……

  4. Configure and call application filters,和error信息:Error filterStart。这里是对Filter进行了初始化。

  5. Load and initialize all "load on startup" servlets。这里对配置了load on startup的Servlet进行初始化。

我想介绍的主要就是上面的五个步骤了,总结一下主要组件的初始化顺序为:上下文参数->监听器->过滤器->Servlet。

这里有一点不舒服的地方是Tomcat对着三个组件的装配和初始化顺序有点差别。无耻的贴一点代码一起欣赏下:

// Create context attributes that will be required
if (ok) {
    getServletContext().setAttribute(
            JarScanner.class.getName(), getJarScanner());
}

// Set up the context init params
mergeParameters();

// Call 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;
    }
}

// Configure and call application event listeners
if (ok) {
    if (!listenerStart()) {
        log.error( "Error listenerStart");
        ok = false;
    }
}

// Configure and call application filters
if (ok) {
    if (!filterStart()) {
        log.error("Error filterStart");
        ok = false;
    }
}

// Load and initialize all "load on startup" servlets
if (ok) {
    if (!loadOnStartup(findChildren())){
        log.error("Error loadOnStartup");
        ok = false;
    }
}

四、过滤器的执行顺序

过滤器装配的时候主要涉及到三个数据结构:filters、filterMaps、以及filterMappingNames。我们分析下,如果根据请求来执行过滤器链的话,那么我们肯定是需要映射规则的,因此我们锁定filterMaps这个数据,查找下findFilterMaps这个方法哪里调用就好了。果然我们在ApplicaitonFilterFactory里面找到了下面这个片段:

// Acquire the filter mappings for this Context
StandardContext context = (StandardContext) wrapper.getParent();
FilterMap filterMaps[] = context.findFilterMaps();

这里其实是按照FilterMapping的配置来构造过滤器链的,那么我们深刻的了解到了一点,请求过滤链的顺序为FilterMapping的配置顺序。其中有行代码值得注意:

filterChain.setServlet(servlet);

在创建过滤器链的方法实现里面,Servlet也被放进去了。

五、过滤器链以及Servlet的最终执行

我们拿到过滤器链之后顺藤摸瓜,找到调用createFilterChain的地方,在StandardWrapperValve类里面(这里又是一个标准实现)。贴一个片段:

// Call the filter chain for this request
// NOTE: This also calls the servlet's service() method
try {
    if ((servlet != null) && (filterChain != null)) {
        // Swallow output if needed
        if (context.getSwallowOutput()) {
            try {
                SystemLogHandler.startCapture();
                if (request.isAsyncDispatching()) {
                    //TODO SERVLET3 - async
                    ((AsyncContextImpl)request.getAsyncContext()).doInternalDispatch(); 
                } else if (comet) {
                    filterChain.doFilterEvent(request.getEvent());
                    request.setComet(true);
                } else {
                    filterChain.doFilter(request.getRequest(), 
                            response.getResponse());
                }
            } finally {
                String log = SystemLogHandler.stopCapture();
                if (log != null && log.length() > 0) {
                    context.getLogger().info(log);
                }
            }

上面的Comments可告诉我们过滤器链在这里执行,而且值得注意的是Servlet也是在这里面执行的。我从ApplicationFilterChain里面捞出了两句英文,大家慢慢体会:

  1. Call the next filter if there is one.

  2. We fell off the end of the chain -- call the servlet instance.

Tomcat的过滤器链是一种典型的责任链模式的实践,组织的也还算精巧,到此我们的分析已经结束了,相信通过对源码的分析,我们可以对web.xml有了更深刻的了解。

本片文章是基于apache-tomcat7.0.56版本源代码,由作者原创,如果有我没有讲到的地方欢迎大家在评论里面补充。


作者:陆晨

于2016年1月1日(元旦)

共有 人打赏支持
粉丝 184
博文 46
码字总数 102357
×
Float_Luuu
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: