2.Tomcat starts
  • Daemon.start (), analysis of the tomcat startup phase
# 1.Bootstrap's start() methodpublic void start(a)
    throws Exception {
    if( catalinaDaemon==null ) init();
    // Catalina's start() method is actually executed
    Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
    method.invoke(catalinaDaemon, (Object [])null);
}

# 1.Catalina's start() methodpublic void start(a) {
    if (getServer() == null) {
        load();
    }
    if (getServer() == null) {
        log.fatal("Cannot start server. Server instance is not configured.");
        return;
    }
    long t1 = System.nanoTime();
    // Start the new server
    try {
        // Start Server (emphasis)
        getServer().start();
    } catch (LifecycleException e) {
        log.fatal(sm.getString("catalina.serverStartFail"), e);
        try {
            getServer().destroy();
        } catch (LifecycleException e1) {
            log.debug("destroy() failed for failed Server ", e1);
        }
        return;
    }

    long t2 = System.nanoTime();
    if(log.isInfoEnabled()) {
        log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
    }

    // Register the shutdown hook
    if (useShutdownHook) {
        if (shutdownHook == null) {
            shutdownHook = new CatalinaShutdownHook();
        }
        Runtime.getRuntime().addShutdownHook(shutdownHook);
        // If JULI is being used, disable JULI's shutdown hook since
        // shutdown hooks run in parallel and log messages may be lost
        // if JULI's hook completes before the CatalinaShutdownHook()
        LogManager logManager = LogManager.getLogManager();
        if (logManager instanceof ClassLoaderLogManager) {
            ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                    false); }}if(await) { await(); stop(); }}Copy the code
  • GetServer ().start(), method to start Server, source analysis
# 1.LifecycleBase's start() method@Override
public final synchronized void start(a) throws LifecycleException {
    if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) ||
            LifecycleState.STARTED.equals(state)) {

        if (log.isDebugEnabled()) {
            Exception e = new LifecycleException();
            log.debug(sm.getString("lifecycleBase.alreadyStarted", toString()), e);
        } else if (log.isInfoEnabled()) {
            log.info(sm.getString("lifecycleBase.alreadyStarted", toString()));
        }

        return;
    }

    if (state.equals(LifecycleState.NEW)) {
        init();
    } else if (state.equals(LifecycleState.FAILED)) {
        stop();
    } else if(! state.equals(LifecycleState.INITIALIZED) && ! state.equals(LifecycleState.STOPPED)) { invalidTransition(Lifecycle.BEFORE_START_EVENT); }try {
        setStateInternal(LifecycleState.STARTING_PREP, null.false);

        // Start subclass StandardServer (emphasis)
        startInternal();

        if (state.equals(LifecycleState.FAILED)) {
            // This is a 'controlled' failure. The component put itself into the
            // FAILED state so call stop() to complete the clean-up.
            stop();
        } else if(! state.equals(LifecycleState.STARTING)) {// Shouldn't be necessary but acts as a check that sub-classes are
            // doing what they are supposed to.
            invalidTransition(Lifecycle.AFTER_START_EVENT);
        } else {
            setStateInternal(LifecycleState.STARTED, null.false); }}catch (Throwable t) {
        // This is an 'uncontrolled' failure so put the component into the
        // FAILED state and throw an exception.
        ExceptionUtils.handleThrowable(t);
        setStateInternal(LifecycleState.FAILED, null.false);
        throw new LifecycleException(sm.getString("lifecycleBase.startFail", toString()), t); }} #2.StandardServer's startInternal() method@Override
