死磕Tomcat7源码之一:解析web.xml

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介:

熟悉java web开发的同学都清楚,tomcat作为一款非常流行的servlet容器,开源,流行,配置简单,不需要赘述。个人认为,web.xml作为webapp的入口,弄清楚该文件的底层解析过程,进而可以窥探tomcat的底层工作机制,搞明白tomcat对servlert规范的实现机理。

通过本文,可以知道以下部分内容

  • webapp部署3种部署方式

  • webapp web.xml解析流程

  • webapp Context对象信息的生成(不包括对象的生成)

总体来说,webapp部署有三种方式:XML文件描述符、WAR包、文件目录。三种方式部署的总体流程很相似,都是一个web应用分配一个线程来处理,这里统一放到与Host内部的线程池对象中(startStopExecutor),所以有时会看到在默认配置下Tomcat启动后可能有一个叫“-startStop-”的线程还会运行一段时间才结束。但浏览这三种部署方式的实现代码,里面都是构建一个Context对象,并将构建好的Context对象与Host组件关联起来


1.三种部署方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
     /**
      * 部署应用,该方法被org.apache.catalina.startup.HostConfig.start()调用
      * 包含3种部署方式,每个部署方式分别使用内部类,分配一个线程处理
      */
     protected  void  deployApps() {
 
         File appBase = appBase();
         File configBase = configBase();
         String[] filteredAppPaths = filterAppPaths(appBase.list());
         // Deploy XML descriptors from configBase
         deployDescriptors(configBase, configBase.list());
         // Deploy WARs
         deployWARs(appBase, filteredAppPaths);
         // Deploy expanded folders
         deployDirectories(appBase, filteredAppPaths);
 
     }

3个部署的内部类

org.apache.catalina.startup.HostConfig.DeployWar
org.apache.catalina.startup.HostConfig.DeployDescriptor

org.apache.catalina.startup.HostConfig.DeployDirectory

2.从部署webapp到解析web.xml序列图

wKiom1dAKgbSQy6PAAB-RHTc2k0029.png

找了很多网上资料,对从部署webapp开始,到开始解析web.xml这一段处理过程,没找到相关资料。个人就花时间整理出这个序列图,填补下这方面的空缺。通过该图,可以很清楚的知道,这部分主要完成了2件事:

(1)启动StandardContext,并将context对象添加到StandardHost对象中。

(2)通过触发事件机制,开始Context的解析过程。

3.web.xml解析过程

接第二步骤中序列图,开始分析web.xml的解析过程。从ContextConfig开始。

3.1 org.apache.catalina.startup.ContextConfig.configureStart()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    /**
      * Process a "contextConfig" event for this Context.
      */
     protected  synchronized  void  configureStart() {
         // Called from StandardContext.start()
 
         webConfig();
 
         if  (ok) {
             validateSecurityRoles();
         }
         // Configure an authenticator if we need one
         if  (ok)
             authenticatorConfig();
         // Make our application available if no problems were encountered
         if  (ok)
             context.setConfigured( true );
         else  {
             log.error(sm.getString( "contextConfig.unavailable" ));
             context.setConfigured( false );
         }
     }

该方法通过调用webConfig(),具体完成解析工作,此外完成了安全验证相关内容。

