Detailed explanation of how tomcat calls Servlet initialization from source code analysis

Detailed explanation of how tomcat calls Servlet initialization from source code analysis

introduction

In the previous blog, we successfully ran the tomcat source code locally, so in this blog we will analyze from the source code level how tomcat initializes the servlet container during startup. We usually deploy our services to tomcat, then modify the configuration file and start it to provide services to the outside world, but we don’t know much about some of the processes, such as how to load web.xml. This is an essential process for us to analyze servlet and sringMVC.

Annotation source address: https://github.com/good-jack/tomcat_source/tree/master

1. Code to start tomcat

Normally, whether it is Windows or Linux, we start tomcat through scripts, which is not very friendly for us to analyze the source code, so we need to start it through code. The startup code is as follows:

Tomcat tomcat = new Tomcat();
        tomcat.setPort(8080);
        //New out each layer of container and maintain the relationship between each layer of container tomcat.addWebapp("/","/");
        tomcat.start();
        //Block the listening port tomcat.getServer().await();

The startup code is still very simple. From the code, we can see that this blog mainly analyzes the addWebapp() method and the start() method. Through these two methods, we can find out when the servlet container is initialized.

2. Tomcat framework

Before we analyze the above two methods, let's summarize the basic framework of Tomcat. In fact, from the server.xml configuration file that we are very familiar with, we can know that Tomcat is composed of a series of parent-child containers:

Server ---> Service --> Connector Engine addChild---> context (servlet container). These are the containers we analyzed from the configuration file. When tomcat is started, the containers are started layer by layer.

3. Create a container (addWebapp())

3.1 Method call flow chart

The flowchart above shows several important methods that are gradually analyzed from the source code, which is very helpful for us to analyze the source code.

3.2 Source code analysis

1) Get the configContext listener through reflection

Method path: package org.apache.catalina.startup.Tomcat.addWebapp(Host host, String contextPath, String docBase);

 
    public Context addWebapp(Host host, String contextPath, String docBase) {
        //Get a listener ContextConfig through reflection,
        //The one obtained through reflection must be an implementation class of LifecycleListener. Enter getConfigClass to get the implementation class (org.apache.catalina.startup.ContextConfig)
        LifecycleListener listener = null;
        try {
            Class<?> clazz = Class.forName(getHost().getConfigClass());
            listener = (LifecycleListener) clazz.getConstructor().newInstance();
        } catch (ReflectiveOperationException e) {
            // Wrap in IAE since we can't easily change the method signature to
            // to throw the specific checked exceptions
            throw new IllegalArgumentException(e);
        }
 
        return addWebapp(host, contextPath, docBase, listener);
    }

2) Get a context container (StandardContext)

In the following code, the createContext() method loads the StandardContext container through reflection and sets the listener ContextConfig, ctx.addLifecycleListener(config);

public Context addWebapp(Host host, String contextPath, String docBase,
            LifecycleListener config) {
 
        silence(host, contextPath);
 
        //Get a context container (StandardContext)
        Context ctx = createContext(host, contextPath);
        ctx.setPath(contextPath);
        ctx.setDocBase(docBase);
 
        if (addDefaultWebXmlToWebapp) {
            ctx.addLifecycleListener(getDefaultWebXmlListener());
        }
 
        ctx.setConfigFile(getWebappConfigFile(docBase, contextPath));
        //Add the listener to the context ctx.addLifecycleListener(config);
 
        if (addDefaultWebXmlToWebapp && (config instanceof ContextConfig)) {
            // prevent it from looking ( if it finds one - it'll have dup error )
            ((ContextConfig) config).setDefaultWebXml(noDefaultWebXmlPath());
        }
 
        if (host == null) {
            //getHost will create containers layer by layer and maintain the parent-child relationship of containers getHost().addChild(ctx);
        } else {
            host.addChild(ctx);
        }
 
        return ctx;
    }

3) Maintaining containers at all levels

The getHost() method obtains containers at each layer and maintains the parent container relationship, including the server container and the Engine container. And the StandardContext container is maintained in the children map by calling the addChild() method in containerBase through getHost().addChild(ctx);.

  public Host getHost() {
        //Create new containers for each layerEngine engine = getEngine();
        if (engine.findChildren().length > 0) {
            return (Host) engine.findChildren()[0];
        }
 
        Host host = new StandardHost();
        host.setName(hostname);
        //Maintain the parent-child container in tomcat getEngine().addChild(host);
        return host;
    }