protected void startInternal(a) throws LifecycleException {

    fireLifecycleEvent(CONFIGURE_START_EVENT, null);
    setState(LifecycleState.STARTING);

    globalNamingResources.start();

    // Start our defined Services
    synchronized (servicesLock) {
        for (int i = 0; i < services.length; i++) {
            // Start services (emphasis)services[i].start(); }}}Copy the code

Server initializes the start() method by calling the superclass LifecycleBase’s start() method and then the startInternal() method of the subclass Server: The superclass start– > subclass startInternal is initialized in the same way as services, Connector, and engine.

  • Services [I].start()
# 1.StartInternal () method of standardService@Override
protected void startInternal(a) throws LifecycleException {

   if(log.isInfoEnabled())
        log.info(sm.getString("standardService.start.name".this.name));
    setState(LifecycleState.STARTING);

    // Start engine (emphasis)
    // Start our defined Container first
    if(engine ! =null) {
        synchronized(engine) { engine.start(); }}synchronized (executors) {
        for(Executor executor: executors) { executor.start(); }}/ / start mapperListener
    mapperListener.start();

    // Start our defined Connectors second
    synchronized (connectorsLock) {
        for (Connector connector: connectors) {
            try {
                // If it has already failed, don't try and start it
                if(connector.getState() ! = LifecycleState.FAILED) {// Start the connector (key)connector.start(); }}catch (Exception e) {
                log.error(sm.getString(
                        "standardService.connector.startFailed", connector), e); }}}}Copy the code

The engine and connector are started when the services are started. (executor, mapperListener)

  • Connector.start (), start the connector, source analysis
# 1.StartInternal () method of the Connector@Override
protected void startInternal(a) throws LifecycleException {

    // Validate settings before starting
    if (getPort() < 0) {
        throw new LifecycleException(sm.getString(
                "coyoteConnector.invalidPort", Integer.valueOf(getPort())));
    }
    setState(LifecycleState.STARTING);
    try {
        // Start the handler protocol (emphasis)
        protocolHandler.start();
    } catch (Exception e) {
        throw new LifecycleException(
                sm.getString("coyoteConnector.protocolHandlerStartFailed"), e); }} #2.Start () method of a protocolHandler in AbstractProtocol@Override
public void start(a) throws Exception {
    if (getLog().isInfoEnabled()) {
        getLog().info(sm.getString("abstractProtocolHandler.start", getName()));
    }

    / / start the endpoint
    endpoint.start();

    // Start async timeout thread
    asyncTimeout = new AsyncTimeout();
    Thread timeoutThread = new Thread(asyncTimeout, getNameInternal() + "-AsyncTimeout");
    int priority = endpoint.getThreadPriority();
    if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {
        priority = Thread.NORM_PRIORITY;
    }
    timeoutThread.setPriority(priority);
    timeoutThread.setDaemon(true);
    timeoutThread.start();
}

# 3.Start the start() method of the endpoint under AbstractEndpointpublic final void start(a) throws Exception {
    if (bindState == BindState.UNBOUND) {
        bind();
        bindState = BindState.BOUND_ON_START;
    }
    startInternal();
}
Copy the code

From the launch analysis of the Connector, you can see that the connector is started, which will initiate a protocolHandler and an endpoint

  • Engine. Start (), start engine, source analysis
# 1.StandardEngine's startInternal() method@Override
protected synchronized void startInternal(a) throws LifecycleException {

    // Log our server identification information
    if(log.isInfoEnabled())
        log.info( "Starting Servlet Engine: " + ServerInfo.getServerInfo());

    // Standard container startup
    super.startInternal();
}

# 2.ContainerBase startInternal() method@Override
protected synchronized void startInternal(a) throws LifecycleException {

    // Start our subordinate components, if any
    logger = null;
    // Log processing
    getLogger();
    // If a cluster exists, start the cluster
    Cluster cluster = getClusterInternal();
    if (cluster instanceof Lifecycle) {
        ((Lifecycle) cluster).start();
    }
    / / domain processing
    Realm realm = getRealmInternal();
    if (realm instanceof Lifecycle) {
        ((Lifecycle) realm).start();
    }

    // Start all child containers StandardHost, StandardContext, StandardWrapper
    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;

    // Start the thread to start the child container
    for (Future<Void> result : results) {
        try {
        	// Block until the child container starts before executing the following code
            result.get();
        } catch (Throwable e) {
            log.error(sm.getString("containerBase.threadedStartFailed"), e);
            if (multiThrowable == null) {
                multiThrowable = newMultiThrowable(); } multiThrowable.add(e); }}if(multiThrowable ! =null) {
        throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"),
                multiThrowable.getThrowable());
    }
    // Start Pipeline
    // Start the Valves in our pipeline (including the basic), if any
    if (pipeline instanceof Lifecycle) {
        ((Lifecycle) pipeline).start();
    }
    // Set the life cycle state to "starting" and invoke the listener: HostConfig to start Host (important).
    setState(LifecycleState.STARTING);
    // Start the thread
    threadStart();
}

# 3.StandardHost's startInternal() method@Override
protected synchronized void startInternal(a) throws LifecycleException {
    // Set error reporting
    // 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); }}// Call the parent class of ContainerBase again
    super.startInternal();
}

# 4.LifecycleBase.setState(LifecycleState.STARTING); Change the state of the lifecycle by firing the listener: HostConfig.protected synchronized void setState(LifecycleState state) throws LifecycleException {
   setStateInternal(state, null.true);
}

private synchronized void setStateInternal(LifecycleState state,
          Object data, boolean check) throws LifecycleException {
    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) {
    	// The listener is hostConfig.listener.lifecycleEvent(event); }} #5.HostConfig start ()public void lifecycleEvent(LifecycleEvent event) {
    // 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)) {
    	// Execute the start() method of hostConfig
        start();
    } else if(event.getType().equals(Lifecycle.STOP_EVENT)) { stop(); }}public void start(a) {.../ / webapps deployment
    if (host.getDeployOnStartup())
        deployApps();

}

# 6.The deployment ofwebapps
protected void deployApps(a) {
   File appBase = host.getAppBaseFile();
   File configBase = host.getConfigBaseFile();
   String[] filteredAppPaths = filterAppPaths(appBase.list());

   // Deploy XML file configuration server. XML 
      
        ...
       
   // Deploy XML descriptors from configBase
   deployDescriptors(configBase, configBase.list());

   // Deploy the WAR package
   // Deploy WARs
   deployWARs(appBase, filteredAppPaths);

   // Deploy the folder
   // Deploy expanded folders
   deployDirectories(appBase, filteredAppPaths);
}

# 7.Deploy folder deployDirectories()protected void deployDirectories(File appBase, String[] files) {
    // Publish the folder
    if (files == null)
        return;

    // Use future and thread pooling techniquesExecutorService 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;

            Submit (Runnable), receive a Runnable, and return a Future
            // New DeployDirectory (focus)
            results.add(es.submit(new DeployDirectory(this, cn, dir))); }}// Return null if the task is finished (wait for the thread to finish)
    for(Future<? > result : results) {try {
            result.get();
        } catch (Exception e) {
            log.error(sm.getString(
                    "hostConfig.deployDir.threaded.error"), e); }}}// Implement the Runnable interface
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(a) {
        // Deploy the folderconfig.deployDirectory(cn, dir); }} #8.The deployment folder deployDirectory()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()));
    }

    / / get the context
    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 {
                    // Parse the context nodecontext = (Context) digester.parse(xml); }}}else if(! deployThisXML && xml.exists()) { context =new FailedContext();
        } else {
            context = (Context) Class.forName(contextClass).getConstructor().newInstance();
        }

		// Instantiate ContextConfig as a LifecycleListener to the Context container
		// This is the same as StandardHost, using XXXConfigClass<? > 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());// Add the context to the child node and start the context (emphasis)host.addChild(context); }}Copy the code

From the startup process of standardEngine, we can see that the main start is standardHost. Then through the listener technology, start HostConfig, then parse webApps, start the context.

HostConfig’s deployDirectory mainly does several things: 1. Instantiate StandardContext 2 using Digester, or reflection. Instantiate ContextConfig and register event listeners for the Context container, starting and stopping the container using XXXConfig, just like StandardHost. 3. If the state of the current Container is STARTING_PREP and startChildren is true, add the current Context instance to the Host Container as a child Container. The logic for adding children is already implemented in ContainerBase. The child container is also started

  • StandardContext’s startInternal() method is analyzed
