Tomcat source code analysis | multi graph analysis of the start process when Tomcat starts (including the application start process in the container)

Keywords: Java Tomcat source code

Tomcat source code analysis - startup

The source code of this article comes from Tomcat 8.5.33

The reference cited in this paper is Tomcat architecture analysis - Liu Guangrui

Note: This article is a serial article. You can refer to the previous article class loader initialization and the subsequent article processing requests

preface

As we have analyzed above, after Tomcat is initialized, some components have called the init() method. The next step is to load and start the Web application.

Catalina loads Web applications mainly by five classes: standardhost, hostconfig, standardcontext, contextconfig and standwrapper.

Source code analysis

flow chart

code analysis

1. Bootstrap.main()

After initializing the component, call start() to start loading the Web application; In fact, catalina.start() is called through reflection; See method 2;

// Bootstrap.class
public static void main(String args[]) {
    // ...
    try {
        if (command.equals("start")) {
            daemon.setAwait(true);
            // load
            daemon.load(args);
            // start-up
            daemon.start();
            if (null == daemon.getServer()) {
                System.exit(1);
            }
        }
    } catch(e){}
    // ...
}

public void start() throws Exception {
    if (catalinaDaemon == null) {
        init();
    }
    // Reflection calls catalina.start()
    Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
    method.invoke(catalinaDaemon, (Object [])null);
}
2. Catalina.start()

We can see that StandardServer.start() is called here, see method 3, and then the main thread blocks and waits.

// Catalina.class
public void start() {

    if (getServer() == null) {
        load();
    }

    if (getServer() == null) {
        log.fatal("Cannot start server. Server instance is not configured.");
        return;
    }

    // Start the new server
    try {
        getServer().start();
    } catch (LifecycleException e) {
        return;
    }

    // Register shutdown hook
    // Register close callback
    if (useShutdownHook) {
        // ...
    }

    if (await) {
        // The main thread wait s here
        await();
        stop();
    }
}
3. StandardServer#LifecycleBase.start()

Similarly, the start() method of server is completed by the life cycle parent class. Then call startInternal() implemented by the subclass, see method 4;

// StandardServer#LifecycleBase
public final synchronized void start() throws LifecycleException {

    if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) ||
        LifecycleState.STARTED.equals(state)) {
        return;
    }

    // Initialize uninitialized components
    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);
        // Call subclass startup
        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.
        handleSubClassException(t, "lifecycleBase.startFail", toString());
    }
}
4. StandardServer.startInternal()

Here, the standard server starts the global resources (Global naming resources defines the global JNDI resources of the server);

Then start all services, as shown in method 5.

// StandardServer.class
protected void startInternal() throws LifecycleException {

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

    globalNamingResources.start();

    // Start our defined Services
    synchronized (servicesLock) {
        for (Service service : services) {
            // Start Service
            service.start();
        }
    }
}
5. StandardService.startInternal()

Service starts its lower container here:

Standard engine; See method 7;

Standardthreadexecution thread pool; See method 6;

connector; See method 15;

// StandardService.class
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
    // Start the engine
    if (engine != null) {
        synchronized (engine) {
            engine.start();
        }
    }
    // Start thread pool
    synchronized (executors) {
        for (Executor executor: executors) {
            executor.start();
        }
    }
    // Start Mapper listener
    mapperListener.start();
    // Start connector
    // 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) {
                    connector.start();
                }
            } catch (Exception e) {
                log.error(sm.getString(
                    "standardService.connector.startFailed",
                    connector), e);
            }
        }
    }
}
6. StandardThreadExecutor.startInternal()

Here, tomcat encapsulates and maintains a thread pool.

Firstly, it defines a queue TaskQueue, which is the encapsulation of LinkedBlockingQueue. Because LinkedBlockingQueue is an unbounded queue, there is still a chance to create new threads for the thread pool, so rewrite the offer() method to make it "bounded"; offer() returns false and creates a relief thread to process;

It defines a thread pool with the default number of cores (minSpareThreads) of 25, the default number of maximum threads (maxThreads) of 100, and the keep alive time of 6s. We can configure it in server.xml:

<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="150" minSpareThreads="4"/>

Then the trigger status is start;

The design pattern embodied here: facade pattern, which provides a unified exposure interface org.apache.tomcat.util.threads.ResizableExecutor;

public interface ResizableExecutor extends Executor {

    /**
     * Returns the current number of threads in the pool.
     *
     * @return the number of threads
     */
    public int getPoolSize();

    public int getMaxThreads();

    /**
     * Returns the approximate number of threads that are actively executing
     * tasks.
     *
     * @return the number of threads
     */
    public int getActiveCount();

    public boolean resizePool(int corePoolSize, int maximumPoolSize);

    public boolean resizeQueue(int capacity);

}

The specific source code is as follows:

// StandardThreadExecutor.class
protected void startInternal() throws LifecycleException {

    // Tomcat implements a custom task queue and rewrites the offer method, so that the thread pool still has the opportunity to create new threads when the length of the task queue is unlimited.
    taskqueue = new TaskQueue(maxQueueSize);
    // Thread name Tomcat Exec-
    // daemon=true all threads are daemon threads
    // getThreadPriority() defaults to 5
    TaskThreadFactory tf = new TaskThreadFactory(namePrefix,daemon,getThreadPriority());
    // getMinSpareThreads() number of core threads. The default configuration is 25
    // getMaxThreads() maximum number of threads. The default configuration is 100
    // maxIdleTime live time, 60000ms by default
    executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS,taskqueue, tf);
    executor.setThreadRenewalDelay(threadRenewalDelay);
    if (prestartminSpareThreads) {
        executor.prestartAllCoreThreads();
    }
    taskqueue.setParent(executor);

    setState(LifecycleState.STARTING);
}
7. StandardEngine.startInternal()

This is the first container we started. When it was started, it made a log, and the main logic was handed over to the parent ContainerBase container to implement; see method 8

// StandardEngine.class
@Override
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
    // Call ContainerBase#startInternal()
    super.startInternal();
}
8. StandardEngine#ContainerBase.startInternal()

The engine does the following here:

  • Cluster, security domain startup;

  • Start the child container; by creating the thread startStopExecutor.submit(new StartChild(container)), the container.start() is called. The container here is actually the second container - StandardHost. See method 9;

  • Startup pipeline;

  • Set the Engine status to STARTING, and the START_EVENT life cycle event will be triggered. EngineConfig listens to this event and makes a startup log;

  • Start background task processing at Engine level;

// ContainerBase
protected synchronized void startInternal() throws LifecycleException {
    // Start our subordinate components, if any
    logger = null;
    getLogger();
    // Cluster configuration startup
    Cluster cluster = getClusterInternal();
    if (cluster instanceof Lifecycle) {
        ((Lifecycle) cluster).start();
    }
    // Security domain startup
    Realm realm = getRealmInternal();
    if (realm instanceof Lifecycle) {
        ((Lifecycle) realm).start();
    }

    // Start our child containers, if any
    // Promoter container
    Container children[] = findChildren();
    List<Future<Void>> results = new ArrayList<>();
    for (Container child : children) {
        // Start by thread
        results.add(startStopExecutor.submit(new StartChild(child)));
    }

    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
    // Start pipeline
    if (pipeline instanceof Lifecycle) {
        ((Lifecycle) pipeline).start();
    }
    /*
         *StandardEngine: 
         * Set the Engine status to STARTING, and the START_EVENT lifecycle event will be triggered.
         * EngineConfig Listen to this event and make a startup log
         */
    setState(LifecycleState.STARTING);

    /*
         * StandardEngine:
         * Start background task processing at Engine level: Cluster background task processing (including deployment change detection and heartbeat), and real background task processing
         * Pipeline Background task processing in Value
         */
    // Start our thread
    threadStart();
}
9.StandardHost.startInternal()

The StandardHost here adds an error report Value(ErrorReportValve) to the pipeline, and then calls the parent ContainerBase container method.

The parent class of Host does the following:

  • Cluster, security domain startup;

  • Start the child container; by creating the thread startStopExecutor.submit(new StartChild(container)), the container.start() is called. See method 10. The container here is actually the third container -- StandardContext;

  • Startup pipeline;

  • Set the Host status to STARTING, and a START_EVENT life cycle event will be triggered. HostConfig listens to this event: scan the Web deployment directory. For deployment description files and WAR packages, the directory will automatically create a StandardContext instance, add it to the Host and start it; see Method 14;

  • Start the Host level background task processing: Cluster background task processing (including deployment change detection and heartbeat), real background task processing, and Value background task processing in Pipeline (some values realize periodic processing functions through background tasks, such as StuckThreadDetectionValue, which is used to regularly detect time-consuming requests and output them);

Therefore, we can see that there are two ways for the Host to create a sub container:

  1. The < context / > node configured through server.xml;
  2. By directly deploying the application, scan and create in HostConfig; see method 10;
// StandardHost
protected synchronized void startInternal() throws LifecycleException {

    // Set error report valve
    // Add org.apache.catalina.values.errorreportvalve error page to Pipleline
    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 parent method
    super.startInternal();
}

// StandardHost#ContainerBase
protected synchronized void startInternal() throws LifecycleException {

    // Start our subordinate components, if any
    logger = null;
    getLogger();
    // Cluster configuration startup
    Cluster cluster = getClusterInternal();
    if (cluster instanceof Lifecycle) {
        ((Lifecycle) cluster).start();
    }
    // Security domain startup
    Realm realm = getRealmInternal();
    if (realm instanceof Lifecycle) {
        ((Lifecycle) realm).start();
    }

    // Start our child containers, if any
    // Start sub container context [path 1]
    Container children[] = findChildren();
    List<Future<Void>> results = new ArrayList<>();
    for (Container child : children) {
        results.add(startStopExecutor.submit(new StartChild(child)));
    }

    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
    // Start pipeline
    if (pipeline instanceof Lifecycle) {
        ((Lifecycle) pipeline).start();
    }
    /*
     *StandardHost: 
     * Set the Host status to STARTING, and the START_EVENT lifecycle event will be triggered.
     * HostConfig Listen to this event and scan the Web deployment directory. For the deployment description file, WAR package and directory, the StandardContext instance will be automatically created,
     * Add to Host and start
     */
    // Start sub container context [path 2]
    setState(LifecycleState.STARTING);
 	/*
     * StandardHost:
     * Start the Host level background task processing: Cluster background task processing (including deployment change detection and heartbeat), real background task processing,
     * Pipeline Background task processing of Value in (some values realize periodic processing function through background tasks,
     * For example, StuckThreadDetectionValue is used to regularly detect time-consuming requests and output them)
     */
    // Start our thread
    threadStart();
}
10. StandardContext.startInternal()