getEngine().addChild(host); method selects to call the addChild method in the parent class containerBase

  @Override
    public void addChild(Container child) {
        if (Globals.IS_SECURITY_ENABLED) {
            PrivilegedAction<Void> dp =
                new PrivilegedAddChild(child);
            AccessController.doPrivileged(dp);
        } else {
            //The child parameter here is the context container addChildInternal(child);
        }
    }

The core code of addChildInternal() method

 private void addChildInternal(Container child) {
 
        if( log.isDebugEnabled() )
            log.debug("Add child " + child + " " + this);
        synchronized(children) {
            if (children.get(child.getName()) != null)
                throw new IllegalArgumentException("addChild: Child name '" +
                                                   child.getName() +
                                                   "' is not unique");
            child.setParent(this); // May throw IAE
            children.put(child.getName(), child);
    }

4. Start the container (tomcat.start())

4.1 Method call flow chart

4.2 Source Code Analysis

Note: StandardServer, StandardService, StandardEngine and other containers all inherit LifecycleBase

So here is the classic application of the template pattern

1) Start containers layer by layer

The server at this time corresponds to the StandardServer we created earlier

  public void start() throws LifecycleException {
        //Prevent the server container from being created getServer();
        //Get the connector container and set it to the service container getConnector();
        //The implementation of start here is implemented in the LifecycleBase class //The LifecycleBase method is a template method, which is very critical in the Tomcat startup process server.start();
    }

2) Enter the start method

Enter the start method in LifecycelBase, where the core method is startInternal.

From the above we know that we are now calling the startInternal() method of the StandardServer container, so we choose StandardServer here

Method path: org.apache.catalina.core.StandardServer.startInternal()

protected void startInternal() throws LifecycleException {
 
        fireLifecycleEvent(CONFIGURE_START_EVENT, null);
        setState(LifecycleState.STARTING);
 
        globalNamingResources.start();
 
        // Start our defined Services
        synchronized (servicesLock) {
            //Start the service container. Multiple service containers can be configured in a tomcat. Each service container corresponds to one of our service applications for (Service service : services) {
                //Corresponding to StandardService.startInternal()
                service.start();
            }
        }
    }

From the above code, we can see that when starting the server container, it is necessary to start the subcontainer service container. From here on, the container is detonated inward layer by layer, so the next step is to start calling the star method of each layer of container in turn. I won’t go into details here.

2) The core code of the startInternal() method in ContainerBase, from which the StandardContext container is started

 // Start our child containers, if any
        //Added in the addChild method in the addWwbapp process, so we need to find it here //What we find here is the context container Container children[] = findChildren();
        List<Future<Void>> results = new ArrayList<>();
        for (Container child : children) {
            //Start the thread pool asynchronously to start the context container and enter new StartChild
            results.add(startStopExecutor.submit(new StartChild(child)));
        }

The new StartChild(child) method starts the StandardContext container

    private static class StartChild implements Callable<Void> {
 
        private Container child;
 
        public StartChild(Container child) {
            this.child = child;
        }
 
        @Override
        public Void call() throws LifecycleException {
            //Start starting the context, actually calling StandardContext.startInternal()
            child.start();
            return null;
        }
    }

The core code in the StandardContext.startInternal() method:

   protected void fireLifecycleEvent(String type, Object data) {
        LifecycleEvent event = new LifecycleEvent(this, type, data);
        //lifecycleListeners In the first step of the addwebapp method, set the listening contextConfig object for (LifecycleListener listener : lifecycleListeners) {
            //The lifecycleEvent() method of contextConfig is called here listener.lifecycleEvent(event);
        }
    }

Enter the lifecycleEvent() method in contextConfig

public void lifecycleEvent(LifecycleEvent event) {
 
        // Identify the context we are associated with
        try {
            context = (Context) event.getLifecycle();
        } catch (ClassCastException e) {
            log.error(sm.getString("contextConfig.cce", event.getLifecycle()), e);
            return;
        }
 
        // Process the event that has occurred
        if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
            //Complete the content parsing of web.xml configureStart();
        } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
            beforeStart();
        } else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
            // Restore docBase for management tools
            if (originalDocBase != null) {
                context.setDocBase(originalDocBase);
            }
        } else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) {
            configureStop();
        } else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
            init();
        } else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
            destroy();
        }
 
    }