# 1.StartInternal () method of StandardContext@Override
protected synchronized void startInternal(a) throws LifecycleException {
   / / StandardContext startup

   if(log.isDebugEnabled())
       log.debug("Starting " + getBaseName());

   // Publish this status, broadcast it, and let others listen
   // 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;

   // Start namespace resources
   // 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();
   }

   // Create the work directory
   // Post work directory
   postWorkDirectory();

   // Load the resource
   // 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();
   }

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

   // Initialize a cookie
   // An explicit cookie processor hasn't been specified; use the default
   if (cookieProcessor == null) {
       cookieProcessor = new Rfc6265CookieProcessor();
   }

   // Mapping the character set
   // Initialize character set mapper
   getCharsetMapper();

   // Dependency processing
   // 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;
   }

   // The user names the attribute to get the environment variable
   // 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 = newNamingContextListener(); 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();

   // Emit a lifecycle event, triggering the listener: ContextConfig (ContextConfig)
   fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);

   // Start the wrapper child node (important)
   // Start our child containers, if not already started
   for (Container child : findChildren()) {
       if(! child.getState().isAvailable()) { child.start(); #}}}2.ConfigureStart () method of ContextConfigprotected synchronized void configureStart(a) {    
    // Parse web.xml (emphasis)
    // Parse the servlet, filter, and listener
    webConfig();
}

protected void webConfig(a) {   
    // Create a web.xml parser
    WebXmlParser webXmlParser = new WebXmlParser(context.getXmlNamespaceAware(),
            context.getXmlValidation(), context.getXmlBlockExternal());

    Set<WebXml> defaults = new HashSet<>();
    defaults.add(getDefaultWebXmlFragment(webXmlParser));

    WebXml webXml = createWebXml();

    / / parse 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) {
            / / configuration contextconfigureContext(webXml); }}else {
        webXml.merge(defaults);
        convertJsps(webXml);
        configureContext(webXml);
    }

    if (context.getLogEffectiveWebXml()) {
        log.info("web.xml:\n" + webXml.toXml());
    }

    // Always need to look for static resources
    // Step 10. Look for static resources packaged in JARs
    if (ok) {
        // Spec does not define an order.
        // Use ordered JARs followed by remaining JARs
        Set<WebXml> resourceJars = new LinkedHashSet<>();
        for (WebXml fragment : orderedFragments) {
            resourceJars.add(fragment);
        }
        for (WebXml fragment : fragments.values()) {
            if(! resourceJars.contains(fragment)) { resourceJars.add(fragment); } } processResourceJARs(resourceJars);// See also StandardContext.resourcesStart() for
        // WEB-INF/classes/META-INF/resources configuration
    }

    // Step 11. Apply the ServletContainerInitializer config to the
    // context
    if (ok) {
        for(Map.Entry<ServletContainerInitializer, Set<Class<? >>> entry : initializerClassMap.entrySet()) {if (entry.getValue().isEmpty()) {
                context.addServletContainerInitializer(
                        entry.getKey(), null);
            } else{ context.addServletContainerInitializer( entry.getKey(), entry.getValue()); }}}// Specify the ServletContext parameters
    mergeParameters();

    / / call ServletContainerInitializer# onStartup ()
    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; }}// Initialize the Filter
    if (ok) {
        if(! filterStart()) { log.error(sm.getString("standardContext.filterFail"));
            ok = false; }}// Handle the Wrapper container (if the Servlet loadOnStartup >= 0, the Servlet will be loaded at this stage)
    if (ok) {
        if(! loadOnStartup(findChildren())){ log.error(sm.getString("standardContext.servletFail"));
            ok = false; }}}Copy the code

StandardContext, like other Containers, overrides the startInternal method. Since the startup process of WebApp is involved, there is a lot of preparation, such as loading resource files using WebResourceRoot, loading classes using Loader, scanning JAR packages using JarScanner, and so on. Therefore, StandardContext’s startup logic is complex, and the following important steps are described:

  1. Create a working directory, such as $CATALINA_HOME\work\Catalina\localhost\examples; By instantiating the ContextServlet, the application gets the look and feel pattern of the ApplicationContext
  2. Instantiate WebResourceRoot. The default implementation class is StandardRoot, which reads the file resources of WebApp
  3. Instantiate a Loader object. Loader is Tomcat’s version of a ClassLoader that supports hot loading classes at runtime
  4. The CONFIGURE_START_EVENT event is emitted, which is processed by ContextConfig to read the servlet-related Listener, servlet, Filter, and so on from webApp
  5. Instantiate the Sesssion manager, using StandardManager by default
  6. Call listenerStart, instantiate the various listenerStart listenerStart listenerStart, instantiate the various listenerStart listenerStart listenerStart

ServletContextListener

  1. To deal with the Filter
  2. Load the Servlet
  • Triggers the CONFIGURE_START_EVENT event, triggering the ContextConfig listener

ContextConfig Is a LifycycleListener, which plays an important role in Context startup. StandardContext will emit the CONFIGURE_START_EVENT event, and ContextConfig will handle the event. The main purpose of this event is to configure via web.xml or Servlet3.0 annotations. The core logic of reading servlet-related configuration information, such as Filter, Servlet, Listener, etc., is implemented in ContextConfig#webConfig() method. Important steps performed by ContextConfig:

  1. Web.xml is parsed by WebXmlParser. If a web.xml file exists, the servlets, filters, and listeners defined in the file are registered with the WebXml instance
  2. If there is no web. XML file, tomcat will first scan the class file in the WEB-INF/classes directory, then scan the JAR package in the WEB-INF/lib directory, parse the bytecode and read the servlet-related annotation configuration class. Here I have to make fun of Servlet 3.0 annotations, the processing of servlet annotations is quite heavy. Instead of preloading the class into the JVM, Tomcat parses the bytecode file to get some information about the corresponding class, such as annotations, implemented interfaces, and so on
  • Step 9: Add a child Wrapper to the Context