context is a Web application. Its startup is very complex. The specific steps are as follows (synchronized with code comments):

Let's understand this. If you are interested, go to page P62 of Tomcat architecture analysis - Liu Guangrui. Anyway, I'm dizzy. Pay attention to the following methods: Method 11, method 12 and method 13;

  1. Publish the JMX notification that is being started. You can listen to the start of the Web application by adding a notificationlister;
  2. Processing working directory;
  3. Initialize the WebResourceRoot used by the current Context and start it. WebResourceRoot maintains all resource collections (Class files, Jar packages and other resource files) of Web applications, which are mainly used to load classes and find resource files according to paths;
  4. Create a Web application class loader. WebappLoader inherits lifecycle mbeanbase and creates a Web application class loader when it starts; (WebappClassLoader). In addition, this class also provides background process for Context background processing. When the class file of the Web application is detected and the Jar package is changed, reload the Context;
  5. Set the default Cookie processor;
  6. Set the character set mapping, which is mainly used to obtain the character set code according to Locale;
  7. Start the Web application class loader (WebappLoader.start), and then truly create the WebappClassLoader instance;
  8. Start the safety components;
  9. Publish configuration_ START_ Event event. ContextConfig listens to this event to complete the creation of the Servlet; See method 11;
  10. Start the Context child node (Wrapper); See method 12;
  11. Start the Pipeline maintained by Context;
  12. Create a session Manager contextManager;
  13. If the cluster component is configured, it is created by the cluster component. Otherwise, the standard manager is used. In the cluster environment, the session manager needs to be registered with the cluster component;
  14. Add the WEB resource collection of Context to the ServletContext attribute;
  15. Create instance manager, which is used to create object instances, such as Servlet and Filter;
  16. Add the Jar package scanner to the ServletContext attribute;
  17. Merge the ServletContext initialization parameter and the ApplicationParameter in the Context component (provided that the configuration can be overridden);
  18. Start the ServletContainerInitializer added to the current Context;
  19. Instantiate listeners (time listeners and lifecycle listeners);
  20. Detect the security constraints of uncovered HTTP methods;
  21. Start the session manager;
  22. Instantiate FilterConfig and Filter, and call Filter.init for initialization;
  23. For the Wrapper with loadonstartup > = 0, call wrapper.load(); See method 13;
  24. This method is responsible for instantiating the Servlet and calling Servlet.init for initialization;
  25. Start the background timing processing thread; It can only be started when backgroundprocessordelay > 0: used to monitor the changes of Guard files, etc; When backgroundprocessordelay < = 0, the background task identifying the Context is scheduled by the parent container (Host);
  26. Issue running JMX notification;
  27. Call WebResourceRoot.gc() to release resources;
  28. //Start successfully set STARTED;