In the above method, the web.xml is loaded and parsed, and the servlet configured in the xml is loaded and encapsulated into a wrapper object.

3) Start the servlet container, loadOnStartup(findChildren()) method in StandardContext.startInternal()

public boolean loadOnStartup(Container children[]) {
 
        // Collect "load on startup" servlets that need to be initialized
        TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
        for (Container child : children) {
            //The Wrapper here is the servlet we encapsulated earlier
            Wrapper wrapper = (Wrapper) child;
            int loadOnStartup = wrapper.getLoadOnStartup();
            if (loadOnStartup < 0) {
                continue;
            }
            Integer key = Integer.valueOf(loadOnStartup);
            ArrayList<Wrapper> list = map.get(key);
            if (list == null) {
                list = new ArrayList<>();
                map.put(key, list);
            }
            list.add(wrapper);
        }
 
        // Load the collected "load on startup" servlets
        for (ArrayList<Wrapper> list : map.values()) {
            for (Wrapper wrapper : list) {
                try {
                    //The load method will eventually call the servlet's init method wrapper.load();
                } catch (ServletException e) {
                    getLogger().error(sm.getString("standardContext.loadOnStartup.loadException",
                          getName(), wrapper.getName()), StandardWrapper.getRootCause(e));
                    // NOTE: load errors (including a servlet that throws
                    // UnavailableException from the init() method) are NOT
                    // fatal to application startup
                    // unless failCtxIfServletStartFails="true" is specified
                    if(getComputedFailCtxIfServletStartFails()) {
                        return false;
                    }
                }
            }
        }
        return true;
 
    }

The load method will eventually call the servlet's init method.

V. Conclusion

The above content is the process of how the entire tomcat calls the servlet initialization method. This is my understanding of the entire process. If there are any errors, please correct me. I have annotated the important parts of the source code, so if you need it, you can download my annotated source code. The annotated source code address is:

https://github.com/good-jack/tomcat_source/tree/master

This concludes this article on how to analyze tomcat from the source code to call the Servlet initialization. For more information about tomcat calling Servlet initialization, please search for previous articles on 123WORDPRESS.COM or continue to browse the following related articles. I hope you will support 123WORDPRESS.COM in the future!

You may also be interested in:
  • IDEA2021 tomcat10 servlet newer version pitfalls
  • In-depth understanding of the creation and implementation of servlets in tomcat
  • Detailed explanation of how Tomcat implements asynchronous Servlet
  • servlet and tomcat_PowerNode Java Academy
  • How to implement asynchronous Servlet in Tomcat
  • Introduction and use of Servlet object pool in tomcat
  • A detailed introduction to the working mechanism of Servlet in tomcat

<<:  How to reduce memory usage and CPU usage of web pages

>>:  Solution to the blank line in front of the utf8 encoded web page when it contains files

Recommend

Vue basics MVVM, template syntax and data binding

Table of contents 1. Vue Overview Vue official we...

How to convert Chinese into UTF-8 in HTML

In HTML, the Chinese phrase “學好好學” can be express...

Vue implements three-level navigation display and hiding

This article example shares the specific code of ...

Two ways to use react in React html

Basic Use <!DOCTYPE html> <html lang=&qu...

Detailed explanation of the initialization mechanism in bash

Bash Initialization Files Interactive login shell...

About Tomcat combined with Atomikos to implement JTA

Recently, the project switched the environment an...

The current better way to make select list all options when selected/focused

During development, I encountered such a requireme...

Three ways to jump to a page by clicking a button tag in HTML

Method 1: Using the onclick event <input type=...

Super detailed MySQL usage specification sharing

Recently, there have been many database-related o...

Vue.js performance optimization N tips (worth collecting)

Table of contents Functionalcomponents Childcompo...

Solve the problem after adding --subnet to Docker network Create

After adding –subnet to Docker network Create, us...

Explanation of several ways to run Tomcat under Linux

Starting and shutting down Tomcat under Linux In ...

Native JavaScript to achieve slide effects

When we create a page, especially a homepage, we ...