# 1.Context adds wrapper child nodeprivate void configureContext(WebXml webxml) {
    // Set the Filter definition
    for (FilterDef filter : webxml.getFilters().values()) {
        if (filter.getAsyncSupported() == null) {
            filter.setAsyncSupported("false");
        }
        context.addFilterDef(filter);
    }
    // Set FilterMapping, which is the URL mapping of the Filter
    for (FilterMap filterMap : webxml.getFilterMappings()) {
        context.addFilterMap(filterMap);
    }
    // Add a child Wrapper (Servlet) to the Context
    for (ServletDef servlet : webxml.getServlets().values()) {
        Wrapper wrapper = context.createWrapper();
        // Omit some code...
        wrapper.setOverridable(servlet.isOverridable());
        context.addChild(wrapper);
    }
    / /...
}
Copy the code
  • Start the StandardWrapper container
# 1.StandardWrapper's startInternal() method@Override
protected synchronized void startInternal(a) throws LifecycleException {
    // Issue a j2ee.state.starting event notification
    if (this.getObjectName() ! =null) {
        Notification notification = new Notification("j2ee.state.starting".this.getObjectName(),
                                                    sequenceNumber++);
        broadcaster.sendNotification(notification);
    }
    // Start logic of ConainerBase
    super.startInternal();
    setAvailable(0L);

    // Issue a j2ee.state.running event notification
    if (this.getObjectName() ! =null) {
        Notification notification =
            new Notification("j2ee.state.running".this.getObjectName(), sequenceNumber++); broadcaster.sendNotification(notification); }} #2.StandardWrapper's load() method@Override
public synchronized void load(a) throws ServletException {
    // Instantiate the Servlet and call init to complete the initialization
    instance = loadServlet();
    if(! instanceInitialized) { initServlet(instance); }if (isJspServlet) {
        // Handle JSP servlets
        StringBuilder oname = new StringBuilder(getDomain());
        oname.append(":type=JspMonitor");
        oname.append(getWebModuleKeyProperties());
        oname.append(",name=");
        oname.append(getName());
        oname.append(getJ2EEKeyProperties());

        try {
            jspMonitorON = new ObjectName(oname.toString());
            Registry.getRegistry(null.null)
                .registerComponent(instance, jspMonitorON, null);
        } catch( Exception ex ) {
            log.info("Error registering JSP monitoring with jmx "+ instance); }}}Copy the code

StandardWrapper has no child container and its startup logic is relatively simple and clear. It overrides the startInternal method and mainly completes JMX event notification by sending starting and running events to JMX successively

If loadOnStartup >= 0, the load method will be called to load the Wrapper container. StandardWrapper instantiates the Servlet using InstanceManager and initializes it by calling the Init method of the Servlet, passing in the ServletConfig as a StandardWrapperFacade object.

Conclusion: tomcat implements javax.mail. Servlet. The ServletContext interface, in the Context startup will instantiate the object. The Context container reads the configuration of servlet3.0 through web. XML or scanning class bytecode, and loads the Servlet components such as Listeners, servlets, and filters defined by WebApp. But the object is not instantiated immediately. After loading, the Listener, Filter, and Servlet are instantiated and their initialization methods are called, such as servletcontextlist #contextInitialized(), Flter#init(), and so on

At this point, the Tomcat startup phase is complete. The use of chain of responsibility mode, step by step start. Component startup sequence: Server–>Service–>Engine–>Host–>Context–>Wrapper

  • Process of Tomcat initialization and startup:
3. Web request processing phase of Tomcat
  • An overall flowchart for a Web request

As you can see from the overall Tomcat architecture, Tomcat uses connectors to receive user requests and then sends them to Containers. Follow the start of the Connector by starting the protocolHandler, then the endpoint, then the Acceptor(to receive requests)

  • By looking at the main structure of the NioEndpoint, you can see that this class has three important internal classes: Acceptor, Poller, and SocketProcessor.
# 1.NioEndpoint's startInternal() method@Override
public void startInternal(a) throws Exception {

    if(! running) { running =true;
        paused = false;

        processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                socketProperties.getProcessorCache());
        eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                        socketProperties.getEventCache());
        nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                socketProperties.getBufferPool());

        // Create a worker thread pool
        // Create worker collection
        if ( getExecutor() == null ) {
            createExecutor();
        }

        initializeConnectionLatch();

        // Start the Poller thread to poll for new requests
        // Start poller threads
        pollers = new Poller[getPollerThreadCount()];
        for (int i=0; i<pollers.length; i++) {
            pollers[i] = new Poller();
            Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);
            pollerThread.setPriority(threadPriority);
            pollerThread.setDaemon(true);
            pollerThread.start();
        }

        // Start the Acceptor thread to receive user requestsstartAcceptorThreads(); }} #2.Start the Acceptor threadprotected final void startAcceptorThreads(a) {
    // Get the number of Acceptor threads (default: 1)
    int count = getAcceptorThreadCount();
    // Create an Acceptor array
    acceptors = new Acceptor[count];

    for (int i = 0; i < count; i++) {
        // Create an Acceptor object. Acceptor inherits Runnable
        acceptors[i] = createAcceptor();
        String threadName = getName() + "-Acceptor-" + i;
        acceptors[i].setThreadName(threadName);
        
        // Create Thread object
        Thread t = new Thread(acceptors[i], threadName);
        t.setPriority(getAcceptorThreadPriority());
        t.setDaemon(getDaemon());
        
        // Start the threadt.start(); }}Copy the code

NioEndpoint starts the Acceptor thread when the startInternal() method is executed. The Acceptor inherits from Runnable, and then uses Thread to start the Acceptor Thread.

  • Analysis of Acceptor Run () methods
# 1.Acceptor is used to receive requestsprotected class Acceptor extends AbstractEndpoint.Acceptor {
   @Override
    public void run(a) {
        int errorDelay = 0;
        System.out.println("Acceptor receiver starts execution");
        // Loop until we receive a shutdown command
        while (running) {
            // Loop if endpoint is paused
            while (paused && running) {
                state = AcceptorState.PAUSED;
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    // Ignore}}if(! running) {break;
            }
            state = AcceptorState.RUNNING;
            try {
                //if we have reached max connections, wait
                countUpOrAwaitConnection();

                SocketChannel socket = null;
                try {
                    // Receive the request and get the socket
                    socket = serverSock.accept();
                } catch (IOException ioe) {
                    // We didn't get a socket
                    countDownConnection();
                    if (running) {
                        // Introduce delay if necessary
                        errorDelay = handleExceptionWithDelay(errorDelay);
                        // re-throw
                        throw ioe;
                    } else {
                        break; }}// Successful accept, reset the error delay
                errorDelay = 0;

                // Configure the socket
                if(running && ! paused) {// Set some socket properties
                    if (!setSocketOptions(socket)) {
                        closeSocket(socket);
                    }
                } else{ closeSocket(socket); }}catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                log.error(sm.getString("endpoint.accept.fail"), t); } } state = AcceptorState.ENDED; }} #2.Set some socket propertiesprotected boolean setSocketOptions(SocketChannel socket) {
    // Process the connection
    try {
        //disable blocking, APR style, we are gonna be polling it
        socket.configureBlocking(false);
        Socket sock = socket.socket();
        socketProperties.setProperties(sock);

        // Convert SocketChannel to nioChannel
        NioChannel channel = nioChannels.pop();
        if (channel == null) {
            SocketBufferHandler bufhandler = new SocketBufferHandler(
                    socketProperties.getAppReadBufSize(),
                    socketProperties.getAppWriteBufSize(),
                    socketProperties.getDirectBuffer());
            if (isSSLEnabled()) {  //SSL, https
                channel = new SecureNioChannel(socket, bufhandler, selectorPool, this);
            } else {
                / / http1.1
                channel = newNioChannel(socket, bufhandler); }}else {
            channel.setIOChannel(socket);
            channel.reset();
        }
        // Get poller object, register channel (important)
        getPoller0().register(channel);
    } catch (Throwable t) {
        ExceptionUtils.handleThrowable(t);
        try {
            log.error("",t);
        } catch (Throwable tt) {
            ExceptionUtils.handleThrowable(tt);
        }
        // Tell to close the socket
        return false;
    }
    return true;
}

