美文网首页
Tomcat源码分析 -- StandardContext的启动

Tomcat源码分析 -- StandardContext的启动

作者: w1992wishes | 来源:发表于2018-03-09 16:54 被阅读49次

    本篇结构:

    • 前言
    • StandContext的启动过程
    • ContextConfig
    • 总结

    一、前言

    根据上篇的介绍,我们知道创建StandContext的三种方式:

    直接解析server.xml中的Context标签创建、解析Context文件描述符(位于$CATALINA-BASE/conf/Catalina/localhost目录下或者META-INF目录下)、创建默认StandardContext(即没有Context文件描述符,又没在server.xml中配置)。

    了解了Web应用的创建和部署后,下面再来看看Web应用的初始化和启动工作。StandardHost和HostConfig只是根据不同情况创建Context对象,具体初始化和启动工作由组件Context自身完成。

    二、StandContext的启动过程

    Tomcat的生命周期机制告诉我们,一个组件的启动过程应该关注它的start方法,这个start方法是典型的模板方法设计模式。LifecycleBase是所有组件都继承的抽象类,该类提供了生命周期相关的通用方法,start()方法也可以在LifecycleBase中找到。

    观察start方法,在该方法中定义了组件启动的应进行的操作,又留出一个抽象方法startInternal()方法供子类实现组件自身的操作。

    所以来看StandContext的startInternal()方法。

    该方法所做的操作很多很复杂,下面简单列下,就不深究了。主要是:

    1.发布正在启动的JMX通知,这样可以通过NotificationListener来监听Web应用的启动。

    // Send j2ee.state.starting notification
    if (this.getObjectName() != null) {
        Notification notification = new Notification("j2ee.state.starting",
                this.getObjectName(), sequenceNumber.getAndIncrement());
        broadcaster.sendNotification(notification);
    }
    

    2.启动当前维护的JNDI资源。(哼,不懂JNDI)

    if (namingResources != null) {
        namingResources.start();
    }
    

    3.初始化临时工作目录,即设置的workDir,默认为$CATALINA-BASE/work/<Engine名称>/<Host名称>/<Context名称>

    postWorkDirectory();
    
    private void postWorkDirectory() {
    
    // Acquire (or calculate) the work directory path
    String workDir = getWorkDir();
    if (workDir == null || workDir.length() == 0) {
    
        // Retrieve our parent (normally a host) name
        String hostName = null;
        String engineName = null;
        String hostWorkDir = null;
        Container parentHost = getParent();
        if (parentHost != null) {
            hostName = parentHost.getName();
            if (parentHost instanceof StandardHost) {
                hostWorkDir = ((StandardHost)parentHost).getWorkDir();
            }
            Container parentEngine = parentHost.getParent();
            if (parentEngine != null) {
               engineName = parentEngine.getName();
            }
        }
        if ((hostName == null) || (hostName.length() < 1))
            hostName = "_";
        if ((engineName == null) || (engineName.length() < 1))
            engineName = "_";
    
        String temp = getBaseName();
        if (temp.startsWith("/"))
            temp = temp.substring(1);
        temp = temp.replace('/', '_');
        temp = temp.replace('\\', '_');
        if (temp.length() < 1)
            temp = ContextName.ROOT_NAME;
        if (hostWorkDir != null ) {
            workDir = hostWorkDir + File.separator + temp;
        } else {
            workDir = "work" + File.separator + engineName +
                File.separator + hostName + File.separator + temp;
        }
        setWorkDir(workDir);
    }
    

    4.初始化当前Context使用的WebResouceRoot并启动。WebResouceRoot维护了Web应用所以的资源集合(Class文件、Jar包以及其他资源文件),主要用于类加载器和按照路径查找资源文件。

    // Add missing components as necessary
    if (getResources() == null) {   // (1) Required by Loader
        if (log.isDebugEnabled())
            log.debug("Configuring default Resources");
    
        try {
            setResources(new StandardRoot(this));
        } catch (IllegalArgumentException e) {
            log.error(sm.getString("standardContext.resourcesInit"), e);
            ok = false;
        }
    }
    if (ok) {
        resourcesStart();
    }
    

    5.创建Web应用类加载器webappLoader,webappLoader继承自LifecycleMBeanBase,在其启动后会去创建Web应用类加载器(ParallelWebappClassLoader)。

    if (getLoader() == null) {
        WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
        webappLoader.setDelegate(getDelegate());
        setLoader(webappLoader);
    }
    

    同时webappLoader提供了backgroundProcess方法,用于Context后台处理,当检测到Web应用的类文件、Jar包发生变化时,重新加载Context。

    public void backgroundProcess() {
        if (reloadable && modified()) {
            try {
                Thread.currentThread().setContextClassLoader
                    (WebappLoader.class.getClassLoader());
                if (context != null) {
                    context.reload();
                }
            } finally {
                if (context != null && context.getLoader() != null) {
                    Thread.currentThread().setContextClassLoader
                        (context.getLoader().getClassLoader());
                }
            }
        }
    }
    

    6.如果没有设置Cookie处理器,默认为Rfc6265CookieProcessor。

    if (cookieProcessor == null) {
        cookieProcessor = new Rfc6265CookieProcessor();
    }
    

    7.设置字符集映射,用于根据Locale获取字符集编码。

    getCharsetMapper()
    
    public CharsetMapper getCharsetMapper() {
    
        // Create a mapper the first time it is requested
        if (this.charsetMapper == null) {
            try {
                Class<?> clazz = Class.forName(charsetMapperClass);
                this.charsetMapper = (CharsetMapper) clazz.getConstructor().newInstance();
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                this.charsetMapper = new CharsetMapper();
            }
        }
    
        return this.charsetMapper;
    
    }
    

    8.web应用的依赖检测。
    9.NamingContextListener注册。
    10.启动Web应用类加载器,此时真正创建出ParallelWebappClassLoader实例。

    Loader loader = getLoader();
    if (loader instanceof Lifecycle) {
        ((Lifecycle) loader).start();
    }
    

    11.启动安全组件。

    Realm realm = getRealmInternal();
    if(null != realm) {
        if (realm instanceof Lifecycle) {
            ((Lifecycle) realm).start();
        }
    

    12.发布CONFIGURE_START_EVENT事件,ContextConfig监听该事件以完成Servlet的创建。

    fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
    

    13.启动Context子节点Wrapper。

    for (Container child : findChildren()) {
        if (!child.getState().isAvailable()) {
            child.start();
        }
    }
    

    14.启动Context的pipeline。

    if (pipeline instanceof Lifecycle) {
        ((Lifecycle) pipeline).start();
    }
    

    15.创建会话管理器。

    Manager contextManager = null;
    Manager manager = getManager();
    if (manager == null) {
        if (log.isDebugEnabled()) {
            log.debug(sm.getString("standardContext.cluster.noManager",
                    Boolean.valueOf((getCluster() != null)),
                    Boolean.valueOf(distributable)));
        }
        if ( (getCluster() != null) && distributable) {
            try {
                contextManager = getCluster().createManager(getName());
            } catch (Exception ex) {
                log.error("standardContext.clusterFail", ex);
                ok = false;
            }
        } else {
            contextManager = new StandardManager();
        }
    }
    

    16.将Context的Web资源集合添加到ServletContext。

    if (ok)
        getServletContext().setAttribute
            (Globals.RESOURCES_ATTR, getResources());
    

    17.创建实例管理器instanceManager,用于创建对象实例,如Servlet、Filter等。

    if (ok ) {
        if (getInstanceManager() == null) {
            javax.naming.Context context = null;
            if (isUseNaming() && getNamingContextListener() != null) {
                context = getNamingContextListener().getEnvContext();
            }
            Map<String, Map<String, String>> injectionMap = buildInjectionMap(
                    getIgnoreAnnotations() ? new NamingResourcesImpl(): getNamingResources());
            setInstanceManager(new DefaultInstanceManager(context,
                    injectionMap, this, this.getClass().getClassLoader()));
        }
        getServletContext().setAttribute(
                InstanceManager.class.getName(), getInstanceManager());
        InstanceManagerBindings.bind(getLoader().getClassLoader(), getInstanceManager());
    }
    

    18.将Jar包扫描器添加到ServletContext。

    if (ok) {
        getServletContext().setAttribute(
                JarScanner.class.getName(), getJarScanner());
    }
    

    19.合并参数。

    private void mergeParameters() {
        Map<String,String> mergedParams = new HashMap<>();
    
        String names[] = findParameters();
        for (int i = 0; i < names.length; i++) {
            mergedParams.put(names[i], findParameter(names[i]));
        }
    
        ApplicationParameter params[] = findApplicationParameters();
        for (int i = 0; i < params.length; i++) {
            if (params[i].getOverride()) {
                if (mergedParams.get(params[i].getName()) == null) {
                    mergedParams.put(params[i].getName(),
                            params[i].getValue());
                }
            } else {
                mergedParams.put(params[i].getName(), params[i].getValue());
            }
        }
    
        ServletContext sc = getServletContext();
        for (Map.Entry<String,String> entry : mergedParams.entrySet()) {
            sc.setInitParameter(entry.getKey(), entry.getValue());
        }
    
    }
    

    20.启动添加到Context的ServletContainerInitializer。
    21.实例化应用类监听器ApplicationListener。

    if (ok) {
        if (!listenerStart()) {
            log.error(sm.getString("standardContext.listenerFail"));
            ok = false;
        }
    }
    

    22.启动会话管理器。

    Manager manager = getManager();
    if (manager instanceof Lifecycle) {
        ((Lifecycle) manager).start();
    }
    

    23.实例化FilterConfig、Filter并调用Filter.init()。

    if (ok) {
        if (!filterStart()) {
            log.error(sm.getString("standardContext.filterFail"));
            ok = false;
        }
    }
    

    24.对于loadOnStartup大于等于0的Wrapper,调用Wrapper.load(),该方法负责实例化Servlet,并调用Servlet.init()进行初始化。

    if (ok) {
        if (!loadOnStartup(findChildren())){
            log.error(sm.getString("standardContext.servletFail"));
            ok = false;
        }
    }
    

    25.启动后台定时处理程序,只有backgroundProcessorDelay>0才启动,用于监控守护文件的变更。

    // Start ContainerBackgroundProcessor thread
    super.threadStart();
    

    26.发布正在运行的JMX通知。

    // Send j2ee.state.running notification
    if (ok && (this.getObjectName() != null)) {
    Notification notification =
        new Notification("j2ee.state.running", this.getObjectName(),
                         sequenceNumber.getAndIncrement());
    broadcaster.sendNotification(notification);
    }
    

    27.释放资源,如关闭jar文件。

    // The WebResources implementation caches references to JAR files. On
    // some platforms these references may lock the JAR files. Since web
    // application start is likely to have read from lots of JARs, trigger
    // a clean-up now.
    getResources().gc();
    

    28.设置Context状态。

    if (!ok) {
        setState(LifecycleState.FAILED);
    } else {
        setState(LifecycleState.STARTING);
    }
    

    StandContext启动很复杂,涉及很多知识面,我是很模糊的,也不打算深究了。

    三、ContextConfig

    ContextConfig是创建Context时默认添的一个生命周期监听器。它监听6个事件,其中三个和Context启动关系密切:AFTER_INIT_EVENT、BEFORE_START_EVENT、CONFIGURE_START_EVENT。

    ContextConfig的lifecycleEvent()方法:

    public void lifecycleEvent(LifecycleEvent event) {
    
    // Identify the context we are associated with
    try {
        context = (Context) event.getLifecycle();
    } catch (ClassCastException e) {
        log.error(sm.getString("contextConfig.cce", event.getLifecycle()), e);
        return;
    }
    
    // Process the event that has occurred
    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);
        }
    } else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) {
        configureStop();
    } else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
        init();
    } else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
        destroy();
    }
    
    }
    

    3.1、AFTER_INIT_EVENT事件

    严格说,该事件属于Context事件初始化阶段,主要用于Context属性的配置工作。

    根据前面讲的,再来回顾一下Context的创建,有以下来源:

    1. 解析server.xml中的Context元素。
    2. 通过HostConfig部署Web应用时,解析Web应用(或者WAR包)根目录下的META-INF/context.xml文件。如果不存在,则自动创建一个默认的Context对象,只设置name,path,docBase等几个属性。
    3. 通诺HostConfig部署Web应用时,解析$CATALINA-BASE/conf/Catalina/localhost目录下的Context部署文件描述符创建。

    除了Context创建时的属性配置,Tomcat提供的默认配置也要一并添加到Context实例中,AFTER_INIT_EVENT事件就是要完成这部分工作的。

    来看该事件触发时执行的init()方法:

    protected void init() {
        // Called from StandardContext.init()
    
        Digester contextDigester = createContextDigester();
        contextDigester.getParser();
    
        if (log.isDebugEnabled()) {
            log.debug(sm.getString("contextConfig.init"));
        }
        context.setConfigured(false);
        ok = true;
    
        contextConfig(contextDigester);
    }
    

    init首先会创建createContextDigester创建解析规则,点进去看可以发现会回到之前讲Server解析时提到的ContextRuleSet,只不过这时传进去的create参数值为false。

    不多说,重点来看contextConfig()方法:

    protected void contextConfig(Digester digester) {
    
        String defaultContextXml = null;
    
        // Open the default context.xml file, if it exists
        if (context instanceof StandardContext) {
            defaultContextXml = ((StandardContext)context).getDefaultContextXml();
        }
        // set the default if we don't have any overrides
        if (defaultContextXml == null) {
            defaultContextXml = Constants.DefaultContextXml;
        }
    
        if (!context.getOverride()) {
            File defaultContextFile = new File(defaultContextXml);
            if (!defaultContextFile.isAbsolute()) {
                defaultContextFile =
                        new File(context.getCatalinaBase(), defaultContextXml);
            }
            if (defaultContextFile.exists()) {
                try {
                    URL defaultContextUrl = defaultContextFile.toURI().toURL();
                    processContextConfig(digester, defaultContextUrl);
                } catch (MalformedURLException e) {
                    log.error(sm.getString(
                            "contextConfig.badUrl", defaultContextFile), e);
                }
            }
    
            File hostContextFile = new File(getHostConfigBase(), Constants.HostContextXml);
            if (hostContextFile.exists()) {
                try {
                    URL hostContextUrl = hostContextFile.toURI().toURL();
                    processContextConfig(digester, hostContextUrl);
                } catch (MalformedURLException e) {
                    log.error(sm.getString(
                            "contextConfig.badUrl", hostContextFile), e);
                }
            }
        }
        if (context.getConfigFile() != null) {
            processContextConfig(digester, context.getConfigFile());
        }
    
    }
    

    (1)如果Context的override属性为false(默认配置):

    ①如果存在defaultContextXml即conf/context.xml(Catalina容器级默认配置文件),那么解析该文件,更新Context实例属性。
    ②如果存在hostContextXml即$CATALINA-BASE/conf/Catalina/localhost/context.xml.default文件(Host级的默认配置),则解析该文件,更新Context实例属性。

    (2)如果context的configFile不为空(即$CATALINA-BASE/conf/Catalina/localhost下的Context部署描述文件或者Web应用根目录下的META-INF/context.xml文件),那么解析该文件,更新Context实例属性。

    看到这会发现configFile其实被解析了两遍,在创建Context时会先解析一遍,这里再被解析一遍,为什么?

    因为这里会解析conf/context.xml和context.xml.default文件,配置默认属性,如果之前创建Context时已经配置了某个属性,而这个属性又在conf/context.xml和context.xml.default中存在,显然这时会被覆盖,想要配置Context级别的属性不被覆盖,所以这时再解析一遍。

    根据上述,可以得出结论:

    Tomcat中Context属性的优先级为:configFile > $CATALINA-BASE/conf/Catalina/localhost/context.xml.default > conf/context.xml,即Web应用配置优先级最高,Host级别配置次之,Catalina容器级别最低。

    3.2、BEFORE_START_EVENT

    该事件在Context启动之前触发,主要用于更新docBase属性,解决Web目录锁的问题。

    protected synchronized void beforeStart() {
    
        try {
            fixDocBase();
        } catch (IOException e) {
            log.error(sm.getString(
                    "contextConfig.fixDocBase", context.getName()), e);
        }
    
        antiLocking();
    }
    

    更新Context的docBase属性是为了满足WAR部署的情况。当Web应用为一个WAR压缩且需要解压部署(Hot的unpackWAR=true,且Context的unpackWAR=true)时,docBase属性指向的是解压后的文件夹目录,而非WAR包的路径。

    具体过程是在fixDocBase()方法中:

    protected void fixDocBase() throws IOException {
    
        Host host = (Host) context.getParent();
        File appBase = host.getAppBaseFile();
    
        String docBase = context.getDocBase();
        if (docBase == null) {
            // Trying to guess the docBase according to the path
            String path = context.getPath();
            if (path == null) {
                return;
            }
            ContextName cn = new ContextName(path, context.getWebappVersion());
            docBase = cn.getBaseName();
        }
    
        File file = new File(docBase);
        if (!file.isAbsolute()) {
            docBase = (new File(appBase, docBase)).getPath();
        } else {
            docBase = file.getCanonicalPath();
        }
        file = new File(docBase);
        String origDocBase = docBase;
    
        ContextName cn = new ContextName(context.getPath(), context.getWebappVersion());
        String pathName = cn.getBaseName();
    
        boolean unpackWARs = true;
        if (host instanceof StandardHost) {
            unpackWARs = ((StandardHost) host).isUnpackWARs();
            if (unpackWARs && context instanceof StandardContext) {
                unpackWARs =  ((StandardContext) context).getUnpackWAR();
            }
        }
    
        boolean docBaseInAppBase = docBase.startsWith(appBase.getPath() + File.separatorChar);
    
        if (docBase.toLowerCase(Locale.ENGLISH).endsWith(".war") && !file.isDirectory()) {
            URL war = UriUtil.buildJarUrl(new File(docBase));
            if (unpackWARs) {
                docBase = ExpandWar.expand(host, war, pathName);
                file = new File(docBase);
                docBase = file.getCanonicalPath();
                if (context instanceof StandardContext) {
                    ((StandardContext) context).setOriginalDocBase(origDocBase);
                }
            } else {
                ExpandWar.validate(host, war, pathName);
            }
        } else {
            File docDir = new File(docBase);
            File warFile = new File(docBase + ".war");
            URL war = null;
            if (warFile.exists() && docBaseInAppBase) {
                war = UriUtil.buildJarUrl(warFile);
            }
            if (docDir.exists()) {
                if (war != null && unpackWARs) {
                    // Check if WAR needs to be re-expanded (e.g. if it has
                    // changed). Note: HostConfig.deployWar() takes care of
                    // ensuring that the correct XML file is used.
                    // This will be a NO-OP if the WAR is unchanged.
                    ExpandWar.expand(host, war, pathName);
                }
            } else {
                if (war != null) {
                    if (unpackWARs) {
                        docBase = ExpandWar.expand(host, war, pathName);
                        file = new File(docBase);
                        docBase = file.getCanonicalPath();
                    } else {
                        docBase = warFile.getCanonicalPath();
                        ExpandWar.validate(host, war, pathName);
                    }
                }
                if (context instanceof StandardContext) {
                    ((StandardContext) context).setOriginalDocBase(origDocBase);
                }
            }
        }
    
        // Re-calculate now docBase is a canonical path
        docBaseInAppBase = docBase.startsWith(appBase.getPath() + File.separatorChar);
    
        if (docBaseInAppBase) {
            docBase = docBase.substring(appBase.getPath().length());
            docBase = docBase.replace(File.separatorChar, '/');
            if (docBase.startsWith("/")) {
                docBase = docBase.substring(1);
            }
        } else {
            docBase = docBase.replace(File.separatorChar, '/');
        }
    
        context.setDocBase(docBase);
    }
    

    (1)根据Host的appBase以及Context的docBase计算docBase的绝对路径。
    (2)如果docBase指向WAR包:
    需要解压部署:
    ①解压WAR文件;
    ②将Context的docBase设置为解压后的路径。
    不需要解压部署:
    只检测WAR包,不更新docBase。
    (3)如果docBase指向目录:
    ①如果docBase指向的是有效目录,且存在与该目录同名的WAR包,同时需要解压部署,则重新解压WAR包;
    ②如果docBase指向的是无效目录,即不存在,但是存在与该目录同名的WAR包,如果需要解压,则解压WAR包,更新Context的docBase为解压路径。
    ③如果不需要解压部署,则只检测WAR包。

    当Context的antiResourceLocking属性为true时,Tomcat会将当前Web应用目录复制到临时文件夹下,以避免对原目录的资源加锁。

    3.3、CONFIGURE_START_EVENT

    Context在启动之前,会触发CONFIGURE_START_EVENT事件,ContextConfig通过该事件解析web.xml,创建Wrapper(Servlet)、Filter、ServletContextListener等,完成web容器的初始化。

    CONFIGURE_START_EVENT触发该方法:

    protected synchronized void configureStart() {
        // Called from StandardContext.start()
    
        if (log.isDebugEnabled()) {
            log.debug(sm.getString("contextConfig.start"));
        }
    
        if (log.isDebugEnabled()) {
            log.debug(sm.getString("contextConfig.xmlSettings",
                    context.getName(),
                    Boolean.valueOf(context.getXmlValidation()),
                    Boolean.valueOf(context.getXmlNamespaceAware())));
        }
    
        webConfig();
    
        if (!context.getIgnoreAnnotations()) {
            applicationAnnotationsConfig();
        }
        if (ok) {
            validateSecurityRoles();
        }
    
        // Configure an authenticator if we need one
        if (ok) {
            authenticatorConfig();
        }
    
        // Dump the contents of this pipeline if requested
        if (log.isDebugEnabled()) {
            log.debug("Pipeline Configuration:");
            Pipeline pipeline = context.getPipeline();
            Valve valves[] = null;
            if (pipeline != null) {
                valves = pipeline.getValves();
            }
            if (valves != null) {
                for (int i = 0; i < valves.length; i++) {
                    log.debug("  " + valves[i].getClass().getName());
                }
            }
            log.debug("======================");
        }
    
        // Make our application available if no problems were encountered
        if (ok) {
            context.setConfigured(true);
        } else {
            log.error(sm.getString("contextConfig.unavailable"));
            context.setConfigured(false);
        }
    
    }
    

    该事件主要工作内容:

    根据配置创建Wrapper(Servlet)、Filter、ServletContextListener等,完成Web容器的初始化。除了解析Web应用目录下的web.xml外,还包括Tomcat的默认配置、web-fragment.xml、ServletContainerInitializer,以及相关XML文件的排序和合并。

    根据Servlet规范,Web应用部署描述可来源于WEB-IN/web.xml、Web应用JAR包中的META-INF/web-fragment.xml和META-INF/services/javax.servlet.ServletContainerInitializer。

    除了Servlet规范中提到的部署描述方式,Tomcat还支持默认配置,以简化Web应用的配置工作。这些默认配置有容器级别的(conf/web.xml)和Host级别(conf/Engine名称/Host名称/web.xml.default)。解析中Web应用中的配置优先级最高,Host其次,最后是容器级。

    来看webConfig方法:

    protected void webConfig() {
    /*
     * Anything and everything can override the global and host defaults.
     * This is implemented in two parts
     * - Handle as a web fragment that gets added after everything else so
     *   everything else takes priority
     * - Mark Servlets as overridable so SCI configuration can replace
     *   configuration from the defaults
     */
    
    /*
     * The rules for annotation scanning are not as clear-cut as one might
     * think. Tomcat implements the following process:
     * - As per SRV.1.6.2, Tomcat will scan for annotations regardless of
     *   which Servlet spec version is declared in web.xml. The EG has
     *   confirmed this is the expected behaviour.
     * - As per http://java.net/jira/browse/SERVLET_SPEC-36, if the main
     *   web.xml is marked as metadata-complete, JARs are still processed
     *   for SCIs.
     * - If metadata-complete=true and an absolute ordering is specified,
     *   JARs excluded from the ordering are also excluded from the SCI
     *   processing.
     * - If an SCI has a @HandlesType annotation then all classes (except
     *   those in JARs excluded from an absolute ordering) need to be
     *   scanned to check if they match.
     */
    WebXmlParser webXmlParser = new WebXmlParser(context.getXmlNamespaceAware(),
            context.getXmlValidation(), context.getXmlBlockExternal());
    
    Set<WebXml> defaults = new HashSet<>();
    defaults.add(getDefaultWebXmlFragment(webXmlParser));
    
    Set<WebXml> tomcatWebXml = new HashSet<>();
    tomcatWebXml.add(getTomcatWebXmlFragment(webXmlParser));
    
    WebXml webXml = createWebXml();
    
    // Parse context level web.xml
    InputSource contextWebXml = getContextWebXmlSource();
    if (!webXmlParser.parseWebXml(contextWebXml, webXml, false)) {
        ok = false;
    }
    
    ServletContext sContext = context.getServletContext();
    
    // 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, webXmlParser);
    
    // Step 2. Order the fragments.
    Set<WebXml> orderedFragments = null;
    orderedFragments =
            WebXml.orderWebFragments(webXml, fragments, sContext);
    
    // Step 3. Look for ServletContainerInitializer implementations
    if (ok) {
        processServletContainerInitializers();
    }
    
    if  (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) {
        // Step 4. Process /WEB-INF/classes for annotations and
        // @HandlesTypes matches
        Map<String,JavaClassCacheEntry> javaClassCache = new HashMap<>();
    
        if (ok) {
            WebResource[] webResources =
                    context.getResources().listResources("/WEB-INF/classes");
    
            for (WebResource webResource : webResources) {
                // Skip the META-INF directory from any JARs that have been
                // expanded in to WEB-INF/classes (sometimes IDEs do this).
                if ("META-INF".equals(webResource.getName())) {
                    continue;
                }
                processAnnotationsWebResource(webResource, webXml,
                        webXml.isMetadataComplete(), javaClassCache);
            }
        }
    
        // Step 5. Process JARs for annotations and
        // @HandlesTypes matches - only need to process those fragments we
        // are going to use (remember orderedFragments includes any
        // container fragments)
        if (ok) {
            processAnnotations(
                    orderedFragments, webXml.isMetadataComplete(), javaClassCache);
        }
    
        // Cache, if used, is no longer required so clear it
        javaClassCache.clear();
    }
    
    if (!webXml.isMetadataComplete()) {
        // Step 6. Merge web-fragment.xml files into the main web.xml
        // file.
        if (ok) {
            ok = webXml.merge(orderedFragments);
        }
    
        // Step 7a
        // merge tomcat-web.xml
        webXml.merge(tomcatWebXml);
    
        // Step 7b. Apply global defaults
        // 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. Apply merged web.xml to Context
        if (ok) {
            configureContext(webXml);
        }
    } else {
        webXml.merge(tomcatWebXml);
        webXml.merge(defaults);
        convertJsps(webXml);
        configureContext(webXml);
    }
    
    if (context.getLogEffectiveWebXml()) {
        log.info("web.xml:\n" + webXml.toXml());
    }
    
    // Always need to look for static resources
    // Step 10. Look for static resources packaged in JARs
    if (ok) {
        // Spec does not define an order.
        // Use ordered JARs followed by remaining JARs
        Set<WebXml> resourceJars = new LinkedHashSet<>();
        for (WebXml fragment : orderedFragments) {
            resourceJars.add(fragment);
        }
        for (WebXml fragment : fragments.values()) {
            if (!resourceJars.contains(fragment)) {
                resourceJars.add(fragment);
            }
        }
        processResourceJARs(resourceJars);
        // See also StandardContext.resourcesStart() for
        // WEB-INF/classes/META-INF/resources configuration
    }
    
    // Step 11. Apply the ServletContainerInitializer config to the
    // context
    if (ok) {
        for (Map.Entry<ServletContainerInitializer,
                Set<Class<?>>> entry :
                    initializerClassMap.entrySet()) {
            if (entry.getValue().isEmpty()) {
                context.addServletContainerInitializer(
                        entry.getKey(), null);
            } else {
                context.addServletContainerInitializer(
                        entry.getKey(), entry.getValue());
            }
        }
    }
    }
    

    这个方法是Web容器的初始化,过程涉及内容较多,就简单描述下:

    主要是解析默认配置,先解析容器级的配置(conf/web.xml),然后解析Host级别的配置(web.xml.default);

    解析Web应用的web.xml,其他的解析结果均会合并到该解析结果中。

    扫描素有JAR包,如果有META-INF/web-fragment.xml,解析该分件。

    ...

    最关注的应该是使用web.xml配置Context实例,包括Servlet、Filter、Listener等Servelt规范中支持的组件,这些可以在configureContext()中找到。

    四、总结

    这篇很模糊不清,很潦草,没有太大参考价值,更多的还是自己去看源码吧。

    当然看到这,至少希望明白web.xml是在这里解析,Servlet是在这里被包装成StandardWrapper,Filter是在这里创建的。

    相关文章

      网友评论

          本文标题:Tomcat源码分析 -- StandardContext的启动

          本文链接:https://www.haomeiwen.com/subject/moqmfftx.html