文档章节

Tomcat源码深析之web.xml组件的处理

Float_Luuu
 Float_Luuu
发布于 2016/01/01 21:52
字数 1991
阅读 2031
收藏 26

    这篇文章主要是带着读者通过分析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日(元旦)

© 著作权归作者所有

共有 人打赏支持
Float_Luuu
粉丝 208
博文 47
码字总数 104674
作品 0
长宁
高级程序员
私信 提问
StandardContext分析-tomcat6.x源码阅读

2013-10-06 StandardContext 是什么 是org.apache.catalina.Context的标准实现,继承自ContainerBase基容器,具备容器的功能,在tomcat结构层次图中位于Host内部,包含ServletWrapper容器,它...

douglaswei
2013/10/20
0
0
StandardWrapper分析-tomcat6.x源码阅读

2013-10-20 StandardWrapper是什么StandardWrapper是负责对Servlet的封装,在tomcat的结构层次中属于最内层,跟Servlet最接近的组件,是装载Servlet的容器,StandardWrapper没有子容器,因为...

douglaswei
2013/11/19
0
0
servlet3异步原理与实践

一、什么是Servlet servlet 是基于 Java 的 Web 组件,由容器进行管理,来生成动态内容。像其他基于 Java 的组件技术一样,servlet 也是基于平台无关的 Java 类格式,被编译为平台无关的字节...

新栋BOOK
2017/10/24
0
5
【整理】WEB 容器、WEB服务和应用服务器的区别与联系

对于一个不了解 WEB 开发的人来说,下面的概念是为了免于被别人鄙视和忽悠的~~ 【web 容器】 何为容器: 容器是一种服务调用规范框架,J2EE 大量运用了容器和组件技术来构建分层的企业级应用...

摩云飞
2013/11/22
0
3
Java Web(一) Servlet详解!!

一、什么是servlet?     处理请求和发送响应的过程是由一种叫做Servlet的程序来完成的,并且Servlet是为了解决实现动态页面而衍生的东西。理解这个的前提是了解一些http协议的东西,并且...

architect刘源源
05/08
0
0

没有更多内容

加载失败,请刷新页面

加载更多

phpstorm xdebug 配置

xdebug方便了调试代码,比起一个一个地方的打印结果还是debug看的更明白下面介绍下maxOS系统下的debug配置 下载 https://xdebug.org/download.php 点击红线部分进入,粘贴phpinfo()信息推荐适...

被猪拱了的JAVA
24分钟前
2
0
Golang学习笔记(1)

基本知识 golang的文件格式以go结尾。 运行方式 go run main.go 用于开发调试使用 编译成二进制文件 go build main.go 会生成一个可执行的二进制文件 变量 变量定义的形式 Golang的变量定义有...

ExtreU
37分钟前
1
0
基于Kafka构建事件溯源模式的微服务

概要 本文中我们将讨论如何借助Kafka实现分布式消息管理,使用事件溯源(Event Sourcing)模式实现原子化数据处理,使用CQRS模式(Command-Query Responsibility Segregation )实现查询职责...

架构师springboot
44分钟前
1
0
git上传项目步骤

https://blog.csdn.net/m0_37725003/article/details/80904824

fame_yao
44分钟前
1
0
NOOBS自定义安装多系统

一、预置条件: 宿主系统是win10_x64 virtual box 虚拟机,安装了centos7 树莓派的系统安装工具:NOOBS_v2_9_0.zip,镜像文件 二、根据镜像文件生成boot.tar.xz 和root.tar.xz 1、设置共享目...

mbzhong
55分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部