1. ContextConfig的基本流程

同HostConfig一样,ContextConfig也是在初始化阶段由Digester解析server.xml的时候添加到StandardContext上的监听器,在后续的启动阶段,会触发调用这个之前创建的监听器:
在这里插入图片描述
初始化期间完成注册流程:

load() //catalina
->Digester digester = createStartDigester();  //catalina.load()
->digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/")); //catalina.createStartDigester()
->ruleSet.addRuleInstances(this); //Digester. addRuleSet()
-> digester.addRule(prefix + "Context",
                             new LifecycleListenerRule
                                 ("org.apache.catalina.startup.ContextConfig",
                                  "configClass"));  //ContextRuleSet.addRuleInstances()
->完成注册                                  

ContextConfig 实现了LifecycleListener,就是一个监听器的实例:

public class ContextConfig implements LifecycleListener{
  public void lifecycleEvent(LifecycleEvent event){
     ....
     do many things here!
  }

}
//定义前缀"Server/Service/Engine/Host/",后面会拼接这个前缀,作为容器对象的路径的前缀
digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/"));
//拼接表示容器的全路径信息
//Server/Service/Engine/Host/Context表示context需要添加一个ContextConfig 监听器
//最终加到context实现类StandardContext的集合属性lifecycleListeners中(该属性继承自父接口)
digester.addRule(prefix + "Context",
                             new LifecycleListenerRule
                                 ("org.apache.catalina.startup.ContextConfig",
                                  "configClass")); 

触发解析的流程,位于启动流程内:

startInternal()  //StandardContext
->fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);  //StandardContext.startInternal() 
->      for (LifecycleListener listener : lifecycleListeners) {
            listener.lifecycleEvent(event);//递归
        }   //LifecycleBase .fireLifecycleEvent()
->ContextConfig实例的fireLifecycleEvent()
->       if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
            configureStart(); //消息类型为CONFIGURE_START_EVENT时
        } //ContextConfig.fireLifecycleEvent()
 

调用 listener.lifecycleEvent(event);时,注意event=start
参见入参的源头代码: fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);

2. ContextConfig的核心功能

ContextConfig主要是处理web应用的配置文件:

org.apache.catalina.startup.ContextConfig {
    //监听器调用的入口
    public void lifecycleEvent(LifecycleEvent event){
         
         if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
            //根据入参,会走到该分支
            configureStart();
        } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
            beforeStart();
        } else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
            // Restore docBase for management tools
            if (originalDocBase != null) {
                context.setDocBase(originalDocBase);
            }
        } 
        ...
     }
     
     protected synchronized void configureStart() {
        // Called from StandardContext.start()
        //调用此处方法
        webConfig();
     }
     
      protected void webConfig() {
        //xml解析器
        WebXmlParser webXmlParser = new WebXmlParser(context.getXmlNamespaceAware(),
                context.getXmlValidation(), context.getXmlBlockExternal());

        Set<WebXml> defaults = new HashSet<>();
        defaults.add(getDefaultWebXmlFragment(webXmlParser));
        
        //创建web.xml格式的对象,此时属性都为空
        WebXml webXml = createWebXml();        
        
        // 创建web.xml文件流
        InputSource contextWebXml = getContextWebXmlSource();
          //解析流,把内容封装到webXml
        if (!webXmlParser.parseWebXml(contextWebXml, webXml, false)) {
            ok = false;
        }
        
        //处理web-fragment.xml文件,分多个步骤
        //Step 1. 扫描/META-INF/lib/目录下的jar文件,如果在META-INF下含有web-fragment.xml文件,解析它
        Map<String,WebXml> fragments = processJarsForWebFragments(webXml, webXmlParser);

        // Step 2. 确定确定这些xml片段的顺序
        Set<WebXml> orderedFragments = null;
        orderedFragments =
                WebXml.orderWebFragments(webXml, fragments, sContext);

        // Step 3. 处理ServletContainerInitializers的实现类
        if (ok) {
            processServletContainerInitializers();
        }

         if  (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) {
            // Steps 4 & 5 都在processClasses里面,提前讲下
            //Steps 4 处理/WEB-INF/classes 下的注解类和 @HandlesTypes matches
            //Steps 5 处理 引入的JARs中的注解类和 @HandlesTypes matches
            processClasses(webXml, orderedFragments);
        }

        if (!webXml.isMetadataComplete()) {
            // Step 6. 将应用中的web.xml与orderedFragments进行合并,合并在WebXml类的merge方法中实现
            
            if (ok) {
                ok = webXml.merge(orderedFragments);
            }

            // Step 7. 将应用中的web.xml与全局的web.xml文件(conf/web.xml和web.xml.default)进行合并
            // Have to merge defaults before JSP conversion since defaults
            // provide JSP servlet definition.
            webXml.merge(defaults);
            
            // Step 8. Convert explicitly mentioned jsps to servlets
            if (ok) {
                convertJsps(webXml);
            }

            // Step 9. 通过webXml对象,间接把web.xml构建成 Context对象
            if (ok) {
                configureContext(webXml);
            }
      
      }
1、扫描/META-INF/lib/目录下的jar文件,如果在META-INF下含有web-fragment.xml文件,解析它;
2、确定确定这些xml片段的顺序
3、处理ServletContainerInitializers的实现类
4、将应用中的web.xml与orderedFragments进行合并,合并在WebXml类的merge方法中实现
5、将应用中的web.xml与全局的web.xml文件(conf/web.xml和web.xml.default)进行合并
6、用合并好的WebXml来配置Context,这一步在处理servlet时,会为每个servlet创建一个wrapper,并调用addChild将每个wrapper作为context子容器,后续分析

2.1 web-fragment.xml知识

servlet 3.0可以将配置文件分散在多个jar包里面,而且还可以定义这些配置文件的顺序。分为绝对顺序和相对顺序,绝对顺序是通过absolute-ordering标签定义的:

    <web-app>
        <name>...</name>
        <absolute-ordering>
            <name>fragment1</name>
            <name>fragment2</name>
        </absolute-ordering>
    </web-app>

还可以在web-fragment.xml里面通过before,after标签来定义这些配置文件的先后顺序,这里不再举例这步主要是根据顺序,将这些配置文件加到集合orderedFragments中。

2.2 ServletContainerInitializers的实现类

这也是servlet 3.0新增的特性,容器在启动时使用 JAR 服务 API(JAR Service API) 来发现 ServletContainerInitializer 的实现类,并且容器将 WEB-INF/lib 目录下 JAR 包中的类都交给该类的 onStartup() 方法处理,我们通常需要在该实现类上使用 @HandlesTypes 注解来指定希望被处理的类,过滤掉不希望给 onStartup() 处理的类。

在onStartup方法中可以优先加载这些类,或者修改其中的方法等。这步主要是把这些类找到放到Set<ServletContainerInitializer> scis中,进而放入typeInitializerMap;

 protected void processServletContainerInitializers() {
   Set<ServletContainerInitializer> scis =
            typeInitializerMap.get(type);
    if (scis == null) {
        scis = new HashSet<>();
        typeInitializerMap.put(type, scis);
    }
    scis.add(sci);

2.3 web.xml文件加载的细节

只要知道了web.xml加载路径信息即可:
在这里插入图片描述

参考:《Tomcat学习之ContextConfig》

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