合作联系
  wanwqing@vip.163.com
  0790-XXXXXXX
关于实验室
web.xml是如何被解析的
发布者:实验室   时间:2019-02-02 13:18:44   浏览次数:121

Java Web 开发人员进行应用开发的时候,无论是从头开始用Servlet、Jsp开发,还是使用SpringMVC、Spring等框架,都会在web.xml配置文件中声明一系列的Listener,Filter、或者是负责前端分发的DispatcherServlet。应该说对web.xml都相当熟悉。

虽然后来一些新的框架提供了零配置的概念,像SpringMVC的EnableMvcConfig可以把web.xml中的DispatcherServlet以注解的形式声明,其实质也是使用了应用服务器中实现的动态Servlet的新特性。而先理解了web.xml中的配置,对于使用注解声明也会更清晰易懂些。

本篇先来通过代码分析应用的web.xml是何时解析生效的,以及其和应用服务器自行包含的默认web.xml又是如何整合到一起的。(关于应用服务器默认web.xml文件,之前的文章曾有过介绍,可以在后台回复关键字004查看)

有个小伙遇到一个这样的问题:

他在conf/web.xml中声明了servlet,并且设置了load-on-startup属性,此时遇到了classNotFoundException的问题。

 

那为什么会出现这个异常呢?其实出现这个异常是情理之中的。如果看过前面介绍默认web.xml文章的朋友应该都了解:

conf/web.xml这个文件是一个全局配置文件,配置的是一些通用的属性,类似于MIME类型,session超时时间等信息。

而这个文件,最终会和应用自己的web.xml文件merge到一起的。这也就是问题产生的原因:各性化配置的servlet类的信息并不会在每个应用下都包含,所以加载不到是正常现象。

基本原因说明了之后,我们来看Tomcat内部是如何解析web.xml文件的。

解析web.xml文件代码,位于ContextConfig类内。

解析代码的注释说明了整个功能

/**
* Scan the web.xml files that apply to the web application and merge them
* using the rules defined in the spec. For the global web.xml files,
* where there is duplicate configuration, the most specific level wins. ie
* an application's web.xml takes precedence over the host level or global
* web.xml file.
*/

 

在merge的过程中,自定义的配置覆盖全局配置。

我们来看主要代码解析

Set<WebXml> defaults = new HashSet<>();
defaults.add(getDefaultWebXmlFragment()); //首先是默认web.xml
WebXml webXml = createWebXml();
// Parse context level web.xml
InputSource contextWebXml = getContextWebXmlSource();
if (!webXmlParser.parseWebXml(contextWebXml, webXml, false)) {
 ok = false;
}

 

再之后是Servlet3的新特性中的web-fragement特性。需要先解析已有jar包中是否包含自定义配置

// Ordering is important here
// Step 1. Identify all the JARs packaged with the application and those
// provided by the container. If any of the application JARs have a
// web-fragment.xml it will be parsed at this point. web-fragment.xml
// files are ignored for container provided JARs.
Map<String,WebXml> fragments = processJarsForWebFragments(webXml);
// Step 2. Order the fragments.
Set<WebXml> orderedFragments = null;
orderedFragments =
 WebXml.orderWebFragments(webXml, fragments, sContext);

 

之后则是把web-fragement.xml和web.xml合到一起。

 // Step 7. Apply global defaults
 // Have to merge defaults before JSP conversion since defaults
 // provide JSP servlet definition.
 webXml.merge(defaults); //merge全局web.xml,这里是先放进去,后续自定义的会覆盖

 

再向下走到merge应用自定义的web.xml中。

// Step 9. Apply merged web.xml to Context
if (ok) {
 configureContext(webXml);
}

 

而整个应用的web.xml解析和直观感受基本一致,逐个解析声明的各个组件,例如Filter和Listener的解析代码如下:

for (FilterDef filter : webxml.getFilters().values()) {
 if (filter.getAsyncSupported() == null) {
 filter.setAsyncSupported("false");
 }
 context.addFilterDef(filter);
}
for (FilterMap filterMap : webxml.getFilterMappings()) {
 context.addFilterMap(filterMap);
}
context.setJspConfigDescriptor(webxml.getJspConfigDescriptor());
for (String listener : webxml.getListeners()) {
 context.addApplicationListener(listener);
}

 

单独说下session配置的这一小部分:

SessionConfig sessionConfig = webxml.getSessionConfig();
if (sessionConfig != null) {
 if (sessionConfig.getSessionTimeout() != null) {
 context.setSessionTimeout(
 sessionConfig.getSessionTimeout().intValue());
 }
 SessionCookieConfig scc =
 context.getServletContext().getSessionCookieConfig();
 scc.setName(sessionConfig.getCookieName());
 scc.setDomain(sessionConfig.getCookieDomain());
 scc.setPath(sessionConfig.getCookiePath());
 scc.setComment(sessionConfig.getCookieComment());
 if (sessionConfig.getCookieHttpOnly() != null) {
 scc.setHttpOnly(sessionConfig.getCookieHttpOnly().booleanValue());
 }

这里除了会覆盖全局配置文件中的session超时时间外,还可以声明SessionCookie的一些配置,就是我们前面文章提到的使用Cookie来保持Session的一种方式(后台回复关键字005查看)。我们看到可以定义名称,域等一系列属性。