• Tomcat(一):Tomcat启动时加载web.xml


    server.xml配置文件样例:

    <?xml version="1.0" encoding="UTF-8"?>
    <!--
      Licensed to the Apache Software Foundation (ASF) under one or more
      contributor license agreements.  See the NOTICE file distributed with
      this work for additional information regarding copyright ownership.
      The ASF licenses this file to You under the Apache License, Version 2.0
      (the "License"); you may not use this file except in compliance with
      the License.  You may obtain a copy of the License at
    
          http://www.apache.org/licenses/LICENSE-2.0
    
      Unless required by applicable law or agreed to in writing, software
      distributed under the License is distributed on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      See the License for the specific language governing permissions and
      limitations under the License.
    -->
    <!-- Note:  A "Server" is not itself a "Container", so you may not
         define subcomponents such as "Valves" at this level.
         Documentation at /docs/config/server.html
     -->
     <Server port="8005" shutdown="SHUTDOWN">
      <Listener className="org.apache.catalina.startup.VersionLoggerListener"/>
      <!-- Security listener. Documentation at /docs/config/listeners.html
      <Listener className="org.apache.catalina.security.SecurityListener" />
      -->
      <!--APR library loader. Documentation at /docs/apr.html -->
      <Listener SSLEngine="on" className="org.apache.catalina.core.AprLifecycleListener"/>
      <!-- Prevent memory leaks due to use of particular java/javax APIs-->
      <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener"/>
      <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"/>
      <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener"/>
    
      <!-- Global JNDI resources
           Documentation at /docs/jndi-resources-howto.html
      -->
      <GlobalNamingResources>
        <!-- Editable user database that can also be used by
             UserDatabaseRealm to authenticate users
        -->
        <Resource auth="Container" description="User database that can be updated and saved" factory="org.apache.catalina.users.MemoryUserDatabaseFactory" name="UserDatabase" pathname="conf/tomcat-users.xml" type="org.apache.catalina.UserDatabase"/>
      </GlobalNamingResources>
    
      <!-- A "Service" is a collection of one or more "Connectors" that share
           a single "Container" Note:  A "Service" is not itself a "Container",
           so you may not define subcomponents such as "Valves" at this level.
           Documentation at /docs/config/service.html
       -->
      <Service name="Catalina">
    
        <!--The connectors can use a shared executor, you can define one or more named thread pools-->
        <!--
        <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
            maxThreads="150" minSpareThreads="4"/>
        -->
        
        <!-- A "Connector" represents an endpoint by which requests are received
             and responses are returned. Documentation at :
             Java HTTP Connector: /docs/config/http.html
             Java AJP  Connector: /docs/config/ajp.html
             APR (HTTP/AJP) Connector: /docs/apr.html
             Define a non-SSL/TLS HTTP/1.1 Connector on port 8080
        -->
        <Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>
        <!-- A "Connector" using the shared thread pool-->
        <!--
        <Connector executor="tomcatThreadPool"
                   port="8080" protocol="HTTP/1.1"
                   connectionTimeout="20000"
                   redirectPort="8443" />
        -->
        <!-- Define an SSL/TLS HTTP/1.1 Connector on port 8443
             This connector uses the NIO implementation. The default
             SSLImplementation will depend on the presence of the APR/native
             library and the useOpenSSL attribute of the
             AprLifecycleListener.
             Either JSSE or OpenSSL style configuration may be used regardless of
             the SSLImplementation selected. JSSE style configuration is used below.
        -->
        <!--
        <Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
                   maxThreads="150" SSLEnabled="true">
            <SSLHostConfig>
                <Certificate certificateKeystoreFile="conf/localhost-rsa.jks"
                             type="RSA" />
            </SSLHostConfig>
        </Connector>
        -->
        <!-- Define an SSL/TLS HTTP/1.1 Connector on port 8443 with HTTP/2
             This connector uses the APR/native implementation which always uses
             OpenSSL for TLS.
             Either JSSE or OpenSSL style configuration may be used. OpenSSL style
             configuration is used below.
        -->
        <!--
        <Connector port="8443" protocol="org.apache.coyote.http11.Http11AprProtocol"
                   maxThreads="150" SSLEnabled="true" >
            <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
            <SSLHostConfig>
                <Certificate certificateKeyFile="conf/localhost-rsa-key.pem"
                             certificateFile="conf/localhost-rsa-cert.pem"
                             certificateChainFile="conf/localhost-rsa-chain.pem"
                             type="RSA" />
            </SSLHostConfig>
        </Connector>
        -->
        
        <!-- Define an AJP 1.3 Connector on port 8009 -->
        <Connector port="8009" protocol="AJP/1.3" redirectPort="8443"/>
    
    
        <!-- An Engine represents the entry point (within Catalina) that processes
             every request.  The Engine implementation for Tomcat stand alone
             analyzes the HTTP headers included with the request, and passes them
             on to the appropriate Host (virtual host).
             Documentation at /docs/config/engine.html -->
        
        <!-- You should set jvmRoute to support load-balancing via AJP ie :
        <Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1">
        -->
        <Engine defaultHost="localhost" name="Catalina">
        
          <!--For clustering, please take a look at documentation at:
              /docs/cluster-howto.html  (simple how to)
              /docs/config/cluster.html (reference documentation) -->
          <!--
          <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>
          -->
        
          <!-- Use the LockOutRealm to prevent attempts to guess user passwords
               via a brute-force attack -->
          <Realm className="org.apache.catalina.realm.LockOutRealm">
            <!-- This Realm uses the UserDatabase configured in the global JNDI
                 resources under the key "UserDatabase".  Any edits
                 that are performed against this UserDatabase are immediately
                 available for use by the Realm.  -->
            <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/>
          </Realm>
        
          <Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true">
        
            <!-- SingleSignOn valve, share authentication between web applications
                 Documentation at: /docs/config/valve.html -->
            <!--
            <Valve className="org.apache.catalina.authenticator.SingleSignOn" />
            -->
        
            <!-- Access log processes all example.
                 Documentation at: /docs/config/valve.html
                 Note: The pattern used is equivalent to using pattern="common" -->
            <Valve className="org.apache.catalina.valves.AccessLogValve" 
              directory="logs" 
              pattern="%h %l %u %t &quot;%r&quot; %s %b" 
              refix="localhost_access_log" 
              suffix=".txt"/>
            <Context docBase="mybaits-test-dynamic-sql" path="/mybaits-test-dynamic-sql" reloadable="true" source="org.eclipse.jst.j2ee.server:mybaits-test-dynamic-sql"/>
          </Host>
        
          <!--spring_security_01_war-->
          <Host name="lhd.myweb.com" appBase="/opt/apache-tomcat-8.5.49/webapps" unpackWARs="true" autoDeploy="true">
            <Context docBase="/opt/apache-tomcat-8.5.49/webapps/spring_security_01_war" path="" />
            <Valve className="org.apache.catalina.valves.AccessLogValve" 
              directory="logs" 
              pattern="%h %l %u %t &quot;%r&quot; %s %b" 
              refix="localhost_access_log" 
              suffix=".txt"/>
          </Host>
        
        </Engine>
    
      </Service>
    
      <Service name="Catalina2">
        <Connector connectionTimeout="20000" port="8200" protocol="HTTP/1.1" 
          redirectPort="8443"/>
        <Connector port="8010" protocol="AJP/1.3" redirectPort="8443"/>
        <Engine defaultHost="localhost" name="Catalina">
          <Realm className="org.apache.catalina.realm.LockOutRealm">
            <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/>
          </Realm>
    
          <!--spring_security_01_war-->
          <Host name="lhd.myweb.com" appBase="/opt/apache-tomcat-8.5.49/webapps" unpackWARs="true" autoDeploy="true">
            <Context docBase="/opt/apache-tomcat-8.5.49/webapps/spring_security_01_war" path="" />
            <Valve className="org.apache.catalina.valves.AccessLogValve" 
              directory="logs" 
              pattern="%h %l %u %t &quot;%r&quot; %s %b" 
              refix="localhost_access_log" 
              suffix=".txt"/>
          </Host>
        
        </Engine>
    
      </Service>
    </Server>
    View Code

    在Tomcat启动时,会加载并解析tomcat安装目录/conf/server.xml文件,在server.xml配置文件中通过Host或Content进行web应用加载:

    1)Host配置: 在tomcat的server.xml中默认Host配置:

    <Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true">

    如此设置则会自动加载webapps下的文件夹或者war包。

    2)Content配置:

    <Context docBase="/xxxxx" path="/xxxx"  reloadable="true" ></Context>

    如此则会加载指定的context。

    备注:

    样例server.xml访问站点的方式包含:
    1)http://localhost:8080/mybaits-test-dynamic-sql
    2)http://lhd.myweb.com:8080/
    3)http://lhd.myweb.com:8200/

    1)配置Host的appBase则可以自动加载appBase文件夹下的应用

    1.1)查看StandardHost的startInternal方法:

        /**
         * Start this component and implement the requirements
         * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
         *
         * @exception LifecycleException if this component detects a fatal error
         *  that prevents this component from being used
         */
        @Override
        protected synchronized void startInternal() throws LifecycleException {
    
            // Set error report valve
            String errorValve = getErrorReportValveClass();
            if ((errorValve != null) && (!errorValve.equals(""))) {
                try {
                    boolean found = false;
                    Valve[] valves = getPipeline().getValves();
                    for (Valve valve : valves) {
                        if (errorValve.equals(valve.getClass().getName())) {
                            found = true;
                            break;
                        }
                    }
                    if(!found) {
                        Valve valve =
                            (Valve) Class.forName(errorValve).getConstructor().newInstance();
                        getPipeline().addValve(valve);
                    }
                } catch (Throwable t) {
                    ExceptionUtils.handleThrowable(t);
                    log.error(sm.getString("standardHost.invalidErrorReportValveClass",
                            errorValve), t);
                }
            }
            super.startInternal();
        }

    Super.startInternal()方法是ContainerBase.java中的方法:

        /**
         * Start this component and implement the requirements
         * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
         *
         * @exception LifecycleException if this component detects a fatal error
         *  that prevents this component from being used
         */
        @Override
        protected synchronized void startInternal() throws LifecycleException {
    
            // Start our subordinate components, if any
            logger = null;
            getLogger();
            Cluster cluster = getClusterInternal();
            if (cluster instanceof Lifecycle) {
                ((Lifecycle) cluster).start();
            }
            Realm realm = getRealmInternal();
            if (realm instanceof Lifecycle) {
                ((Lifecycle) realm).start();
            }
    
            // Start our child containers, if any
            Container children[] = findChildren();
            List<Future<Void>> results = new ArrayList<>();
            for (int i = 0; i < children.length; i++) {
                results.add(startStopExecutor.submit(new StartChild(children[i])));
            }
    
            MultiThrowable multiThrowable = null;
    
            for (Future<Void> result : results) {
                try {
                    result.get();
                } catch (Throwable e) {
                    log.error(sm.getString("containerBase.threadedStartFailed"), e);
                    if (multiThrowable == null) {
                        multiThrowable = new MultiThrowable();
                    }
                    multiThrowable.add(e);
                }
    
            }
            if (multiThrowable != null) {
                throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"),
                        multiThrowable.getThrowable());
            }
    
            // Start the Valves in our pipeline (including the basic), if any
            if (pipeline instanceof Lifecycle) {
                ((Lifecycle) pipeline).start();
            }
    
    
            setState(LifecycleState.STARTING);
    
            // Start our thread
            threadStart();
        }

    查看ContainerBase#startInternal()中调用了ContainerBase#setState(LifecycleState.STARTING);

        /**
         * Provides a mechanism for sub-classes to update the component state.
         * Calling this method will automatically fire any associated
         * {@link Lifecycle} event. It will also check that any attempted state
         * transition is valid for a sub-class.
         *
         * @param state The new state for this component
         * @throws LifecycleException when attempting to set an invalid state
         */
        protected synchronized void setState(LifecycleState state) throws LifecycleException {
            setStateInternal(state, null, true);
        }

    查看ContainerBase#setState(LifecycleState state)方法内部调用了ContainerBase#setStateInternal(state,null,true)方法:

        private synchronized void setStateInternal(LifecycleState state, Object data, boolean check)
                throws LifecycleException {
    
            if (log.isDebugEnabled()) {
                log.debug(sm.getString("lifecycleBase.setState", this, state));
            }
    
            if (check) {
                // Must have been triggered by one of the abstract methods (assume
                // code in this class is correct)
                // null is never a valid state
                if (state == null) {
                    invalidTransition("null");
                    // Unreachable code - here to stop eclipse complaining about
                    // a possible NPE further down the method
                    return;
                }
    
                // Any method can transition to failed
                // startInternal() permits STARTING_PREP to STARTING
                // stopInternal() permits STOPPING_PREP to STOPPING and FAILED to
                // STOPPING
                if (!(state == LifecycleState.FAILED ||
                        (this.state == LifecycleState.STARTING_PREP &&
                                state == LifecycleState.STARTING) ||
                        (this.state == LifecycleState.STOPPING_PREP &&
                                state == LifecycleState.STOPPING) ||
                        (this.state == LifecycleState.FAILED &&
                                state == LifecycleState.STOPPING))) {
                    // No other transition permitted
                    invalidTransition(state.name());
                }
            }
    
            this.state = state;
            String lifecycleEvent = state.getLifecycleEvent();
            if (lifecycleEvent != null) {
                fireLifecycleEvent(lifecycleEvent, data);
            }
        }

    在ContainerBase#setStateInternal()内部调用了ContainerBase#fireLifecycleEvent(lifecycleEvent,data);方法:

        /**
         * The list of registered LifecycleListeners for event notifications.
         */
        private final List<LifecycleListener> lifecycleListeners = new CopyOnWriteArrayList<>();
            /**
         * Allow sub classes to fire {@link Lifecycle} events.
         *
         * @param type  Event type
         * @param data  Data associated with event.
         */
        protected void fireLifecycleEvent(String type, Object data) {
            LifecycleEvent event = new LifecycleEvent(this, type, data);
            for (LifecycleListener listener : lifecycleListeners) {
                listener.lifecycleEvent(event);
            }
        }

    StandardHost在启动时,会通知所有listener它的启动状态,而StandardHost有一个默认listener是HostConfig,所以会调用到HostConfig的lifecycleEvent方法。

    LifecycleListener接口的实现类:

    1.2)查看HostConfig的lifecycleEvent方法:

    查看HostConfig#lifecycleEvent()方法代码:

        /**
         * Process the START event for an associated Host.
         *
         * @param event The lifecycle event that has occurred
         */
        @Override
        public void lifecycleEvent(LifecycleEvent event) {
            // Identify the host we are associated with
            try {
                host = (Host) event.getLifecycle();
                if (host instanceof StandardHost) {
                    setCopyXML(((StandardHost) host).isCopyXML());
                    setDeployXML(((StandardHost) host).isDeployXML());
                    setUnpackWARs(((StandardHost) host).isUnpackWARs());
                    setContextClass(((StandardHost) host).getContextClass());
                }
            } catch (ClassCastException e) {
                log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e);
                return;
            }
    
            // Process the event that has occurred
            if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
                check();
            } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
                beforeStart();
            } else if (event.getType().equals(Lifecycle.START_EVENT)) {
                start();
            } else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
                stop();
            }
        }

    在HostConfig#lifecycleEvent()方法内部会调用HostConfig#start()方法:

        /**
         * Process a "start" event for this Host.
         */
        public void start() {
            if (log.isDebugEnabled())
                log.debug(sm.getString("hostConfig.start"));
    
            try {
                ObjectName hostON = host.getObjectName();
                oname = new ObjectName
                    (hostON.getDomain() + ":type=Deployer,host=" + host.getName());
                Registry.getRegistry(null, null).registerComponent
                    (this, oname, this.getClass().getName());
            } catch (Exception e) {
                log.warn(sm.getString("hostConfig.jmx.register", oname), e);
            }
    
            if (!host.getAppBaseFile().isDirectory()) {
                log.error(sm.getString("hostConfig.appBase", host.getName(),
                        host.getAppBaseFile().getPath()));
                host.setDeployOnStartup(false);
                host.setAutoDeploy(false);
            }
    
            if (host.getDeployOnStartup())
                deployApps();
    
        }

    在HostConfig#start()内部会调用HostConfig#deployApps()方法:

        /**
         * Deploy applications for any directories or WAR files that are found
         * in our "application root" directory.
         */
        protected void deployApps() {
            File appBase = host.getAppBaseFile();
            File configBase = host.getConfigBaseFile();
            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);
        }

    在HostConfig#deployApps()方法内部会一次调用deployWARs(appBase,filteredAppPaths);和deployDirectories(appBase,filteredAppPaths);方法:

        /**
         * Deploy exploded webapps.
         * @param appBase The base path for applications
         * @param files The exploded webapps that should be deployed
         */
        protected void deployDirectories(File appBase, String[] files) {
            if (files == null)
                return;
    
            ExecutorService es = host.getStartStopExecutor();
            List<Future<?>> results = new ArrayList<>();
    
            for (int i = 0; i < files.length; i++) {
                if (files[i].equalsIgnoreCase("META-INF"))
                    continue;
                if (files[i].equalsIgnoreCase("WEB-INF"))
                    continue;
                File dir = new File(appBase, files[i]);
                if (dir.isDirectory()) {
                    ContextName cn = new ContextName(files[i], false);
    
                    if (isServiced(cn.getName()) || deploymentExists(cn.getName()))
                        continue;
    
                    results.add(es.submit(new DeployDirectory(this, cn, dir)));
                }
            }
    
            for (Future<?> result : results) {
                try {
                    result.get();
                } catch (Exception e) {
                    log.error(sm.getString("hostConfig.deployDir.threaded.error"), e);
                }
            }
        }

    在HostConfig#deployDirectories()方法内部调用了ExecutorService#submit(new DeployDirectory(this,cn,dir));,对于每一个文件夹,提交了一个部署任务。

    1.3)查看DeployDirectory的run方法:

        private static class DeployDirectory implements Runnable {
            private HostConfig config;
            private ContextName cn;
            private File dir;
    
            public DeployDirectory(HostConfig config, ContextName cn, File dir) {
                this.config = config;
                this.cn = cn;
                this.dir = dir;
            }
    
            @Override
            public void run() {
                config.deployDirectory(cn, dir);
            }
        }

    在DeployDirectory#run()内部调用config.deployDirectory(cn,dir);代码,HostConfig#deployDirectory(cn,dir)方法内部代码:

        /**
         * Deploy exploded webapp.
         * @param cn The context name
         * @param dir The path to the root folder of the weapp
         */
        protected void deployDirectory(ContextName cn, File dir) {
            long startTime = 0;
            // Deploy the application in this directory
            if( log.isInfoEnabled() ) {
                startTime = System.currentTimeMillis();
                log.info(sm.getString("hostConfig.deployDir",
                        dir.getAbsolutePath()));
            }
    
            Context context = null;
            File xml = new File(dir, Constants.ApplicationContextXml);
            File xmlCopy =
                    new File(host.getConfigBaseFile(), cn.getBaseName() + ".xml");
    
    
            DeployedApplication deployedApp;
            boolean copyThisXml = isCopyXML();
            boolean deployThisXML = isDeployThisXML(dir, cn);
    
            try {
                if (deployThisXML && xml.exists()) {
                    synchronized (digesterLock) {
                        try {
                            context = (Context) digester.parse(xml);
                        } catch (Exception e) {
                            log.error(sm.getString(
                                    "hostConfig.deployDescriptor.error",
                                    xml), e);
                            context = new FailedContext();
                        } finally {
                            digester.reset();
                            if (context == null) {
                                context = new FailedContext();
                            }
                        }
                    }
    
                    if (copyThisXml == false && context instanceof StandardContext) {
                        // Host is using default value. Context may override it.
                        copyThisXml = ((StandardContext) context).getCopyXML();
                    }
    
                    if (copyThisXml) {
                        Files.copy(xml.toPath(), xmlCopy.toPath());
                        context.setConfigFile(xmlCopy.toURI().toURL());
                    } else {
                        context.setConfigFile(xml.toURI().toURL());
                    }
                } else if (!deployThisXML && xml.exists()) {
                    // Block deployment as META-INF/context.xml may contain security
                    // configuration necessary for a secure deployment.
                    log.error(sm.getString("hostConfig.deployDescriptor.blocked",
                            cn.getPath(), xml, xmlCopy));
                    context = new FailedContext();
                } else {
                    context = (Context) Class.forName(contextClass).getConstructor().newInstance();
                }
    
                Class<?> clazz = Class.forName(host.getConfigClass());
                LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance();
                context.addLifecycleListener(listener);
    
                context.setName(cn.getName());
                context.setPath(cn.getPath());
                context.setWebappVersion(cn.getVersion());
                context.setDocBase(cn.getBaseName());
                host.addChild(context);
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                log.error(sm.getString("hostConfig.deployDir.error",
                        dir.getAbsolutePath()), t);
            } finally {
                deployedApp = new DeployedApplication(cn.getName(),
                        xml.exists() && deployThisXML && copyThisXml);
    
                // Fake re-deploy resource to detect if a WAR is added at a later
                // point
                deployedApp.redeployResources.put(dir.getAbsolutePath() + ".war",
                        Long.valueOf(0));
                deployedApp.redeployResources.put(dir.getAbsolutePath(),
                        Long.valueOf(dir.lastModified()));
                if (deployThisXML && xml.exists()) {
                    if (copyThisXml) {
                        deployedApp.redeployResources.put(
                                xmlCopy.getAbsolutePath(),
                                Long.valueOf(xmlCopy.lastModified()));
                    } else {
                        deployedApp.redeployResources.put(
                                xml.getAbsolutePath(),
                                Long.valueOf(xml.lastModified()));
                        // Fake re-deploy resource to detect if a context.xml file is
                        // added at a later point
                        deployedApp.redeployResources.put(
                                xmlCopy.getAbsolutePath(),
                                Long.valueOf(0));
                    }
                } else {
                    // Fake re-deploy resource to detect if a context.xml file is
                    // added at a later point
                    deployedApp.redeployResources.put(
                            xmlCopy.getAbsolutePath(),
                            Long.valueOf(0));
                    if (!xml.exists()) {
                        deployedApp.redeployResources.put(
                                xml.getAbsolutePath(),
                                Long.valueOf(0));
                    }
                }
                addWatchedResources(deployedApp, dir.getAbsolutePath(), context);
                // Add the global redeploy resources (which are never deleted) at
                // the end so they don't interfere with the deletion process
                addGlobalRedeployResources(deployedApp);
            }
    
            deployed.put(cn.getName(), deployedApp);
    
            if( log.isInfoEnabled() ) {
                log.info(sm.getString("hostConfig.deployDir.finished",
                        dir.getAbsolutePath(), Long.valueOf(System.currentTimeMillis() - startTime)));
            }
        }

    部署任务中会调用HostConfig的方法进行实际的部署操作,再次方法中会构造Context,并使用host.addChild(context)方法加入到Host中。

    2)自行配置Context

    如果在Host下配置了Context,则在解析server.xml时会创建Context对象,并使用host.addChild(context)方法加入到Host。

    3)加载web.xml

    一个Context对应一个web应用,而一个web应用应该由一个web.xml。

    查看StandardContext#startInternal()方法:

       /**
         * Start this component and implement the requirements
         * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
         *
         * @exception LifecycleException if this component detects a fatal error
         *  that prevents this component from being used
         */
        @Override
        protected synchronized void startInternal() throws LifecycleException {
            if(log.isDebugEnabled())
                log.debug("Starting " + getBaseName());
    
            // Send j2ee.state.starting notification
            if (this.getObjectName() != null) {
                Notification notification = new Notification("j2ee.state.starting",
                        this.getObjectName(), sequenceNumber.getAndIncrement());
                broadcaster.sendNotification(notification);
            }
    
            setConfigured(false);
            boolean ok = true;
    
            // Currently this is effectively a NO-OP but needs to be called to
            // ensure the NamingResources follows the correct lifecycle
            if (namingResources != null) {
                namingResources.start();
            }
    
            // Post work directory
            postWorkDirectory();
    
            // 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();
            }
    
            if (getLoader() == null) {
                WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
                webappLoader.setDelegate(getDelegate());
                setLoader(webappLoader);
            }
    
            // An explicit cookie processor hasn't been specified; use the default
            if (cookieProcessor == null) {
                cookieProcessor = new Rfc6265CookieProcessor();
            }
    
            // Initialize character set mapper
            getCharsetMapper();
    
            // Validate required extensions
            boolean dependencyCheck = true;
            try {
                dependencyCheck = ExtensionValidator.validateApplication
                    (getResources(), this);
            } catch (IOException ioe) {
                log.error(sm.getString("standardContext.extensionValidationError"), ioe);
                dependencyCheck = false;
            }
    
            if (!dependencyCheck) {
                // do not make application available if dependency check fails
                ok = false;
            }
    
            // Reading the "catalina.useNaming" environment variable
            String useNamingProperty = System.getProperty("catalina.useNaming");
            if ((useNamingProperty != null)
                && (useNamingProperty.equals("false"))) {
                useNaming = false;
            }
    
            if (ok && isUseNaming()) {
                if (getNamingContextListener() == null) {
                    NamingContextListener ncl = new NamingContextListener();
                    ncl.setName(getNamingContextName());
                    ncl.setExceptionOnFailedWrite(getJndiExceptionOnFailedWrite());
                    addLifecycleListener(ncl);
                    setNamingContextListener(ncl);
                }
            }
    
            // Standard container startup
            if (log.isDebugEnabled())
                log.debug("Processing standard container startup");
    
    
            // Binding thread
            ClassLoader oldCCL = bindThread();
    
            try {
                if (ok) {
                    // Start our subordinate components, if any
                    Loader loader = getLoader();
                    if (loader instanceof Lifecycle) {
                        ((Lifecycle) loader).start();
                    }
    
                    // since the loader just started, the webapp classloader is now
                    // created.
                    setClassLoaderProperty("clearReferencesRmiTargets",
                            getClearReferencesRmiTargets());
                    setClassLoaderProperty("clearReferencesStopThreads",
                            getClearReferencesStopThreads());
                    setClassLoaderProperty("clearReferencesStopTimerThreads",
                            getClearReferencesStopTimerThreads());
                    setClassLoaderProperty("clearReferencesHttpClientKeepAliveThread",
                            getClearReferencesHttpClientKeepAliveThread());
                    setClassLoaderProperty("clearReferencesObjectStreamClassCaches",
                            getClearReferencesObjectStreamClassCaches());
                    setClassLoaderProperty("clearReferencesThreadLocals",
                            getClearReferencesThreadLocals());
    
                    // By calling unbindThread and bindThread in a row, we setup the
                    // current Thread CCL to be the webapp classloader
                    unbindThread(oldCCL);
                    oldCCL = bindThread();
    
                    // Initialize logger again. Other components might have used it
                    // too early, so it should be reset.
                    logger = null;
                    getLogger();
    
                    Realm realm = getRealmInternal();
                    if(null != realm) {
                        if (realm instanceof Lifecycle) {
                            ((Lifecycle) realm).start();
                        }
    
                        // Place the CredentialHandler into the ServletContext so
                        // applications can have access to it. Wrap it in a "safe"
                        // handler so application's can't modify it.
                        CredentialHandler safeHandler = new CredentialHandler() {
                            @Override
                            public boolean matches(String inputCredentials, String storedCredentials) {
                                return getRealmInternal().getCredentialHandler().matches(inputCredentials, storedCredentials);
                            }
    
                            @Override
                            public String mutate(String inputCredentials) {
                                return getRealmInternal().getCredentialHandler().mutate(inputCredentials);
                            }
                        };
                        context.setAttribute(Globals.CREDENTIAL_HANDLER, safeHandler);
                    }
    
                    // Notify our interested LifecycleListeners
                    fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
    
                    // Start our child containers, if not already started
                    for (Container child : findChildren()) {
                        if (!child.getState().isAvailable()) {
                            child.start();
                        }
                    }
    
                    // Start the Valves in our pipeline (including the basic),
                    // if any
                    if (pipeline instanceof Lifecycle) {
                        ((Lifecycle) pipeline).start();
                    }
    
                    // Acquire clustered manager
                    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();
                        }
                    }
    
                    // Configure default manager if none was specified
                    if (contextManager != null) {
                        if (log.isDebugEnabled()) {
                            log.debug(sm.getString("standardContext.manager",
                                    contextManager.getClass().getName()));
                        }
                        setManager(contextManager);
                    }
    
                    if (manager!=null && (getCluster() != null) && distributable) {
                        //let the cluster know that there is a context that is distributable
                        //and that it has its own manager
                        getCluster().registerManager(manager);
                    }
                }
    
                if (!getConfigured()) {
                    log.error(sm.getString("standardContext.configurationFail"));
                    ok = false;
                }
    
                // We put the resources into the servlet context
                if (ok)
                    getServletContext().setAttribute
                        (Globals.RESOURCES_ATTR, getResources());
    
                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());
                }
    
                // 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(sm.getString("standardContext.listenerFail"));
                        ok = false;
                    }
                }
    
                // Check constraints for uncovered HTTP methods
                // Needs to be after SCIs and listeners as they may programmatically
                // change constraints
                if (ok) {
                    checkConstraintsForUncoveredMethods(findConstraints());
                }
    
                try {
                    // Start manager
                    Manager manager = getManager();
                    if (manager instanceof Lifecycle) {
                        ((Lifecycle) manager).start();
                    }
                } catch(Exception e) {
                    log.error(sm.getString("standardContext.managerFail"), e);
                    ok = false;
                }
    
                // Configure and call application filters
                if (ok) {
                    if (!filterStart()) {
                        log.error(sm.getString("standardContext.filterFail"));
                        ok = false;
                    }
                }
    
                // Load and initialize all "load on startup" servlets
                if (ok) {
                    if (!loadOnStartup(findChildren())){
                        log.error(sm.getString("standardContext.servletFail"));
                        ok = false;
                    }
                }
    
                // Start ContainerBackgroundProcessor thread
                super.threadStart();
            } finally {
                // Unbinding thread
                unbindThread(oldCCL);
            }
    
            // Set available status depending upon startup success
            if (ok) {
                if (log.isDebugEnabled())
                    log.debug("Starting completed");
            } else {
                log.error(sm.getString("standardContext.startFailed", getName()));
            }
    
            startTime=System.currentTimeMillis();
    
            // Send j2ee.state.running notification
            if (ok && (this.getObjectName() != null)) {
                Notification notification =
                    new Notification("j2ee.state.running", this.getObjectName(),
                                     sequenceNumber.getAndIncrement());
                broadcaster.sendNotification(notification);
            }
    
            // 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();
    
            // Reinitializing if something went wrong
            if (!ok) {
                setState(LifecycleState.FAILED);
            } else {
                setState(LifecycleState.STARTING);
            }
        }
    View Code

    其内部主要调用流水线:

    startInternal()->fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT,NULL)
                   ->child.start()
                   ->mergeParameters()
                   ->listenerStart()
                             ->setApplicationEventListeners()
                             ->setApplicationLifecycleListeners()
                   ->filterStart()
                   ->loadOnStartup(findChildren())

    3.1)StandardContext#fireLifecycleEvent()方法

    StandardContext默认会添加ContextConfig到listener,当StandardContext启动时会通知所有listener它的启动状态,所以会调用到ContextConfig#lifecycleEvent方法。

    查看ContextConfig#lifecycleEvent()方法:

        /**
         * Process events for an associated Context.
         *
         * @param event The lifecycle event that has occurred
         */
        @Override
        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();
            }
        }

    因为传递的event是CONFIGURES_START_EVENT,因此在ContextConfig#lifecycleEvent()内部会调用ContextConfig#configureStart(),查看ContextConfig#configureStart()代码:

        /**
         * Process a "contextConfig" event for this Context.
         */
        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();
    
            // Just for debug
            context.addServletContainerInitializer(new JasperInitializer(), null);
    
            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);
            }
        }

    在ContextConfig#configureStart()内部会调用ContextConfig#webConfig()方法:

        /**
         * 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() {
            /*
             * 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));
    
            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) {
                // Steps 4 & 5.
                processClasses(webXml, orderedFragments);
            }
    
            if (!webXml.isMetadataComplete()) {
                // Step 6. Merge web-fragment.xml files into the main web.xml
                // file.
                if (ok) {
                    ok = webXml.merge(orderedFragments);
                }
    
                // Step 7. 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(defaults);
                convertJsps(webXml);
                configureContext(webXml);
            }
    
            if (context.getLogEffectiveWebXml()) {
                log.info("web.xml:
    " + 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());
                    }
                }
            }
        }

    在ContextConfig#webConfig内部会创建WebXmlParser对象,并调用该对象的parseWebXml()方法进行web.xml解析(在parseWebXml()方法内部采用Digester读取web.xml);并调用configureContext(webXml)对StandardContext实例属性进行设置:

        private void configureContext(WebXml webxml) {
            // As far as possible, process in alphabetical order so it is easy to
            // check everything is present
            // Some validation depends on correct public ID
            context.setPublicId(webxml.getPublicId());
    
            // Everything else in order
            context.setEffectiveMajorVersion(webxml.getMajorVersion());
            context.setEffectiveMinorVersion(webxml.getMinorVersion());
    
            for (Entry<String, String> entry : webxml.getContextParams().entrySet()) {
                context.addParameter(entry.getKey(), entry.getValue());
            }
            context.setDenyUncoveredHttpMethods(
                    webxml.getDenyUncoveredHttpMethods());
            context.setDisplayName(webxml.getDisplayName());
            context.setDistributable(webxml.isDistributable());
            for (ContextLocalEjb ejbLocalRef : webxml.getEjbLocalRefs().values()) {
                context.getNamingResources().addLocalEjb(ejbLocalRef);
            }
            for (ContextEjb ejbRef : webxml.getEjbRefs().values()) {
                context.getNamingResources().addEjb(ejbRef);
            }
            for (ContextEnvironment environment : webxml.getEnvEntries().values()) {
                context.getNamingResources().addEnvironment(environment);
            }
            for (ErrorPage errorPage : webxml.getErrorPages().values()) {
                context.addErrorPage(errorPage);
            }
            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);
            }
            for (Entry<String, String> entry :
                    webxml.getLocaleEncodingMappings().entrySet()) {
                context.addLocaleEncodingMappingParameter(entry.getKey(),
                        entry.getValue());
            }
            // Prevents IAE
            if (webxml.getLoginConfig() != null) {
                context.setLoginConfig(webxml.getLoginConfig());
            }
            for (MessageDestinationRef mdr :
                    webxml.getMessageDestinationRefs().values()) {
                context.getNamingResources().addMessageDestinationRef(mdr);
            }
    
            // messageDestinations were ignored in Tomcat 6, so ignore here
    
            context.setIgnoreAnnotations(webxml.isMetadataComplete());
            for (Entry<String, String> entry :
                    webxml.getMimeMappings().entrySet()) {
                context.addMimeMapping(entry.getKey(), entry.getValue());
            }
            // Name is just used for ordering
            for (ContextResourceEnvRef resource :
                    webxml.getResourceEnvRefs().values()) {
                context.getNamingResources().addResourceEnvRef(resource);
            }
            for (ContextResource resource : webxml.getResourceRefs().values()) {
                context.getNamingResources().addResource(resource);
            }
            boolean allAuthenticatedUsersIsAppRole =
                    webxml.getSecurityRoles().contains(
                            SecurityConstraint.ROLE_ALL_AUTHENTICATED_USERS);
            for (SecurityConstraint constraint : webxml.getSecurityConstraints()) {
                if (allAuthenticatedUsersIsAppRole) {
                    constraint.treatAllAuthenticatedUsersAsApplicationRole();
                }
                context.addConstraint(constraint);
            }
            for (String role : webxml.getSecurityRoles()) {
                context.addSecurityRole(role);
            }
            for (ContextService service : webxml.getServiceRefs().values()) {
                context.getNamingResources().addService(service);
            }
            for (ServletDef servlet : webxml.getServlets().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 :
                    webxml.getServletMappings().entrySet()) {
                context.addServletMappingDecoded(entry.getKey(), entry.getValue());
            }
            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());
                }
                if (sessionConfig.getCookieSecure() != null) {
                    scc.setSecure(sessionConfig.getCookieSecure().booleanValue());
                }
                if (sessionConfig.getCookieMaxAge() != null) {
                    scc.setMaxAge(sessionConfig.getCookieMaxAge().intValue());
                }
                if (sessionConfig.getSessionTrackingModes().size() > 0) {
                    context.getServletContext().setSessionTrackingModes(
                            sessionConfig.getSessionTrackingModes());
                }
            }
    
            // Context doesn't use version directly
    
            for (String welcomeFile : webxml.getWelcomeFiles()) {
                /*
                 * The following will result in a welcome file of "" so don't add
                 * that to the context
                 * <welcome-file-list>
                 *   <welcome-file/>
                 * </welcome-file-list>
                 */
                if (welcomeFile != null && welcomeFile.length() > 0) {
                    context.addWelcomeFile(welcomeFile);
                }
            }
    
            // Do this last as it depends on servlets
            for (JspPropertyGroup jspPropertyGroup :
                    webxml.getJspPropertyGroups()) {
                String jspServletName = context.findServletMapping("*.jsp");
                if (jspServletName == null) {
                    jspServletName = "jsp";
                }
                if (context.findChild(jspServletName) != null) {
                    for (String urlPattern : jspPropertyGroup.getUrlPatterns()) {
                        context.addServletMappingDecoded(urlPattern, jspServletName, true);
                    }
                } else {
                    if(log.isDebugEnabled()) {
                        for (String urlPattern : jspPropertyGroup.getUrlPatterns()) {
                            log.debug("Skipping " + urlPattern + " , no servlet " +
                                    jspServletName);
                        }
                    }
                }
            }
    
            for (Entry<String, String> entry :
                    webxml.getPostConstructMethods().entrySet()) {
                context.addPostConstructMethod(entry.getKey(), entry.getValue());
            }
    
            for (Entry<String, String> entry :
                webxml.getPreDestroyMethods().entrySet()) {
                context.addPreDestroyMethod(entry.getKey(), entry.getValue());
            }
        }

    3.2)StandardContext#mergeParameters()方法:

        /**
         * Merge the context initialization parameters specified in the application
         * deployment descriptor with the application parameters described in the
         * server configuration, respecting the <code>override</code> property of
         * the application parameters appropriately.
         */
        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());
            }
    
        }

    步骤3.1)中会将web.xml中的context-param元素设置到context的parameters里,此处则是把parameters设置到servletContext里。

    3.3)StandardContext#listenerStart(),启动listener:

        /**
         * Configure the set of instantiated application event listeners
         * for this Context.
         * @return <code>true</code> if all listeners wre
         * initialized successfully, or <code>false</code> otherwise.
         */
        public boolean listenerStart() {
            if (log.isDebugEnabled())
                log.debug("Configuring application event listeners");
    
            // Instantiate the required listeners
            String listeners[] = findApplicationListeners();
            Object results[] = new Object[listeners.length];
            boolean ok = true;
            for (int i = 0; i < results.length; i++) {
                if (getLogger().isDebugEnabled())
                    getLogger().debug(" Configuring event listener class '" +
                        listeners[i] + "'");
                try {
                    String listener = listeners[i];
                    results[i] = getInstanceManager().newInstance(listener);
                } catch (Throwable t) {
                    t = ExceptionUtils.unwrapInvocationTargetException(t);
                    ExceptionUtils.handleThrowable(t);
                    getLogger().error(sm.getString(
                            "standardContext.applicationListener", listeners[i]), t);
                    ok = false;
                }
            }
            if (!ok) {
                getLogger().error(sm.getString("standardContext.applicationSkipped"));
                return false;
            }
    
            // Sort listeners in two arrays
            ArrayList<Object> eventListeners = new ArrayList<>();
            ArrayList<Object> lifecycleListeners = new ArrayList<>();
            for (int i = 0; i < results.length; i++) {
                if ((results[i] instanceof ServletContextAttributeListener)
                    || (results[i] instanceof ServletRequestAttributeListener)
                    || (results[i] instanceof ServletRequestListener)
                    || (results[i] instanceof HttpSessionIdListener)
                    || (results[i] instanceof HttpSessionAttributeListener)) {
                    eventListeners.add(results[i]);
                }
                if ((results[i] instanceof ServletContextListener)
                    || (results[i] instanceof HttpSessionListener)) {
                    lifecycleListeners.add(results[i]);
                }
            }
    
            // Listener instances may have been added directly to this Context by
            // ServletContextInitializers and other code via the pluggability APIs.
            // Put them these listeners after the ones defined in web.xml and/or
            // annotations then overwrite the list of instances with the new, full
            // list.
            for (Object eventListener: getApplicationEventListeners()) {
                eventListeners.add(eventListener);
            }
            setApplicationEventListeners(eventListeners.toArray());
            for (Object lifecycleListener: getApplicationLifecycleListeners()) {
                lifecycleListeners.add(lifecycleListener);
                if (lifecycleListener instanceof ServletContextListener) {
                    noPluggabilityListeners.add(lifecycleListener);
                }
            }
            setApplicationLifecycleListeners(lifecycleListeners.toArray());
    
            // Send application start events
    
            if (getLogger().isDebugEnabled())
                getLogger().debug("Sending application start events");
    
            // Ensure context is not null
            getServletContext();
            context.setNewServletContextListenerAllowed(false);
    
            Object instances[] = getApplicationLifecycleListeners();
            if (instances == null || instances.length == 0) {
                return ok;
            }
    
            ServletContextEvent event = new ServletContextEvent(getServletContext());
            ServletContextEvent tldEvent = null;
            if (noPluggabilityListeners.size() > 0) {
                noPluggabilityServletContext = new NoPluggabilityServletContext(getServletContext());
                tldEvent = new ServletContextEvent(noPluggabilityServletContext);
            }
            for (int i = 0; i < instances.length; i++) {
                if (!(instances[i] instanceof ServletContextListener))
                    continue;
                ServletContextListener listener =
                    (ServletContextListener) instances[i];
                try {
                    fireContainerEvent("beforeContextInitialized", listener);
                    if (noPluggabilityListeners.contains(listener)) {
                        listener.contextInitialized(tldEvent);
                    } else {
                        listener.contextInitialized(event);
                    }
                    fireContainerEvent("afterContextInitialized", listener);
                } catch (Throwable t) {
                    ExceptionUtils.handleThrowable(t);
                    fireContainerEvent("afterContextInitialized", listener);
                    getLogger().error
                        (sm.getString("standardContext.listenerStart",
                                      instances[i].getClass().getName()), t);
                    ok = false;
                }
            }
            return ok;
    
        }

    步骤3.1)中会将web.xml中的listener元素设置到context的applicationListeners里,此处则取出listener类名,创建实例,并将listener分为两类:

    eventListener:ServletRequestAttributeListener、ServletRequestListener、HttpSessionIdListener、HttpSessionAttributeListener;

    lifecycleListener:ServletContextListener、HttpSesssionListener。

    对于ServletContextListener,会调用fireContainerEvent("beforeContextInitialized", listener);、listener.contextInitialized(event);、fireContainerEvent("afterContextInitialized", listener);。

    3.4)StandardContext#filterStart(),启动filter

        /**
         * Configure and initialize the set of filters for this Context.
         * @return <code>true</code> if all filter initialization completed
         * successfully, or <code>false</code> otherwise.
         */
        public boolean filterStart() {
            if (getLogger().isDebugEnabled()) {
                getLogger().debug("Starting filters");
            }
            // Instantiate and record a FilterConfig for each defined filter
            boolean ok = true;
            synchronized (filterConfigs) {
                filterConfigs.clear();
                for (Entry<String,FilterDef> entry : filterDefs.entrySet()) {
                    String name = entry.getKey();
                    if (getLogger().isDebugEnabled()) {
                        getLogger().debug(" Starting filter '" + name + "'");
                    }
                    try {
                        ApplicationFilterConfig filterConfig =
                                new ApplicationFilterConfig(this, entry.getValue());
                        filterConfigs.put(name, filterConfig);
                    } catch (Throwable t) {
                        t = ExceptionUtils.unwrapInvocationTargetException(t);
                        ExceptionUtils.handleThrowable(t);
                        getLogger().error(sm.getString(
                                "standardContext.filterStart", name), t);
                        ok = false;
                    }
                }
            }
    
            return ok;
        }

    步骤3.1)中会将web.xml中的filter元素设置到filter的FilterDef里,此处则会实例化filter设置到filterConfigs里。

    3.5)StandardContext#loadOnStartup(findChildren()),启动servlet

        /**
         * Load and initialize all servlets marked "load on startup" in the
         * web application deployment descriptor.
         *
         * @param children Array of wrappers for all currently defined
         *  servlets (including those not declared load on startup)
         * @return <code>true</code> if load on startup was considered successful
         */
        public boolean loadOnStartup(Container children[]) {
            // Collect "load on startup" servlets that need to be initialized
            TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
            for (int i = 0; i < children.length; i++) {
                Wrapper wrapper = (Wrapper) children[i];
                int loadOnStartup = wrapper.getLoadOnStartup();
                //如果web.xml中servlet配置的<load-on-startup>值小于0,则不会在启动时初始化servlet;
                //如果设置的<load-on-startup>值大于等于0,则会在启动时初始化servlet。
                if (loadOnStartup < 0)
                    continue;
                Integer key = Integer.valueOf(loadOnStartup);
                ArrayList<Wrapper> list = map.get(key);
                if (list == null) {
                    list = new ArrayList<>();
                    map.put(key, list);
                }
                list.add(wrapper);
            }
    
            // Load the collected "load on startup" servlets
            for (ArrayList<Wrapper> list : map.values()) {
                for (Wrapper wrapper : list) {
                    try {
                        wrapper.load();
                    } catch (ServletException e) {
                        getLogger().error(sm.getString("standardContext.loadOnStartup.loadException",
                              getName(), wrapper.getName()), StandardWrapper.getRootCause(e));
                        // NOTE: load errors (including a servlet that throws
                        // UnavailableException from the init() method) are NOT
                        // fatal to application startup
                        // unless failCtxIfServletStartFails="true" is specified
                        if(getComputedFailCtxIfServletStartFails()) {
                            return false;
                        }
                    }
                }
            }
            return true;
        }

    步骤3.1)中会将web.xml中的servlet元素封装成wrapper,并调用addChild()方法设置到Context里。此处则会检查是否需要loadOnStartup(),如果需要则对servlet执行servlet.load()。

    参考:

    Tomcat应用 web.xml的加载过程

    Tomcat原理系列之四:Tomat如何启动spring(加载web.xml)

  • 相关阅读:
    Hello, Fedora.
    Android与Linux分道扬镳
    VIM教程V1.5梁昌泰
    强大的NTFS文件系统
    Linux下的cc与gcc
    g++与gcc的区别
    Fedora下解压缩的相关问题
    The GNU C Reference Manual
    Linux Kbuild文档
    实验一:计算机是怎样工作的
  • 原文地址:https://www.cnblogs.com/yy3b2007com/p/12272001.html
Copyright © 2020-2023  润新知