protected synchronized void startInternal() throws LifecycleException {

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

    // Send j2ee.state.starting notification
    // Publish the JMX notification that is being started. You can listen to the start of the Web application by adding a notificationlister
    if (this.getObjectName() != null) {
        Notification notification = new Notification("j2ee.state.starting",
                                                     this.getObjectName(), sequenceNumber.getAndIncrement());
        broadcaster.sendNotification(notification);
    }

    setConfigured(false);
    boolean ok = true;

    // Currently this is effectively a NO-OP but needs to be called to
    // ensure the NamingResources follows the correct lifecycle
    if (namingResources != null) {
        namingResources.start();
    }

    // Post work directory
    // Process working directory
    postWorkDirectory();

    // Add missing components as necessary
    if (getResources() == null) {   // (1) Required by Loader
        if (log.isDebugEnabled()) {
            log.debug("Configuring default Resources");
        }
        // Initialize the WebResourceRoot used by the current Context and start it. WebResourceRoot maintains all the functions of Web applications
        // Resource collection (Class file, Jar package and other resource files) is mainly used to load classes and find resource files according to paths
        try {
            /*
                 * WebResourceRoot[StandardRoot]
                 * Represents a collection of all resources that make up a Web application. In the WebResourceRoot, the resources of a Web application can be
                 * The classification is divided into multiple sets. When finding resources, they are processed in the specified order;
                 */
            setResources(new StandardRoot(this));
        } catch (IllegalArgumentException e) {
            log.error(sm.getString("standardContext.resourcesInit"), e);
            ok = false;
        }
    }
    if (ok) {
        resourcesStart();
    }
    // Create a Web application class loader. WebappLoader inherits lifecycle mbeanbase and creates a Web application class loader when it starts
    // (WebappClassLoader).  In addition, this class also provides background process for Context background processing.
    // When the class file of the Web application is detected and the Jar package is changed, reload the Context
    if (getLoader() == null) {
        WebappLoader webappLoader = new WebappLoader();
        webappLoader.setDelegate(getDelegate());
        setLoader(webappLoader);
    }

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

    // Initialize character set mapper
    // Set character set mapping, which is mainly used to obtain character set encoding according to Locale
    getCharsetMapper();

    // Validate required extensions
    boolean dependencyCheck = true;
    try {
        dependencyCheck = ExtensionValidator.validateApplication
            (getResources(), this);
    } catch (IOException ioe) {
        log.error(sm.getString("standardContext.extensionValidationError"), ioe);
        dependencyCheck = false;
    }

    if (!dependencyCheck) {
        // do not make application available if dependency check fails
        ok = false;
    }

    // Reading the "catalina.useNaming" environment variable
    String useNamingProperty = System.getProperty("catalina.useNaming");
    if ((useNamingProperty != null)
        && (useNamingProperty.equals("false"))) {
        useNaming = false;
    }

    if (ok && isUseNaming()) {
        if (getNamingContextListener() == null) {
            NamingContextListener ncl = new NamingContextListener();
            ncl.setName(getNamingContextName());
            ncl.setExceptionOnFailedWrite(getJndiExceptionOnFailedWrite());
            addLifecycleListener(ncl);
            setNamingContextListener(ncl);
        }
    }

    // Standard container startup
    if (log.isDebugEnabled()) {
        log.debug("Processing standard container startup");
    }


    // Binding thread
    ClassLoader oldCCL = bindThread();

    try {
        if (ok) {
            // Start our subordinate components, if any
            Loader loader = getLoader();
            // Start the Web application class loader (WebappLoader.start), and then the WebappClassLoader instance will be created
            if (loader instanceof Lifecycle) {
                ((Lifecycle) loader).start();
            }

            // since the loader just started, the webapp classloader is now
            // created.
            if (loader.getClassLoader() instanceof WebappClassLoaderBase) {
                WebappClassLoaderBase cl = (WebappClassLoaderBase) loader.getClassLoader();
                cl.setClearReferencesRmiTargets(getClearReferencesRmiTargets());
                cl.setClearReferencesStopThreads(getClearReferencesStopThreads());
                cl.setClearReferencesStopTimerThreads(getClearReferencesStopTimerThreads());
                cl.setClearReferencesHttpClientKeepAliveThread(getClearReferencesHttpClientKeepAliveThread());
                cl.setClearReferencesObjectStreamClassCaches(getClearReferencesObjectStreamClassCaches());
                cl.setClearReferencesThreadLocals(getClearReferencesThreadLocals());
            }

            // By calling unbindThread and bindThread in a row, we setup the
            // current Thread CCL to be the webapp classloader
            unbindThread(oldCCL);
            oldCCL = bindThread();

            // Initialize logger again. Other components might have used it
            // too early, so it should be reset.
            logger = null;
            getLogger();
            // Start security components
            Realm realm = getRealmInternal();
            if(null != realm) {
                if (realm instanceof Lifecycle) {
                    ((Lifecycle) realm).start();
                }

                // Place the CredentialHandler into the ServletContext so
                // applications can have access to it. Wrap it in a "safe"
                // handler so application's can't modify it.
                CredentialHandler safeHandler = new CredentialHandler() {
                    @Override
                    public boolean matches(String inputCredentials, String storedCredentials) {
                        return getRealmInternal().getCredentialHandler().matches(inputCredentials, storedCredentials);
                    }

                    @Override
                    public String mutate(String inputCredentials) {
                        return getRealmInternal().getCredentialHandler().mutate(inputCredentials);
                    }
                };
                context.setAttribute(Globals.CREDENTIAL_HANDLER, safeHandler);
            }

            // Notify our interested LifecycleListeners
            // Publish configuration_ START_ Event event. ContextConfig listens to this event to complete the creation of the Servlet
            fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);

            // Start our child containers, if not already started
            for (Container child : findChildren()) {
                // Start Context child node
                if (!child.getState().isAvailable()) {
                    child.start();
                }
            }

            // Start the Valves in our pipeline (including the basic),
            // if any
            // Start Pipeline maintained by Context
            if (pipeline instanceof Lifecycle) {
                ((Lifecycle) pipeline).start();
            }

            // Acquire clustered manager
            // Create session manager
            Manager contextManager = null;
            Manager manager = getManager();
            if (manager == null) {
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString("standardContext.cluster.noManager",
                                           Boolean.valueOf((getCluster() != null)),
                                           Boolean.valueOf(distributable)));
                }
                // If a cluster component is configured, it is created by the cluster component; otherwise, standard manager is used
                // In a cluster environment, you need to register the session manager with the cluster component
                if ((getCluster() != null) && distributable) {
                    try {
                        contextManager = getCluster().createManager(getName());
                    } catch (Exception ex) {
                        log.error("standardContext.clusterFail", ex);
                        ok = false;
                    }
                } else {
                    contextManager = new StandardManager();
                }
            }

            // Configure default manager if none was specified
            if (contextManager != null) {
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString("standardContext.manager",
                                           contextManager.getClass().getName()));
                }
                setManager(contextManager);
            }

            if (manager!=null && (getCluster() != null) && distributable) {
                //let the cluster know that there is a context that is distributable
                //and that it has its own manager
                getCluster().registerManager(manager);
            }
        }

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

        // We put the resources into the servlet context
        if (ok) {
            // Add the WEB resource collection of Context to the ServletContext property
            getServletContext().setAttribute
                (Globals.RESOURCES_ATTR, getResources());

            // Create instance manager is used to create object instances, such as Servlet and Filter
            if (getInstanceManager() == null) {
                javax.naming.Context context = null;
                if (isUseNaming() && getNamingContextListener() != null) {
                    context = getNamingContextListener().getEnvContext();
                }
                Map<String, Map<String, String>> injectionMap = buildInjectionMap(
                    getIgnoreAnnotations() ? new NamingResourcesImpl(): getNamingResources());
                setInstanceManager(new DefaultInstanceManager(context,
                                                              injectionMap, this, this.getClass().getClassLoader()));
            }
            getServletContext().setAttribute(
                InstanceManager.class.getName(), getInstanceManager());
            InstanceManagerBindings.bind(getLoader().getClassLoader(), getInstanceManager());
            // Add the Jar package scanner to the ServletContext property
            // Create context attributes that will be required
            getServletContext().setAttribute(
                JarScanner.class.getName(), getJarScanner());

            // Make the version info available
            getServletContext().setAttribute(Globals.WEBAPP_VERSION, getWebappVersion());
        }

        // Set up the context init params
        // Merge the ServletContext initialization parameter and the ApplicationParameter in the Context component (provided that the configuration can be overridden)
        mergeParameters();

        // Call ServletContainerInitializers
        // Start the ServletContainerInitializer added to the current Context
        for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
             initializers.entrySet()) {
            try {
                entry.getKey().onStartup(entry.getValue(),
                                         getServletContext());
            } catch (ServletException e) {
                log.error(sm.getString("standardContext.sciFail"), e);
                ok = false;
                break;
            }
        }

        // Configure and call application event listeners
        if (ok) {
            // Instantiate listeners (time listeners and lifecycle listeners)
            if (!listenerStart()) {
                log.error(sm.getString("standardContext.listenerFail"));
                ok = false;
            }
        }

        // Check constraints for uncovered HTTP methods
        // Needs to be after SCIs and listeners as they may programmatically
        // change constraints
        // Detect security constraints for uncovered HTTP methods
        if (ok) {
            checkConstraintsForUncoveredMethods(findConstraints());
        }

        try {
            // Start manager
            // Start session manager
            Manager manager = getManager();
            if (manager instanceof Lifecycle) {
                ((Lifecycle) manager).start();
            }
        } catch(Exception e) {
            log.error(sm.getString("standardContext.managerFail"), e);
            ok = false;
        }

        // Configure and call application filters
        if (ok) {
            // Instantiate FilterConfig and Filter, and call Filter.init to initialize
            if (!filterStart()) {
                log.error(sm.getString("standardContext.filterFail"));
                ok = false;
            }
        }

        // Load and initialize all "load on startup" servlets
        if (ok) {
            // For the Wrapper with loadonstartup > = 0, call wrapper.load()
            // This method is responsible for instantiating the Servlet and calling Servlet.init for initialization
            if (!loadOnStartup(findChildren())){
                log.error(sm.getString("standardContext.servletFail"));
                ok = false;
            }
        }

        // Start ContainerBackgroundProcessor thread
        // Start the background timing processing thread
        // It can only be started when backgroundprocessordelay > 0: it is used to monitor the changes of Guard files, etc
        // When backgroundprocessordelay < = 0, the background task identifying the Context is scheduled by the parent container (Host)
        super.threadStart();
    } finally {
        // Unbinding thread
        unbindThread(oldCCL);
    }

    // Set available status depending upon startup success
    if (ok) {
        if (log.isDebugEnabled()) {
            log.debug("Starting completed");
        }
    } else {
        log.error(sm.getString("standardContext.startFailed", getName()));
    }

    startTime=System.currentTimeMillis();

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

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

    // Reinitializing if something went wrong
    if (!ok) {
        setState(LifecycleState.FAILED);
        // Send j2ee.object.failed notification
        if (this.getObjectName() != null) {
            Notification notification = new Notification("j2ee.object.failed",
                                                         this.getObjectName(), sequenceNumber.getAndIncrement());
            broadcaster.sendNotification(notification);
        }
    } else {
        // Start successfully set STARTED
        setState(LifecycleState.STARTING);
    }
}
11.ContextConfig.lifecycleEvent()

