Tomcat source code analysis - startup of Container

Posted by jlive on Sat, 15 Jan 2022 07:24:53 +0100

1. Start process of Container

stay Tomcat source code analysis (1) - structure composition and core components In, the author analyzes the general composition of Tomcat. The Container container is a core component strongly related to the upper business logic, and is divided into four levels. During the startup of tomcat, the startup process of Container and Connector connector is basically the same. However, because the Container is divided into four layers and one Tomcat server can load multiple web applications, the creation of application level containers will be delayed. The process is as follows:

  1. Parse the configuration file, create the top-level Container object Engine and initialize it
  2. Start the top-level container and complete the environment construction
  3. Load the web application, complete the creation and startup of the application level container, and load the servlet into the container

2. Source code analysis

2.1 creation and initialization of top-level container Engine

  1. In fact, the creation process of container is consistent with that of connector. Readers can refer to it Connector object creation , which is mainly used to resolve server The XML file completes the instance creation of the container object. General server The Context container will not be configured in the XML file because the Tomcat server supports automatic deployment. You can dynamically load web applications by regularly scanning the specified directory during Tomcat runtime without restarting the server. And server XML is a resource that cannot be dynamically reloaded. If you want to modify the web application configured in this file, you have to restart the server to take effect. To sum up, in the process of server initialization, only two levels of containers, Engine and Host, are usually created. First, let's take a look at the construction method of standard Engine, the implementation class of Engine container

    It can be seen that the logic in the construction method is relatively simple. It is worth noting that the standardengineval object is created and added to the pipeline pipeline inside StandardEngine. This is the normal operation of container objects. In the future, readers will see that a default Valve processor will be created when each container object is created

     public StandardEngine() {
    
         super();
         pipeline.setBasic(new StandardEngineValve());
         /* Set the jmvRoute using the system property jvmRoute */
         try {
             setJvmRoute(System.getProperty("jvmRoute"));
         } catch(Exception ex) {
             log.warn(sm.getString("standardEngine.jvmRouteFail"));
         }
         // By default, the engine will hold the reloading thread
         backgroundProcessorDelay = 10;
    
     }
    
  2. The implementation class of the Host container is StandardHost. The construction method of this class is very simple and will not be repeated. Note that when you create a Host container, you will also create a HostConfig configuration object and add it to the list of life cycle listeners of the Host container. This HostConfig will complete the configuration of the container as the container life cycle changes

     public StandardHost() {
    
         super();
         pipeline.setBasic(new StandardHostValve());
    
     }
    
  3. After the container object is created, the next step is the initialization process of the container. As we know, in Tomcat, a Service consists of several connectors and a container Egine. In fact, these two components will be initialized at the same time during Service initialization. Please refer to the detailed trigger process Connector initialization , the direct trigger point is in the StandardService#initInternal() method, where engine Init() will actually call the StandardEngine#initInternal() method

    @Override
     protected void initInternal() throws LifecycleException {
    
         super.initInternal();
    
         if (engine != null) {
             engine.init();
         }
    
         // Initialize any Executors
         for (Executor executor : findExecutors()) {
             if (executor instanceof JmxEnabled) {
                 ((JmxEnabled) executor).setDomain(getDomain());
             }
             executor.init();
         }
    
         // Initialize mapper listener
         mapperListener.init();
    
         // Initialize our defined Connectors
         synchronized (connectorsLock) {
             for (Connector connector : connectors) {
                 connector.init();
             }
         }
     }
    
  4. The StandardEngine#initInternal() method will not go into depth for the time being. It is mainly used to initialize some common components of the container. So far, the initialization of the whole container has come to an end

    @Override
     protected void initInternal() throws LifecycleException {
         // Ensure that a Realm is present before any attempt is made to start
         // one. This will create the default NullRealm if necessary.
         getRealm();
         super.initInternal();
     }
    