# 3.Poller object to register a channel. The Poller. The register () methodpublic void register(final NioChannel socket) {
    socket.setPoller(this);
    NioSocketWrapper ka = new NioSocketWrapper(socket, NioEndpoint.this);
    socket.setSocketWrapper(ka);
    ka.setPoller(this);
    ka.setReadTimeout(getSocketProperties().getSoTimeout());
    ka.setWriteTimeout(getSocketProperties().getSoTimeout());
    ka.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests());
    ka.setSecure(isSSLEnabled());
    ka.setReadTimeout(getConnectionTimeout());
    ka.setWriteTimeout(getConnectionTimeout());

    PollerEvent r = eventCache.pop();
    // Generate a poller and add the socket to the even queue
    ka.interestOps(SelectionKey.OP_READ);//this is what OP_REGISTER turns into.
    if ( r==null) r = new PollerEvent(socket,ka,OP_REGISTER);
    else r.reset(socket,ka,OP_REGISTER);
    addEvent(r);
}
Copy the code

By looking at the run method of Acceptor, you can see that the socket object has been retrieved. This is then done by generating poller. The Poller thread is primarily used to poll connected sockets with fewer resources to keep the connection alive and to transfer data to the worker thread when it becomes available.

  • Poller polls to see if there are new requests