Parsing web.xml; Create a series of Web container related objects such as weapper (servlet), filter and servletcontextlister to complete the initialization of the Web container;

The specific process and code comments have been synchronized:

  • When the application is deployed according to web.xml, the configuration priority in the Web application is the highest, followed by the Host level, and finally the container level;
  • The application annotation configuration can perform JNDI resource dependency injection when instantiating relevant interfaces;
  • Based on the resolved Web container, detect the security role name used in the Web application deployment description. When it is found that an undefined role is used, prompt a warning, and add the undefined role to the Context security role list;
  • When the Context needs security authentication, but no specific Authenticator is specified, the default instance is created according to the server configuration;
// ContextConfig.class
public void lifecycleEvent(LifecycleEvent event) {
    // ...
    if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
        configureStart();
    } 
    // ...
}

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())));
    }
    // Initialization of Web container
    // When the application is deployed according to web.xml, the configuration priority in the Web application is the highest, followed by the Host level, and finally the container level
    webConfig();
    // Error when closing jsp startup
    context.addServletContainerInitializer(new JasperInitializer(), null);
    if (!context.getIgnoreAnnotations()) {
        // Application annotation configuration can perform JNDI resource dependency injection when instantiating related interfaces
        applicationAnnotationsConfig();
    }
    if (ok) {
        // Based on the resolved Web container, detect the security role name used in the Web application deployment description
        // When it is found that an undefined role is used, a warning will be prompted, and the undefined role will be added to the Context security role list
        validateSecurityRoles();
    }

    // Configure an authenticator if we need one
    if (ok) {
        // When the Context needs security authentication, but no specific Authenticator is specified,
        // Create a default instance based on the server configuration
        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 (Valve valve : valves) {
                log.debug("  " + valve.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);
    }
}

