文档章节

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

Float_Luuu
 Float_Luuu
发布于 2016/01/01 21:52
字数 1991
阅读 1947
收藏 26
点赞 2
评论 0

    这篇文章主要是带着读者通过分析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
粉丝 199
博文 46
码字总数 102357
作品 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
Java Web(一) Servlet详解!!

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

architect刘源源
05/08
0
0
【整理】WEB 容器、WEB服务和应用服务器的区别与联系

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

摩云飞
2013/11/22
0
3
六、JSP的由来以及与Servlet的关系

JavaWeb在有了Servlet这项技术以后,就可以编写动态网页了。在动态网页中,一般来说样式是不变的,变化的是数据。如果程序员在Servlet类中写了大量的静态代码,例如out.println("<html>");这...

Wakeeee_
07/09
0
0
tomcat的url-pattern的源码分析

1 静态文件的处理前言分析 最近想把SpringMVC对于静态资源的处理策略弄清楚,如它和普通的请求有什么区别吗? 有人可能就要说了,现在有些静态资源都不是交给这些框架来处理,而是直接交给容...

乒乓狂魔
2015/03/10
0
2
JavaEE细节问题04——Servlet细节问题

1.<url-patten>的优先级问题 大家都知道,在web.xml中可以通过配置<servlet>和<servlet-mapping>来让一个servlet真正跑起来,而让服务器通过URL定位到 具体serlvet的标签就是通过<url-patten......

Lunqi
2015/08/17
0
0
Tomcat,JBoss与JBoss Web

最近接触到应用服务器JBoss,此外JBoss Web与Tomcat也同为web服务器,便查阅资料对三者进行比较,供大家参考。 一、Tomcat Tomcat 服务器是免费开源的Web 应用服务器。支持最新的Servlet 和J...

thinkyoung
2014/11/16
0
0
[转]通俗易懂Tomcat中Servlet的生命周期

我在上一篇文章里详细的介绍了 HTTP协议工作的流程,其中最重要的就是如何理解HTTP请求头和HTTP响应头,现在在这里再来详细的说明Tomcat 容器(即Servlet 容器)到底是如何 管理Servlet的,S...

穿越星辰
2010/05/13
0
2

没有更多内容

加载失败,请刷新页面

加载更多

下一页

OSChina 周一乱弹 —— 你的朋友圈有点生锈了

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @Devoes :分享Trademark的单曲《Only Love (电视剧《妙手仁心 II》插曲)》: 《Only Love (电视剧《妙手仁心 II》插曲)》- Trademark 手机党少...

小小编辑
58分钟前
69
5
【面试题】盲人坐飞机

有100位乘客乘坐飞机,其中有一位是盲人,每位乘客都按自己的座位号就坐。由于盲人看不见自己的座位号,所以他可能会坐错位置,而自己的座位被占的乘客会随便找个座位就坐。问所有乘客都坐对...

garkey
今天
1
0
谈谈神秘的ES6——(二)ES6的变量

谈谈神秘的ES6——(二)ES6的变量 我们在《零基础入门JavaScript》的时候就说过,在ES5里,变量是有弊端的,我们先来回顾一下。 首先,在ES5中,我们所有的变量都是通过关键字var来定义的。...

JandenMa
今天
1
0
arts-week1

Algorithm 594. Longest Harmonious Subsequence - LeetCode 274. H-Index - LeetCode 219. Contains Duplicate II - LeetCode 217. Contains Duplicate - LeetCode 438. Find All Anagrams ......

yysue
今天
1
0
NNS拍卖合约

前言 关于NNS的介绍,这里就不多做描述,相关的信息可以查看NNS的白皮书http://doc.neons.name/zh_CN/latest/nns_background.html。 首先nns中使用的竞价货币是sgas,关于sgas介绍可以戳htt...

红烧飞鱼
今天
1
0
Java IO类库之管道流PipeInputStream与PipeOutputStream

一、java管道流介绍 在java多线程通信中管道通信是一种重要的通信方式,在java中我们通过配套使用管道输出流PipedOutputStream和管道输入流PipedInputStream完成线程间通信。多线程管道通信的...

老韭菜
今天
0
0
用Python绘制红楼梦词云图,竟然发现了这个!

Python在数据分析中越来越受欢迎,已经达到了统计学家对R的喜爱程度,Python的拥护者们当然不会落后于R,开发了一个个好玩的数据分析工具,下面我们来看看如何使用Python,来读红楼梦,绘制小...

猫咪编程
今天
1
0
Java中 发出请求获取别人的数据(阿里云 查询IP归属地)

1.效果 调用阿里云的接口 去定位IP地址 2. 代码 /** * 1. Java中远程调用方法 * http://localhost:8080/mavenssm20180519/invokingUrl.action * @Title: invokingUrl * @Description: * @ret......

Lucky_Me
今天
1
0
protobuf学习笔记

相关文档 Protocol buffers(protobuf)入门简介及性能分析 Protobuf学习 - 入门

OSC_fly
昨天
0
0
Mybaties入门介绍

Mybaties和Hibernate是我们在Java开发中应用的比较多的两个ORM框架。当然,目前Mybaties正在慢慢取代Hibernate,这是因为相比较Hibernate而言Mybaties性能更好,响应更快,更加灵活。我们在开...

王子城
昨天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部