2.2 vessel startup

  1. The startup process of Tomcat container is accompanied by the startup of the container. Refer to the detailed trigger process of container startup Connector start , the direct trigger point is in the StandardService#startInternal() method, where engine Start () will eventually call the StandardEngine#startInternal() method

    @Override
     protected void startInternal() throws LifecycleException {
    
         if(log.isInfoEnabled())
             log.info(sm.getString("standardService.start.name", this.name));
         setState(LifecycleState.STARTING);
    
         // Start our defined Container first
         if (engine != null) {
             synchronized (engine) {
                 engine.start();
             }
         }
    
         synchronized (executors) {
             for (Executor executor: executors) {
                 executor.start();
             }
         }
    
         mapperListener.start();
    
         // Start our defined Connectors second
         synchronized (connectorsLock) {
             for (Connector connector: connectors) {
                 // If it has already failed, don't try and start it
                 if (connector.getState() != LifecycleState.FAILED) {
                     connector.start();
                 }
             }
         }
     }
    
    
  2. The StandardEngine#startInternal() method is relatively simple. The really important logic is actually encapsulated in its parent ContainerBase#startInternal() method

    protected synchronized void startInternal() throws LifecycleException {
    
         // Log our server identification information
         if (log.isInfoEnabled()) {
             log.info(sm.getString("standardEngine.start", ServerInfo.getServerInfo()));
         }
    
         // Standard container startup
         super.startInternal();
     }
    
  3. The core of the ContainerBase#startInternal() method is as follows:

    1. First, call ContainerBase#findChildren() of the container object itself to obtain the list of containers at the next level of the current container. For the Engine container, the Host of its next level container has been added during the creation of the container object. Here, the list of Host containers will be obtained
    2. Encapsulate each sub container object in the StartChild asynchronous task, and put the asynchronous task into the thread pool to run
    3. Asynchronous task processing, trigger child Start(), where the StandardHost#startInternal() method will eventually be triggered
    4. Call the setState() method to update the container lifecycle, which will call back the lifecycle listener
    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
         if (backgroundProcessorDelay > 0) {
             monitorFuture = Container.getService(ContainerBase.this).getServer()
                     .getUtilityExecutor().scheduleWithFixedDelay(
                             new ContainerBackgroundProcessorMonitor(), 0, 60, TimeUnit.SECONDS);
         }
     }
    
  4. The StandardHost#startInternal() method is implemented as follows. The main processing is as follows:

    1. First, find the processor Valve that handles the error report and add it to the pipline pipeline of the Host container
    2. super.startInternal() calls the parent ContainerBase#startInternal() method
    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();
     }
    
  5. Returning to the ContainerBase#startInternal() method, we focus on the update of the container life cycle, that is, the call of the setState() method. Trace the call chain to know that this method will eventually be called to the lifecycle base#setstateinternal () method

    For container objects that inherit LifecycleBase, event notification will be sent to the listener when their life cycle changes. For Host containers, HostConfig will also be notified and its callback method HostConfig#lifecycleEvent() will be triggered

    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);
         }
     }
    
     protected void fireLifecycleEvent(String type, Object data) {
         LifecycleEvent event = new LifecycleEvent(this, type, data);
         for (LifecycleListener listener : lifecycleListeners) {
             listener.lifecycleEvent(event);
         }
     }
    
  6. HostConfig#lifecycleEvent() is implemented as follows. Obviously, for lifecycle START_ The event event triggers the HostConfig#start() method

    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();
         }
     }
    
  7. The HostConfig#start() method is implemented as follows. Here, the configuration properties of the Host will be judged. If the application is deployed at startup, the HostConfig#deployApps() method will be called to start loading the web application. So far, the process of loading the web application has entered

     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();
    
     }
    