// When the application is deployed according to web.xml, the configuration priority in the Web application is the highest, followed by the Host level, and finally the container level
protected void webConfig() {
    WebXmlParser webXmlParser = new WebXmlParser(context.getXmlNamespaceAware(),
                                                 context.getXmlValidation(), context.getXmlBlockExternal());

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

    // Parse the default configuration and generate WebXml objects
    WebXml webXml = createWebXml();

    // Parse context level web.xml
    // Parsing web.xml file of Web application
    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.
    // Scan all the jar packages of the Web application. If it contains Web-fragment.xml, parse the file and create a WebXml object;
    Map<String,WebXml> fragments = processJarsForWebFragments(webXml, webXmlParser);

    // Step 2. Order the fragments.
    // Sort the Webxml objects according to the Servlet specification, and list the JAR file names corresponding to the sorting results
    // Set it to the ServletContext property. The property name is orderedLibs, which determines the execution order of Filter and so on
    Set<WebXml> orderedFragments = null;
    orderedFragments =
        WebXml.orderWebFragments(webXml, fragments, sContext);

    // Step 3. Look for ServletContainerInitializer implementations
    // Find the ServletContainerInitializer implementation and create an instance. The search range is divided into two parts
    // Packages under Web application: if orderedLibs is not empty, only the packages contained in this attribute will be searched; otherwise, all packages under WEB-INF/lib will be searched
    // Container package: search all packages (load first, so the configuration takes effect with the lowest priority)
    if (ok) {
        // Initialize typeInitializerMap and initializerClassMap (for subsequent annotation detection)
        // typeInitializerMap represents the ServletContainerInitializer collection corresponding to the class
        // initializerClassMap represents the collection of classes corresponding to each ServletContainerInitializer
        processServletContainerInitializers();
    }

    if  (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) {
        // Steps 4 & 5.
        // Handle the annotations under WEB-INF/classes and the annotations in the JAR package
        processClasses(webXml, orderedFragments);
    }
    if (!webXml.isMetadataComplete()) {
        // Step 6. Merge web-fragment.xml files into the main web.xml
        // file.
        // Merge all web-fragment.xml into the main WebXml
        if (ok) {
            ok = webXml.merge(orderedFragments);
        }

        // Step 7. Apply global defaults
        // Have to merge defaults before JSP conversion since defaults
        // provide JSP servlet definition.
        // The default WebXml is merged into the main WebXml
        webXml.merge(defaults);

        // Step 8. Convert explicitly mentioned jsps to servlets
        // Configure JspServlet
        if (ok) {
            convertJsps(webXml);
        }

        // Step 9. Apply merged web.xml to Context
        // Configure the current StandardContext using the main WebXml
        if (ok) {
            configureContext(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<>(orderedFragments);
        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());
            }
        }
    }
}
12. StandardWrapper#LifecycleBase.start()

We see that the StandardWrapper needs to call init() to initialize first, and then startInternal();

// StandardWrapper#LifecycleBase
public final synchronized void start() throws LifecycleException {
    // ...
    // Initialize uninitialized components
    if (state.equals(LifecycleState.NEW)) {
        // Call first
        init();
    } 

    try {
        setStateInternal(LifecycleState.STARTING_PREP, null, false);
        // Call subclass startup
        startInternal();
        // ...
    } catch (Throwable t) {
        handleSubClassException(t, "lifecycleBase.startFail", toString());
    }
}

Here, initialization is to create a thread pool for a single thread and initialize the MBean;

// StandardWrapper#ContainerBase.initInternal()
@Override
protected void initInternal() throws LifecycleException {
    BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue<>();
    // Create a thread pool for a single thread, with a maximum survival time of 10s and timeout
    startStopExecutor = new ThreadPoolExecutor(
        getStartStopThreadsInternal(),
        getStartStopThreadsInternal(), 10, TimeUnit.SECONDS,
        startStopQueue,
        new StartStopThreadFactory(getName() + "-startStop-"));
    startStopExecutor.allowCoreThreadTimeOut(true);
    super.initInternal();
}

start() here, since the wrapper is the lowest container, there will be no sub containers, and the start is completed;