3.2 org.apache.catalina.startup.ContextConfig.webConfig()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
    /**
      * 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.
      */
     protected  void  webConfig() {
        
         Set<WebXml> defaults =  new  HashSet<WebXml>();
         defaults.add(getDefaultWebXmlFragment());
         WebXml webXml = createWebXml();
 
         // Parse context level web.xml
         InputSource contextWebXml = getContextWebXmlSource();
         parseWebXml(contextWebXml, webXml,  false ); //解析
 
         ServletContext sContext = context.getServletContext();
 
         // Ordering is important here
         // Step 1. Identify all the JARs packaged with the application
         // If the JARs have a web-fragment.xml it will be parsed at this
         // point.
         Map<String,WebXml> fragments = processJarsForWebFragments(webXml);
 
         // Step 2. Order the fragments.
         Set<WebXml> orderedFragments =  null ;
         orderedFragments =
                 WebXml.orderWebFragments(webXml, fragments, sContext);
 
         // Step 3. Look for ServletContainerInitializer implementations
         if  (ok) {
             processServletContainerInitializers();
         }
 
 
             // Step 5. Process JARs for annotations - only need to process
             // those fragments we are going to use
             if  (ok) {
                 processAnnotations(
                         orderedFragments, webXml.isMetadataComplete());
             }
 
             // Cache, if used, is no longer required so clear it
             javaClassCache.clear();
         }
                     // Step 6. Merge web-fragment.xml files into the main web.xml
                     // Step 7. Apply global defaults
               webXml.merge(defaults);
                // Step 8. Convert explicitly mentioned jsps to servlets
                     convertJsps(webXml);
                      // Step 9. Apply merged web.xml to Context
                     webXml.configureContext(context);
 
         // Step 9a. Make the merged web.xml available to other
         // components, 
 
         // Always need to look for static resources
         // Step 10. Look for static resources packaged in JARs
                 processResourceJARs(resourceJars);
                 // See also StandardContext.resourcesStart() for
                 // WEB-INF/classes/META-INF/resources configuration
 
         // Step 11. Apply the ServletContainerInitializer config to the
         // context
                     context.addServletContainerInitializer(
                             entry.getKey(), entry.getValue());
     }

通过源码中的注释,step1 到step11。主要工作包含:1.解析xml,2.合并xml,3.组装Context,4.编译JSP。具体步骤,参考说明

  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配置到上下文。