2.3 loading of web applications

  1. The HostConfig#deployApps() method is as follows. You can see that the main operation is to obtain the relevant directories of web applications and then deploy them. This paper focuses on the call of HostConfig#deployDirectories() method

     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);
    
     }
    
  2. The logic of the HostConfig#deployDirectories() method is simple and clear, which is to scan the folders of various web applications under the specified directory, encapsulate the relevant information as DeployDirectory, throw the asynchronous task into the thread pool for execution, and finally call the HostConfig#deployDirectory() method

    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);
             }
         }
     }
    
  3. The implementation of HostConfig#deployDirectory() method is relatively long, but the key steps are relatively clear:

    1. First, create a Context container object. The default Context implementation class is StandardContext
    2. Create a ContextConfig container configuration class and add it to the new Context container object as the container's lifecycle listener
    3. host.addChild(context) calls the method of adding the next level container to the Host container, and adds the Context container to the Host container. Here, it will be called to the StandardHost#addChild() method
     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 {
             
             ......
     }
    
  4. The StandardHost#addChild() method is as follows. The key step is super Addchild() calls the parent method, which will eventually trigger the execution of ContainerBase#addChildInternal() method

     public void addChild(Container child) {
    
         if (!(child instanceof Context))
             throw new IllegalArgumentException
                 (sm.getString("standardHost.notContext"));
    
         child.addLifecycleListener(new MemoryLeakTrackingListener());
    
         // Avoid NPE for case where Context is defined in server.xml with only a
         // docBase
         Context context = (Context) child;
         if (context.getPath() == null) {
             ContextName cn = new ContextName(context.getDocBase(), true);
             context.setPath(cn.getPath());
         }
    
         super.addChild(child);
    
     }
    
  5. The key step of the ContainerBase#addChildInternal() method is that after adding the next level container to the internal list, it will pass the child Start() to call the start method of the next level container, that is, the StandardContext#start() method will be triggered here and finally called to the StandardContext#startInternal() method

     private void addChildInternal(Container child) {
    
         if (log.isDebugEnabled()) {
             log.debug("Add child " + child + " " + this);
         }
    
         synchronized(children) {
             if (children.get(child.getName()) != null)
                 throw new IllegalArgumentException(
                         sm.getString("containerBase.child.notUnique", child.getName()));
             child.setParent(this);  // May throw IAE
             children.put(child.getName(), child);
         }
    
         fireContainerEvent(ADD_CHILD_EVENT, child);
    
         // Start child
         // Don't do this inside sync block - start can be a slow process and
         // locking the children object can cause problems elsewhere
         try {
             if ((getState().isAvailable() ||
                     LifecycleState.STARTING_PREP.equals(getState())) &&
                     startChildren) {
                 child.start();
             }
         } catch (LifecycleException e) {
             throw new IllegalStateException(sm.getString("containerBase.child.start"), e);
         }
     }
    
  6. The body of the StandardContext#startInternal() method is very long. It is mainly used to configure various properties for the Context container. This article focuses on the last setState() method call

     protected synchronized void startInternal() throws LifecycleException {
    
         ......
    
         // Reinitializing if something went wrong
         if (!ok) {
             setState(LifecycleState.FAILED);
         } else {
             setState(LifecycleState.STARTING);
         }
     }
    
  7. The process here is exactly the same as that in step 5 of this section. Finally, the ContextConfig#lifecycleEvent() method will be called. You can see that there are different processing methods corresponding to different events. Here, the ContextConfig#configureStart() method will be triggered to configure the container

      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();
         }
    
     }
    
  8. The core of the ContextConfig#configureStart() method is to call the ContextConfig#webConfig() method to enter the configuration of the web application

    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);
         }
    
     }
    
  9. The source code of ContextConfig#webConfig() method is long, and the important steps are as follows:

    1. Parsing the web. Of the current web application XML file to process the configured components
    2. Call the ContextConfig#configureContext() method to complete the configuration of web applications, mainly the creation of servlet s
     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) {
             // 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 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);
         }
    
         ......
    
     }
    
  10. The ContextConfig#configureContext() method is the core of configuring web applications. Its key processing is as follows:

    1. Configure the important properties of Context
    2. The web The servlet configured by the XML file is encapsulated in the Wrapper container, and the default implementation class of the Wrapper container is StandardWrapper
    3. context.addChild(wrapper) adds the Wrapper container to the Context container and starts it. So far, the main process of web application loading has basically ended
    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());
       }
       context.setRequestCharacterEncoding(webxml.getRequestCharacterEncoding());
       // 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);
       }
       context.setResponseCharacterEncoding(webxml.getResponseCharacterEncoding());
       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());
       }
    }
    

Topics: Java Tomcat server