// StandardWrapper.class
protected synchronized void startInternal() throws LifecycleException {
     // Send j2ee.state.starting notification
     if (this.getObjectName() != null) {
         Notification notification = new Notification("j2ee.state.starting",
                                                      this.getObjectName(),
                                                      sequenceNumber++);
         broadcaster.sendNotification(notification);
     }
     // Start up this component
     // Call the method ContainerBase of the parent container
     super.startInternal();

     setAvailable(0L);

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

// ContainerBase
protected synchronized void startInternal() throws LifecycleException {

    // Start our subordinate components, if any
    logger = null;
    getLogger();
    // Cluster configuration startup
    Cluster cluster = getClusterInternal();
    if (cluster instanceof Lifecycle) {
        ((Lifecycle) cluster).start();
    }
    // Security domain startup
    Realm realm = getRealmInternal();
    if (realm instanceof Lifecycle) {
        ((Lifecycle) realm).start();
    }

    // Start our child containers, if any
    // There are no sub containers here. The bottom container is gone
    Container children[] = findChildren();
    List<Future<Void>> results = new ArrayList<>();
    for (Container child : children) {
        results.add(startStopExecutor.submit(new StartChild(child)));
    }

    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
    // Start pipeline
    if (pipeline instanceof Lifecycle) {
        ((Lifecycle) pipeline).start();
    }
    setState(LifecycleState.STARTING);
    // Start our thread
    threadStart();
}
13. StandardWrapper.load()

Tomcat defines two servlets in web.xml by default: defaultservlet and Jsp Servlet, which will exist in all Web application containers by default;

When load on startup > = 0, call StandardWrapper.load() to load the Servlet; The process is as follows:

  1. Create a Servlet instance. If JNDI resource annotation is added, dependency injection will be performed;
  2. Read MultipartConfig configuration for multipart / form data request processing, including temporary file storage path, maximum bytes of uploaded file, maximum bytes of request and file size threshold;
  3. Read the Servlet security () annotation configuration and add Servlet security;
  4. Call servlet.init() to initialize the Servlet;
// StandardWrapper
public synchronized void load() throws ServletException {
    // Load Server
    instance = loadServlet();

    if (!instanceInitialized) {
        // Load Servlet
        initServlet(instance);
    }

    if (isJspServlet) {
       // ... load JSP configuration
    }
}

// Create Servlet instance (load)
public synchronized Servlet loadServlet() throws ServletException {
    // Default org.apache.catalina.servlets.DefaultServlet
    Servlet servlet;
    try {
        long t1=System.currentTimeMillis();
        InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
        try {
            // Create a Servlet instance. If JNDI resource annotation is added, dependency injection will be performed;
            servlet = (Servlet) instanceManager.newInstance(servletClass);
        } catch (Exception e) {
            // ...
        } 
        // Read MultipartConfig configuration for multipart / form data request processing,
        // Including temporary file storage path, maximum bytes of uploaded file, maximum bytes of request, and file size threshold;
        if (multipartConfigElement == null) {
            MultipartConfig annotation =
                servlet.getClass().getAnnotation(MultipartConfig.class);
            if (annotation != null) {
                multipartConfigElement =
                    new MultipartConfigElement(annotation);
            }
        }
        // Special handling for ContainerServlet instances
        // Note: The InstanceManager checks if the application is permitted
        //       to load ContainerServlets
        if (servlet instanceof ContainerServlet) {
            ((ContainerServlet) servlet).setWrapper(this);
        }
        classLoadTime=(int) (System.currentTimeMillis() -t1);
        if (servlet instanceof SingleThreadModel) {
            if (instancePool == null) {
                instancePool = new Stack<>();
            }
            singleThreadModel = true;
        }
        // Call servlet.init() to initialize the Servlet;
        initServlet(servlet);

        fireContainerEvent("load", this);

        loadTime=System.currentTimeMillis() -t1;
    } finally {
        if (swallowOutput) {
            String log = SystemLogHandler.stopCapture();
            if (log != null && log.length() > 0) {
                if (getServletContext() != null) {
                    getServletContext().log(log);
                } else {
                    out.println(log);
                }
            }
        }
    }
    return servlet;
}

// Call servlet.init() to initialize the Servlet;
private synchronized void initServlet(Servlet servlet)
    throws ServletException {
    // Call the initialization method of this servlet
    try {
        if( Globals.IS_SECURITY_ENABLED) {
            boolean success = false;
            try {
                Object[] args = new Object[] { facade };
                SecurityUtil.doAsPrivilege("init",
                                           servlet,
                                           classType,
                                           args);
                success = true;
            } finally {
                if (!success) {
                    SecurityUtil.remove(servlet);
                }
            }
        } else {
            servlet.init(facade);
        }
        instanceInitialized = true;
    } catch (UnavailableException f) {
        unavailable(f);
        throw f;
    } catch (ServletException f) {
        // If the servlet wanted to be unavailable it would have
        // said so, so do not call unavailable(null).
        throw f;
    } catch (Throwable f) {
        ExceptionUtils.handleThrowable(f);
        getServletContext().log(sm.getString("standardWrapper.initException", getName()), f);
        // If the servlet wanted to be unavailable it would have
        // said so, so do not call unavailable(null).
        throw new ServletException
            (sm.getString("standardWrapper.initException", getName()), f);
    }
}
14. HostConfig.lifecycleEvent()

When HostConfig receives Lifecycle.START_EVENT event, call the start () method; start() internally calls the deployApps() method, which is where the context is actually created;

Briefly describe the implementation objectives of deployApps(): describe file deployment, Web directory deployment and War package deployment through Context;

  • Context description file deployment

    Tomcat supports launching Web applications through an independent Context xml file configuration; The location of the configuration file is specified by the xmlBase property of the Host. If not specified, $Catalina is the default_ Base / conf / < engine name > / < Host name >, that is, the default path is D:\tomcat8.5\conf\Catalina\localhost under Tomcat directory;

    <!--testxml-->
    <Context docBase="test/testMyApp" path="/testMyApp" reloadable="false">
        <WatchedResource>WEB-INF/web.xml</WatchedResource>
    </Context>
    

    In the above configuration, docBase points to the physical path of the web application, and path points to the request path suffix;

  • Web directory deployment

    Publish and deploy web applications in the form of directories; Copy the directories of all resources, jar packages and description files (WEB-INF/web.xml) to the appBase (i.e. D:\tomcat8.5\webapps) directory specified by the Host to complete the deployment;

  • War package deployment

    Tomcat will process the war package, which is similar to the Web directory deployment. All qualified Wars (not in the filter rules of deployIgnore, files with file names not META-INF and WEB-INF, and files with war as extension) under the appBase (i.e. D:\tomcat8.5\webapps) directory specified by the Host are deployed by the thread pool;

public class HostConfig implements LifecycleListener {
    // Event listening and processing logic
     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)) {
            // Look here
            start();
        } else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
            stop();
        }
    }
    // start-up
    public void start() {
        // ... the deployOnStartup property of host is true
        if (host.getDeployOnStartup()) {
            deployApps();
        }
    }
    // Three ways to create context
    protected void deployApps() {
        File appBase = host.getAppBaseFile();
        File configBase = host.getConfigBaseFile();
        String[] filteredAppPaths = filterAppPaths(appBase.list());
        // Deploy XML descriptors from configBase
        // Context description file deployment
        deployDescriptors(configBase, configBase.list());
        // Deploy WARs
        deployWARs(appBase, filteredAppPaths);
        // Deploy expanded folders
        deployDirectories(appBase, filteredAppPaths);
    }
}
15. Connector.startInternal()