3.3 org.apache.catalina.startup.ContextConfig.parseWebXml方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
     /**
      * Parses the given source and stores the parsed data in the given web.xml
      * representation. The byte stream will be closed at the end of the parse
      * operation.
      *
      * @param source Input source containing the XML data to be parsed
      * @param dest The object representation of common elements of web.xml and
      *             web-fragment.xml
      * @param fragment Specifies whether the source is web-fragment.xml or
      *                 web.xml
      */
     protected  void  parseWebXml(InputSource source, WebXml dest,
             boolean  fragment) {
         XmlErrorHandler handler =  new  XmlErrorHandler();
 
         Digester digester;
         WebRuleSet ruleSet;
         if  (fragment) {
             digester = webFragmentDigester;
             ruleSet = webFragmentRuleSet;
         else  {
             digester = webDigester;
             ruleSet = webRuleSet;
         }
 
         digester.push(dest);
         digester.setErrorHandler(handler);
 
         digester.parse(source);
        }

使用Digester 对象即系web.xml,并将结果保存到WebXml对象中。

3.4 Digester的解析规则

(1)构造Digester.createWebXmlDigester

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
     public  void  createWebXmlDigester( boolean  namespaceAware,
             boolean  validation) {
 
         boolean  blockExternal = context.getXmlBlockExternal();
         
         webRuleSet =  new  WebRuleSet( false );
         webDigester = DigesterFactory.newDigester(validation,
                 namespaceAware, webRuleSet, blockExternal);
         webDigester.getParser();
 
         webFragmentRuleSet =  new  WebRuleSet( true );
         webFragmentDigester = DigesterFactory.newDigester(validation,
                 namespaceAware, webFragmentRuleSet, blockExternal);
         webFragmentDigester.getParser();
     }

(2)配置解析规则 WebRuleSet.addRuleInstances

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
   /**
      * <p>Add the set of Rule instances defined in this RuleSet to the
      * specified <code>Digester</code> instance, associating them with
      * our namespace URI (if any).  This method should only be called
      * by a Digester instance.</p>
      *
      * @param digester Digester instance to which the new Rule instances
      *  should be added.
      */
     @Override
     public  void  addRuleInstances(Digester digester) {
         digester.addRule(fullPrefix,
                          new  SetPublicIdRule( "setPublicId" ));
         digester.addRule(fullPrefix,
                          new  IgnoreAnnotationsRule());
         digester.addRule(fullPrefix,
                 new  VersionRule());
 
         // Required for both fragments and non-fragments
         digester.addRule(fullPrefix +  "/absolute-ordering" , absoluteOrdering);
         digester.addRule(fullPrefix +  "/ordering" , relativeOrdering);
 
         if  (fragment) {
             // web-fragment.xml
             digester.addRule(fullPrefix +  "/name" , name);
             digester.addCallMethod(fullPrefix +  "/ordering/after/name" ,
                                    "addAfterOrdering" 0 );
             digester.addCallMethod(fullPrefix +  "/ordering/after/others" ,
                                    "addAfterOrderingOthers" );
             digester.addCallMethod(fullPrefix +  "/ordering/before/name" ,
                                    "addBeforeOrdering" 0 );
             digester.addCallMethod(fullPrefix +  "/ordering/before/others" ,
                                    "addBeforeOrderingOthers" );
         else  {
             // web.xml
             digester.addCallMethod(fullPrefix +  "/absolute-ordering/name" ,
                                    "addAbsoluteOrdering" 0 );
             digester.addCallMethod(fullPrefix +  "/absolute-ordering/others" ,
                                    "addAbsoluteOrderingOthers" );
         }
 
         digester.addCallMethod(fullPrefix +  "/context-param" ,
                                "addContextParam" 2 );
         digester.addCallParam(fullPrefix +  "/context-param/param-name" 0 );
         digester.addCallParam(fullPrefix +  "/context-param/param-value" 1 );
 
         digester.addObjectCreate(fullPrefix +  "/filter" ,
                                  "org.apache.catalina.deploy.FilterDef" );
         digester.addSetNext(fullPrefix +  "/filter" ,
                             "addFilter" ,
                             "org.apache.catalina.deploy.FilterDef" );
 
         digester.addCallMethod(fullPrefix +  "/filter/description" ,
                                "setDescription" 0 );
         digester.addCallMethod(fullPrefix +  "/filter/display-name" ,
                                "setDisplayName" 0 );
         digester.addCallMethod(fullPrefix +  "/filter/filter-class" ,
                                "setFilterClass" 0 );
         digester.addCallMethod(fullPrefix +  "/filter/filter-name" ,
                                "setFilterName" 0 );
         digester.addCallMethod(fullPrefix +  "/filter/icon/large-icon" ,
                                "setLargeIcon" 0 );
         digester.addCallMethod(fullPrefix +  "/filter/icon/small-icon" ,
                                "setSmallIcon" 0 );
         digester.addCallMethod(fullPrefix +  "/filter/async-supported" ,
                 "setAsyncSupported" 0 );
 
         digester.addCallMethod(fullPrefix +  "/filter/init-param" ,
                                "addInitParameter" 2 );
         digester.addCallParam(fullPrefix +  "/filter/init-param/param-name" ,
                               0 );
         digester.addCallParam(fullPrefix +  "/filter/init-param/param-value" ,
                               1 );
 
         digester.addObjectCreate(fullPrefix +  "/filter-mapping" ,
                                  "org.apache.catalina.deploy.FilterMap" );
         digester.addSetNext(fullPrefix +  "/filter-mapping" ,
                                  "addFilterMapping" ,
                                  "org.apache.catalina.deploy.FilterMap" );
 
         digester.addCallMethod(fullPrefix +  "/filter-mapping/filter-name" ,
                                "setFilterName" 0 );
         digester.addCallMethod(fullPrefix +  "/filter-mapping/servlet-name" ,
                                "addServletName" 0 );
         digester.addCallMethod(fullPrefix +  "/filter-mapping/url-pattern" ,
                                "addURLPattern" 0 );
 
         digester.addCallMethod(fullPrefix +  "/filter-mapping/dispatcher" ,
                                "setDispatcher" 0 );
 
          digester.addCallMethod(fullPrefix +  "/listener/listener-class" ,
                                 "addListener" 0 );
          
      ...
     }

在这个方法里,可以看到熟悉的“/servlet/servlet-name”,"/listener/listener-class"等等。稍微懂点Digester解析语法的基础的朋友,立刻可以知道这儿就是解析规则所在,Digester解析web.xml规则都是在此配置的。进一步梳理下,可以弄明白servlet,filter等重要对象的数据载体。

标签
数据载体类
/filter
org.apache.catalina.deploy.FilterDef
/error-page
org.apache.catalina.deploy.ErrorPage
/servlet

org.apache.catalina.deploy.ServletDef

/filter-mapping org.apache.catalina.deploy.FilterMap
/login-config org.apache.catalina.deploy.LoginConfig
/session-config org.apache.catalina.deploy.SessionConfig
...
...


部分私有内部Rule列表

wKiom1dAN13QajJHAAAsoLDJMdw744.png


通过分析,可以知道web.xml通过解析之后,配置信息都保存在WebXml对象中了。

WebXml中持有FilterMap,ServletDef,FilterDef等等对象的聚集信息。接下来tomcat就可以按照servlet规范初始化里面的组件了,有空将进一步介绍。



本文转自 randy_shandong 51CTO博客,原文链接:http://blog.51cto.com/dba10g/1775723,如需转载请自行联系原作者

相关文章
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
71 2
|
18天前
|
PyTorch Shell API
Ascend Extension for PyTorch的源码解析
本文介绍了Ascend对PyTorch代码的适配过程,包括源码下载、编译步骤及常见问题,详细解析了torch-npu编译后的文件结构和三种实现昇腾NPU算子调用的方式:通过torch的register方式、定义算子方式和API重定向映射方式。这对于开发者理解和使用Ascend平台上的PyTorch具有重要指导意义。
|
22天前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
50 12
|
1月前
|
存储 安全 Linux
Golang的GMP调度模型与源码解析
【11月更文挑战第11天】GMP 调度模型是 Go 语言运行时系统的核心部分,用于高效管理和调度大量协程(goroutine)。它通过少量的操作系统线程(M)和逻辑处理器(P)来调度大量的轻量级协程(G),从而实现高性能的并发处理。GMP 模型通过本地队列和全局队列来减少锁竞争,提高调度效率。在 Go 源码中,`runtime.h` 文件定义了关键数据结构,`schedule()` 和 `findrunnable()` 函数实现了核心调度逻辑。通过深入研究 GMP 模型,可以更好地理解 Go 语言的并发机制。
|
1月前
|
JSON JavaScript 前端开发
蓝桥杯web组赛题解析和杯赛技巧
本文作者是一位自学前端两年半的大一学生,在第十五届蓝桥杯Web组比赛中获得省一和国三。文章详细解析了比赛题纲,涵盖HTML、CSS、JavaScript、Echarts和Vue等技术要点,并分享了备赛技巧和比赛经验。作者强调了多写代码和解题思路的重要性,同时提供了省赛和国赛的具体流程及注意事项。希望对参赛者有所帮助。
77 3
|
1月前
|
安全 前端开发 Java
Web安全进阶:XSS与CSRF攻击防御策略深度解析
【10月更文挑战第26天】Web安全是现代软件开发的重要领域,本文深入探讨了XSS和CSRF两种常见攻击的原理及防御策略。针对XSS,介绍了输入验证与转义、使用CSP、WAF、HTTP-only Cookie和代码审查等方法。对于CSRF,提出了启用CSRF保护、设置CSRF Token、使用HTTPS、二次验证和用户教育等措施。通过这些策略,开发者可以构建更安全的Web应用。
94 4
|
1月前
|
安全 Go PHP
Web安全进阶:XSS与CSRF攻击防御策略深度解析
【10月更文挑战第27天】本文深入解析了Web安全中的XSS和CSRF攻击防御策略。针对XSS,介绍了输入验证与净化、内容安全策略(CSP)和HTTP头部安全配置;针对CSRF,提出了使用CSRF令牌、验证HTTP请求头、限制同源策略和双重提交Cookie等方法,帮助开发者有效保护网站和用户数据安全。
75 2
|
1月前
|
消息中间件 缓存 安全
Future与FutureTask源码解析,接口阻塞问题及解决方案
【11月更文挑战第5天】在Java开发中,多线程编程是提高系统并发性能和资源利用率的重要手段。然而,多线程编程也带来了诸如线程安全、死锁、接口阻塞等一系列复杂问题。本文将深度剖析多线程优化技巧、Future与FutureTask的源码、接口阻塞问题及解决方案,并通过具体业务场景和Java代码示例进行实战演示。
54 3
|
2月前
|
前端开发 开发者 容器
构建响应式Web界面:Flexbox与Grid布局的深度解析
【10月更文挑战第11天】本文深入解析了CSS3中的Flexbox和Grid布局,探讨了它们的特点、应用场景及使用方法。Flexbox适用于一维布局,如导航栏;Grid布局则适用于二维布局,如复杂网格。通过示例代码和核心属性介绍,帮助开发者灵活构建响应式Web界面。
60 5
|
2月前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
76 0

推荐镜像

更多