# 1.The run() method of Poller@Override
public void run(a) {
    // Loop until destroy() is called
    while (true) {
	   	boolean hasEvents = false;
	    try {
	        if(! close) {// This method iterates through all pollorevents in the EventQueue
	            // Then call the pollorEvent run method in turn
	            // Register the socket with the selector
	            hasEvents = events();
	            if (wakeupCounter.getAndSet(-1) > 0) {               
	                keyCount = selector.selectNow();
	            } else {
	                keyCount = selector.select(selectorTimeout);
	            }
	            wakeupCounter.set(0);
	        }
	        if (close) {
	            events();
	            timeout(0.false);
	            try {
	                selector.close();
	            } catch (IOException ioe) {
	                log.error(sm.getString("endpoint.nio.selectorCloseFail"), ioe);
	            }
	            break; }}catch (Throwable x) {
	        ExceptionUtils.handleThrowable(x);
	        log.error("",x);
	        continue;
	    }
	    //either we timed out or we woke up, process events first
	    if ( keyCount == 0 ) hasEvents = (hasEvents | events());
	
	    Iterator<SelectionKey> iterator =
	        keyCount > 0 ? selector.selectedKeys().iterator() : null;
	    // Walk through the collection of ready keys and dispatch
	    // any active event.
	    while(iterator ! =null && iterator.hasNext()) {
	        SelectionKey sk = iterator.next();
	        NioSocketWrapper attachment = (NioSocketWrapper)sk.attachment();
	        // Attachment may be null if another thread has called
	        // cancelledKey()
	        if (attachment == null) {
	            iterator.remove();
	        } else {
	            iterator.remove();
	            // Processing (emphasis)processKey(sk, attachment); }}//while
	     //process timeouts
	     timeout(keyCount,hasEvents);
 	}//while
  	getStopLatch().countDown();
}

# 2.The Poller, processingprocessKey
protected void processKey(SelectionKey sk, NioSocketWrapper attachment) {
    try {
        if ( close ) {
            cancelledKey(sk);
        } else if( sk.isValid() && attachment ! =null ) {
            if (sk.isReadable() || sk.isWritable() ) {
                if( attachment.getSendfileData() ! =null ) {
                    processSendfile(sk,attachment, false);
                } else {
                    unreg(sk, attachment, sk.readyOps());
                    boolean closeSocket = false;
                    // Read goes before write
                    / / read event
                    if (sk.isReadable()) {
                        // Handle the socket (create worker, emphasis)
                        if(! processSocket(attachment, SocketEvent.OPEN_READ,true)) {
                            closeSocket = true; }}/ / write events
                    if(! closeSocket && sk.isWritable()) {if(! processSocket(attachment, SocketEvent.OPEN_WRITE,true)) {
                            closeSocket = true; }}if(closeSocket) { cancelledKey(sk); }}}}else {
            //invalid keycancelledKey(sk); }}catch ( CancelledKeyException ckx ) {
        cancelledKey(sk);
    } catch (Throwable t) {
        ExceptionUtils.handleThrowable(t);
        log.error("",t); }}Copy the code

The Poller thread is primarily used to poll the connected socket with fewer resources to keep the connection. When data is available, it is forwarded to the worker thread. By tracing the run method of the Poller, you can see that further processing of the socket is created by the SocketProcessor (worker).

  • SocketProcessor (Worker) Processes socket processes.
# 1.AbstractEndpoint's processSocket() method/ / handle the socket
public boolean processSocket(SocketWrapperBase<S> socketWrapper,
        SocketEvent event, boolean dispatch) {
    try {
        if (socketWrapper == null) {
            return false;
        }
        // Create SocketProcessor (worker)
        SocketProcessorBase<S> sc = processorCache.pop();
        if (sc == null) {
            sc = createSocketProcessor(socketWrapper, event);
        } else {
            sc.reset(socketWrapper, event);
        }
        / / execution
        Executor executor = getExecutor();
        if(dispatch && executor ! =null) {
            executor.execute(sc);
        } else{ sc.run(); }}catch (RejectedExecutionException ree) {
        getLog().warn(sm.getString("endpoint.executor.fail", socketWrapper) , ree);
        return false;
    } catch (Throwable t) {
        ExceptionUtils.handleThrowable(t);
        // This means we got an OOM or similar creating a thread, or that
        // the pool and its queue are full
        getLog().error(sm.getString("endpoint.process.fail"), t);
        return false;
    }
    return true;
}
Copy the code
  • DoRun () analysis of SocketProcessor.
# 1.DoRun () method of the SocketProcessorprotected class SocketProcessor extends SocketProcessorBase<NioChannel> {
    public SocketProcessor(SocketWrapperBase<NioChannel> socketWrapper, SocketEvent event) {
        super(socketWrapper, event);
    }
    @Override
    protected void doRun(a) {
        NioChannel socket = socketWrapper.getSocket();
        SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
        try {
            int handshake = -1;
          
            if (handshake == 0) {
                SocketState state = SocketState.OPEN;
                // Process the request from this socket
                if (event == null) {
                    // Handle the socket (emphasis)
                    state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ);
                } else {
                    state = getHandler().process(socketWrapper, event);
                }
                if(state == SocketState.CLOSED) { close(socket, key); }}}}} #2.AbstractProtocol's ConnectionHandler process() method. (Only the focused code is posted)@Override
public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) {
    // Get the socket object
    S socket = wrapper.getSocket();
    Processor processor = connections.get(socket);
    if (processor == null) {
        // Create Processor (emphasis)
        processor = getProtocol().createProcessor();
        register(processor);
    }
    // Execute the wrapper through the processor (important)
    state = processor.process(wrapper, status);
    // Make sure socket/processor is removed from the list of current
    // connections
    connections.remove(socket);
    release(processor);
    return SocketState.CLOSED;
}

# 3.AbstractHttp11Protocol. CreateProcessor () method. To create a Processor.protected Processor createProcessor(a) {
  / / build Http11Processor
    Http11Processor processor = new Http11Processor(getMaxHttpHeaderSize(),
            getAllowHostHeaderMismatch(), getRejectIllegalHeaderName(), getEndpoint(),
            getMaxTrailerSize(), allowedTrailerHeaders, getMaxExtensionSize(),
            getMaxSwallowSize(), httpUpgradeProtocols, getSendReasonPhrase(),
            relaxedPathChars, relaxedQueryChars);
    // Set adapter adapter
    processor.setAdapter(getAdapter());

    // Maximum number of keepAlive requests handled by each socket by default
    processor.setMaxKeepAliveRequests(getMaxKeepAliveRequests());

    // Enable keepAlive Timeout
    processor.setConnectionUploadTimeout(getConnectionUploadTimeout());

    // HTTP defaults to a timeout (300 x 1000) when uploading files
    processor.setDisableUploadTimeout(getDisableUploadTimeout());

    // When the HTTP request body size exceeds this value, it is compressed by gzip
    processor.setCompressionMinSize(getCompressionMinSize());

    // Whether to enable compression for HTTP requests
    processor.setCompression(getCompression());
    processor.setNoCompressionUserAgents(getNoCompressionUserAgents());
    / / HTTP content inside the body is "text/HTML, text/XML, text/plain"
    // will be compressed
    processor.setCompressibleMimeTypes(getCompressibleMimeTypes());
    processor.setRestrictedUserAgents(getRestrictedUserAgents());

    // The maximum post processing size is 4*1000
    processor.setMaxSavePostSize(getMaxSavePostSize());
    processor.setServer(getServer());
    processor.setServerRemoveAppProvidedValues(getServerRemoveAppProvidedValues());
    return processor;
}