This is the protocol processor protocolHandler.start(); See method 16;

// Connector.class
protected final ProtocolHandler protocolHandler;
@Override
protected void startInternal() throws LifecycleException {
    // ...
    setState(LifecycleState.STARTING);
    try {
        protocolHandler.start();
    } catch (Exception e) {
        // ...
    }
}
16. Http11NioProtocol.start()

Let's take a look at the class diagram of the processor;

 <Connector port="8080" protocol="HTTP/1.1" />

protocolHandler is just an interface. According to our configuration, its implementation class is Http11NioProtocol; Http11NioProtocol.start() actually calls the parent class AbstractProtocol.start();

// AbstractProtocol
public void start() throws Exception {
    if (getLog().isInfoEnabled()) {
        getLog().info(sm.getString("abstractProtocolHandler.start", getName()));
    }
    // Listener start
    endpoint.start();
    // Start 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();
}

NioEndpoint is started in AbstractProtocol.start(), where the parent class method AbstractEndpoint.start() is called;

The execution process here is as follows:

  • Apply for 128 cache pools nioChannels. In the Bytebuffer cache, each channel holds a group of buffers (two, except SSL holds four);
  • Construct a thread pool to process the SocketProcessor, read and write the socket, encapsulate it into a request object, and then do business processing;
  • Initialization pollers are two pollers by default. It mainly circularly scans PollerEvent queue for pending requests;
  • Start the Acceptor and use a single thread to process the connection by default;
// NioEndpoint#AbstractEndpoint
public final void start() throws Exception {
    // Bind() has been called during initialization, and bindstate has become BOUND_ON_INIT status
    if (bindState == BindState.UNBOUND) {
        bind();
        bindState = BindState.BOUND_ON_START;
    }
    // Direct execution here
    startInternal();
}
// NioEndpoint
public void startInternal() throws Exception {
    if (!running) {
        running = true;
        paused = false;
        processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                                                 socketProperties.getProcessorCache());
        eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                                             socketProperties.getEventCache());
        // The default number of nioChannel applications is 128. System memory is applied for io reading and writing. The efficiency of using system memory directly is much better and faster than heap memory
        nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                                              socketProperties.getBufferPool());

        // Create worker collection
        // Construct a thread pool to process the SocketProcessor, read and write the socket, encapsulate it into a request object, and then do business processing
        if (getExecutor() == null) {
            createExecutor();
        }
        initializeConnectionLatch();
        // Start poller threads
        // Initializing pollers defaults to two pollers
        // poller mainly circularly scans PollerEvent queue for pending requests
        // If there is PollerEvent to be processed, perform request parsing and encapsulation
        // Start the Executor thread for request read processing
        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();
        }
        //Starting the Acceptor uses a single threaded connection by default
        startAcceptorThreads();
    }
}

summary

After initialization and startup, the Service starts the Connector and container; Here, create a socket according to the configuration file and listen for connection processing. See method 15; Container here, there are two channels for creating Context (loading Web services). The first is that the Host creates Context according to the configuration file, and the second is to create Context through the HostConfig listener (Context description file deployment, Web directory deployment, War package deployment). See Method 14; After the Context is created and started (see method 10), we initialize the Servlet according to the configuration load on startup > = 0 (see method 13). The creation of the Servlet is to instantiate the corresponding implementation class through reflection;

The above is the whole process of startup. It is recommended to read Chapter 3 of Tomcat architecture analysis - Liu Guangrui. In the next article, we will analyze the relationship between Connector and Container - request processing;

Posted by AnAmericanGunner on Tue, 30 Nov 2021 18:10:32 -0800