# 4.Through the processor execution wrapper (AbstractProcessorLight. The process () method)@Override
public SocketState process(SocketWrapperBase
        socketWrapper, SocketEvent status)
        throws IOException {
    SocketState state = SocketState.CLOSED;
    Iterator<DispatchType> dispatches = null;
    do {
        if(dispatches ! =null) {
            DispatchType nextDispatch = dispatches.next();
            state = dispatch(nextDispatch.getSocketStatus());
        } else if (status == SocketEvent.DISCONNECT) {
            // Do nothing here, just wait for it to get recycled
        } else if (isAsync() || isUpgrade() || state == SocketState.ASYNC_END) {
            state = dispatch(status);
            if (state == SocketState.OPEN) {   
                // Execute the service method to handle the socket (emphasis)state = service(socketWrapper); }}else if (status == SocketEvent.OPEN_WRITE) {
            // Extra write event likely after async, ignore
            state = SocketState.LONG;
        } else if (status == SocketEvent.OPEN_READ){
            state = service(socketWrapper);
        } else {           
            state = SocketState.CLOSED;
        }
        if (dispatches == null| |! dispatches.hasNext()) {// Only returns non-null iterator if there are
            // dispatches to process.dispatches = getIteratorAndClearDispatches(); }}while(state == SocketState.ASYNC_END || dispatches ! =null&& state ! = SocketState.CLOSED);return state;
}

# 5.Http11Processor's service() method (keeping only key code)@Override
public SocketState service(SocketWrapperBase
        socketWrapper){
	// Processing requests through adapter (emphasis)
  	getAdapter().service(request, response);
}
Copy the code

Based on the above code analysis, the SocketProcessor is processing the socket object, which is ultimately handled by calling the getAdapter().service(request, response) method.

  • Let’s go to the getAdapter().service(request, response) method.
# 1.The CoyoteAdapter's service() method@Override
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)  
        throws Exception {  // All requests are received
    // Convert request and Response
    Request request = (Request) req.getNote(ADAPTER_NOTES);
    Response response = (Response) res.getNote(ADAPTER_NOTES);
    if (request == null) {
        // Create request and response via connector
        request = connector.createRequest();
        request.setCoyoteRequest(req);
        response = connector.createResponse();
        response.setCoyoteResponse(res);
        // Link connects request and response
        request.setResponse(response);
        response.setRequest(request);
        // Set as notes
        req.setNote(ADAPTER_NOTES, request);
        res.setNote(ADAPTER_NOTES, response);
        // Set the encoding of the URI
        req.getParameters().setQueryStringCharset(connector.getURICharset());
    }
    if (connector.getXpoweredBy()) {
        response.addHeader("X-Powered-By", POWERED_BY);
    }
    boolean async = false;
    boolean postParseSuccess = false;
    req.getRequestProcessor().setWorkerThreadName(THREAD_NAME.get());

    try {

        // Parse and set Catalina and configuration specific
        // request parameters
        // Resolve the business request in the map (important)
        postParseSuccess = postParseRequest(req, request, res, response);
        if (postParseSuccess) {
            // Set asynchronous support (getContainer() gets engine)
            //check valves if we support async
            request.setAsyncSupported(
                    connector.getService().getContainer().getPipeline().isAsyncSupported());
            // Calling the container
            / / execution pipeline pipe (invoke) standardEngineValve. Invoke (key)
            connector.getService().getContainer().getPipeline().getFirst().invoke(
                    request, response);
        }
        if (request.isAsync()) {       
         
        } else {
            // Request and response completerequest.finishRequest(); response.finishResponse(); #}}}2.StandardEngineValve. Invoke () method@Override
public final void invoke(Request request, Response response)
    throws IOException, ServletException {
    // Get the StandardHost object
    // Select the Host to be used for this Request
    Host host = request.getHost();
    if (host == null) {
        response.sendError
            (HttpServletResponse.SC_BAD_REQUEST,
             sm.getString("standardEngine.noHost",
                          request.getServerName()));
        return;
    }
    if (request.isAsyncSupported()) {
        request.setAsyncSupported(host.getPipeline().isAsyncSupported());
    }
    // Execute the standardHostValue.invoke() method (emphasis)
    // Ask this Host to process this request
    host.getPipeline().getFirst().invoke(request, response);
}

# 3.StandardHostValue. Invoke () method@Override
public final void invoke(Request request, Response response)
    throws IOException, ServletException {
    / / get standardContext
    Context context = request.getContext();
    if (context == null) {
        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
             sm.getString("standardHost.noContext"));
        return;
    }
    if (request.isAsyncSupported()) {
        request.setAsyncSupported(context.getPipeline().isAsyncSupported());
    }
    boolean asyncAtStart = request.isAsync();
    boolean asyncDispatching = request.isAsyncDispatching();
    try {
        context.bind(Globals.IS_SECURITY_ENABLED, MY_CLASSLOADER);
        if(! asyncAtStart && ! context.fireRequestInitEvent(request.getRequest())) {return;
        }
        try {
            if(! asyncAtStart || asyncDispatching) {/ / execution standardContextValue. Invoke () method (key)context.getPipeline().getFirst().invoke(request, response); }}}} #4.StandardContextValue. Invoke () method@Override
public final void invoke(Request request, Response response)
    throws IOException, ServletException {    
    / / get StandardWrapper
    // Select the Wrapper to be used for this Request
    Wrapper wrapper = request.getWrapper();
    if (wrapper == null || wrapper.isUnavailable()) {
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return;
    }

    if (request.isAsyncSupported()) {
        request.setAsyncSupported(wrapper.getPipeline().isAsyncSupported());
    }
    / / execution standardWrapperValue. Invoke () method (key)
    wrapper.getPipeline().getFirst().invoke(request, response);
}

# 5.StandardWrapperValue. Invoke () method// Finally find the servlet processing
@Override
public final void invoke(Request request, Response response)
    throws IOException, ServletException {

    // Initialize local variables we may need
    boolean unavailable = false;
    Throwable throwable = null;
    // This should be a Request attribute...
    long t1=System.currentTimeMillis();
    // Increase the number of requests, CAS
    requestCount.incrementAndGet();
    StandardWrapper wrapper = (StandardWrapper) getContainer();

    // Get the servlet object
    Servlet servlet = null;
    Context context = (Context) wrapper.getParent();

    // Check for the application being marked unavailable
    if(! context.getState().isAvailable()) { response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, sm.getString("standardContext.isUnavailable"));
        unavailable = true;
    }

    // Check for the servlet being marked unavailable
    if(! unavailable && wrapper.isUnavailable()) { container.getLogger().info(sm.getString("standardWrapper.isUnavailable",
                wrapper.getName()));
        long available = wrapper.getAvailable();
        if ((available > 0L) && (available < Long.MAX_VALUE)) {
            response.setDateHeader("Retry-After", available);
            response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
                    sm.getString("standardWrapper.isUnavailable",
                            wrapper.getName()));
        } else if (available == Long.MAX_VALUE) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND,
                    sm.getString("standardWrapper.notFound",
                            wrapper.getName()));
        }
        unavailable = true;
    }

    // By default, servlets are instantiated on the first request
    // Allocate a servlet instance to process this request
    try {
        if(! unavailable) {// If you can't get one, load one againservlet = wrapper.allocate(); }}catch (UnavailableException e) {
        container.getLogger().error(
                sm.getString("standardWrapper.allocateException",
                        wrapper.getName()), e);
        long available = wrapper.getAvailable();
        if ((available > 0L) && (available < Long.MAX_VALUE)) {
            response.setDateHeader("Retry-After", available);
            response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
                       sm.getString("standardWrapper.isUnavailable",
                                    wrapper.getName()));
        } else if (available == Long.MAX_VALUE) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND,
                       sm.getString("standardWrapper.notFound", wrapper.getName())); }}catch (ServletException e) {
        container.getLogger().error(sm.getString("standardWrapper.allocateException",
                         wrapper.getName()), StandardWrapper.getRootCause(e));
        throwable = e;
        exception(request, response, e);
    } catch (Throwable e) {
        ExceptionUtils.handleThrowable(e);
        container.getLogger().error(sm.getString("standardWrapper.allocateException",
                         wrapper.getName()), e);
        throwable = e;
        exception(request, response, e);
        servlet = null;
    }

    / / get the path
    MessageBytes requestPathMB = request.getRequestPathMB();
    DispatcherType dispatcherType = DispatcherType.REQUEST;
    if (request.getDispatcherType()==DispatcherType.ASYNC) dispatcherType = DispatcherType.ASYNC;
    request.setAttribute(Globals.DISPATCHER_TYPE_ATTR,dispatcherType);
    request.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR,
            requestPathMB);
    // Create the filter chain for this request
    ApplicationFilterChain filterChain =
            ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);

    // Execute the corresponding filter chain
    // Call the filter chain for this request
    // NOTE: This also calls the servlet's service() method
    try {
        if((servlet ! =null) && (filterChain ! =null)) {
            // Swallow output if needed
            if (context.getSwallowOutput()) {
                try {
                    SystemLogHandler.startCapture();
                    if (request.isAsyncDispatching()) {
                        request.getAsyncContextInternal().doInternalDispatch();
                    } else{ filterChain.doFilter(request.getRequest(), response.getResponse()); }}finally {
                    String log = SystemLogHandler.stopCapture();
                    if(log ! =null && log.length() > 0) { context.getLogger().info(log); }}}else {
                if (request.isAsyncDispatching()) {
                    request.getAsyncContextInternal().doInternalDispatch();
                } else{ filterChain.doFilter (request.getRequest(), response.getResponse()); }}}}catch (ClientAbortException | CloseNowException e) {
        if (container.getLogger().isDebugEnabled()) {
            container.getLogger().debug(sm.getString(
                    "standardWrapper.serviceException", wrapper.getName(),
                    context.getName()), e);
        }
        throwable = e;
        exception(request, response, e);
    } catch (IOException e) {
        container.getLogger().error(sm.getString(
                "standardWrapper.serviceException", wrapper.getName(),
                context.getName()), e);
        throwable = e;
        exception(request, response, e);
    } catch (UnavailableException e) {
        container.getLogger().error(sm.getString(
                "standardWrapper.serviceException", wrapper.getName(),
                context.getName()), e);
        // throwable = e;
        // exception(request, response, e);
        wrapper.unavailable(e);
        long available = wrapper.getAvailable();
        if ((available > 0L) && (available < Long.MAX_VALUE)) {
            response.setDateHeader("Retry-After", available);
            response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
                       sm.getString("standardWrapper.isUnavailable",
                                    wrapper.getName()));
        } else if (available == Long.MAX_VALUE) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND,
                        sm.getString("standardWrapper.notFound",
                                    wrapper.getName()));
        }
        // Do not save exception in 'throwable', because we
        // do not want to do exception(request, response, e) processing
    } catch (ServletException e) {
        Throwable rootCause = StandardWrapper.getRootCause(e);
        if(! (rootCauseinstanceof ClientAbortException)) {
            container.getLogger().error(sm.getString(
                    "standardWrapper.serviceExceptionRoot",
                    wrapper.getName(), context.getName(), e.getMessage()),
                    rootCause);
        }
        throwable = e;
        exception(request, response, e);
    } catch (Throwable e) {
        ExceptionUtils.handleThrowable(e);
        container.getLogger().error(sm.getString(
                "standardWrapper.serviceException", wrapper.getName(),
                context.getName()), e);
        throwable = e;
        exception(request, response, e);
    }

    // Release the filter chain (if any) for this request
    if(filterChain ! =null) {
        filterChain.release();
    }

    // Deallocate the allocated servlet instance
    try {
        if(servlet ! =null) { wrapper.deallocate(servlet); }}catch (Throwable e) {
        ExceptionUtils.handleThrowable(e);
        container.getLogger().error(sm.getString("standardWrapper.deallocateException",
                         wrapper.getName()), e);
        if (throwable == null) { throwable = e; exception(request, response, e); }}// If this servlet has been marked permanently unavailable,
    // unload it and release this instance
    try {
        if((servlet ! =null) && (wrapper.getAvailable() == Long.MAX_VALUE)) { wrapper.unload(); }}catch (Throwable e) {
        ExceptionUtils.handleThrowable(e);
        container.getLogger().error(sm.getString("standardWrapper.unloadException",
                         wrapper.getName()), e);
        if (throwable == null) { throwable = e; exception(request, response, e); }}}Copy the code

Through the analysis of CoyoteAdapter service() method, we can know that he is a step by step call method. StandardEngineValue–>StandardHostValue–>StandardContextValue–>StandardWrapperValue invoke method.

  • Flow chart of web request:
  • Attachment: Tomcat source code (annotated)github.com/llsydn/tomc…
  • Tomcat class diagram