Deadknock Tomcat series (5) — Containers

review

In the Tomcat series (1) — Overall architecture, we briefly introduced the concept of containers and explained that the parent interface of all child containers in a Container is Container. In the Tomcat series (2) — EndPoint source code parsing, we learned that the connector parses the requested data into ServletRequest objects to the container that Tomcat requires. So how does the container assign this object to the request exactly?

Overall container design

Container is the parent interface of a Container. All child containers need to implement this interface. Let’s first look at the design of the Container interface.

public interface Container extends Lifecycle {
    public void setName(String name);
    public Container getParent();
    public void setParent(Container container);
    public void addChild(Container child);
    public void removeChild(Container child);
    public Container findChild(String name);
}

Copy the code

How does Tomcat manage these containers? We can learn from the interface design by setting the parent-child relationship, forming a tree structure (one father, many children), chain structure (one father, one child) to manage. When we think of a tree structure, we should immediately think of a combination pattern in design patterns, whereas a chain structure should think of a chain of responsibility pattern in design patterns. Either way we know that the relationship is hierarchical. It’s graphically shown as follows.

Given the parent-child structure, how does the connector deliver the transformed ServletRequest to the container? We can look at the service method in the CoyoteAdapter. Because the last loop in the connector is to pass the parsed Request to the Adapter and parse it into a ServletRequest object using the Adapter design pattern. In the service method we see this sentence.

connector.getService().getContainer().getPipeline().getFirst().invoke(
                        request, response);

Copy the code

The getContainer method returns an Engine object

public Engine getContainer();

Copy the code

Here we see Pipeline, Pipeline should be familiar with the concept of Pipeline, so what is in the Pipeline? Let’s look at the way it’s defined

public interface Pipeline extends Contained {
  public void addValve(Valve valve);
  public Valve getBasic();
  public void setBasic(Valve valve);
  public Valve getFirst();
}

Copy the code

A Pipeline contains valves. How are valves organized? We can also look at its code definition

public interface Valve {
  public Valve getNext();
  public void setNext(Valve valve);
  public void invoke(Request request, Response response)
}

Copy the code

You can see that each Valve is a processing point, and its Invoke is the corresponding processing logic. You can see the setNext method, so you can probably guess that Valve is organized by a linked list. The Valve is then loaded into the Pipeline. Therefore, each container has a Pipeline, which contains a number of system defined or custom interceptor nodes to do some corresponding processing. So as soon as the first Valve object is obtained in the container’s Pipeline Pipeline, the next series of chains will be executed.

But how do pipelines fire from container to container? Engine (Engine); Engine (Engine); Host (Engine); Engine (Engine); We can see that there is one more method in each Pipeline. SetBasic sets the end node of the Valve chain, which is responsible for calling the first Valve node of the underlying container Pipeline. That’s what it looks like graphically.

Engine container

The Engine container is relatively simple and just defines some basic associations. Its implementation class is StandardEngine.

    @Override
    public void addChild(Container child) {
        if(! (child instanceof Host)) throw new IllegalArgumentException (sm.getString("standardEngine.notHost"));
        super.addChild(child);
    }
    @Override
    public void setParent(Container container) {
        throw new IllegalArgumentException
            (sm.getString("standardEngine.notParent"));

    }

Copy the code

Note that the Engine container does not have a parent. An error is reported if it is added. Adding a child container just adds a Host container.

The Host container

The Host container is a child of Engine. A Host in Engine represents a virtual Host that runs multiple applications, installs and expands the application, and identifies the application so that it can be distinguished. Its child container is usually the Context container. We can look at the configuration file and see what the Host file does.

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

Copy the code

So what exactly does the Host container do when it starts up? If you look at the startInternal method, it doesn’t tell you anything, except that it starts the corresponding Valve, because the concept of the lifecycle is introduced in the design of Tomcat, that is, each module has its own lifecycle. The life cycle of a module is defined as NEW, INITIALIZING, INITIALIZED, SSTARTING_PREP, STARTING, and STARTED. Changes in the state of each module will trigger a series of actions. Is the execution of these actions written directly in startInternal? This violates the open close principle, so how to solve the problem? The open closed principle says that in order to extend the functionality of the system, you cannot modify existing classes in the system, but you can define new classes.

Thus, each module state change corresponds to the occurrence of an event, and the event has corresponding listeners. Specific logic is implemented in listeners, and listeners can be added and removed easily. This is the classic observer model.

Therefore, the Host container needs to scan all Web applications under the Webapps directory to create the corresponding Context container when it is started. The Host listener is HostConfig, which implements the LifecycleListener interface

public interface LifecycleListener {

    public void lifecycleEvent(LifecycleEvent event);
}

Copy the code

There is only one method defined in the interface, which is the processing logic that listens to the corresponding event. You can see that the listener trigger is invoked in the setState method.

protected void fireLifecycleEvent(String type, Object data) {
    LifecycleEvent event = new LifecycleEvent(this, type, data);
    for(LifecycleListener listener : lifecycleListeners) { listener.lifecycleEvent(event); }}Copy the code

So the specific processing logic for each component in the container is implemented in the listener.

The Context container

A Context corresponds to a Web application

Context represents the Context of the Servlet, which has the basic environment for the Servlet to run. The most important function of a Context is to manage its Servlet instances, which appear in a Wrapper inside the Context. Context preparation the runtime environment is prepared by the lifecycleEvent method in ContextConfig.

@Override
public void lifecycleEvent(LifecycleEvent event) {

    // Identify the context we are associated with
    try {
        context = (Context) event.getLifecycle();
    } catch (ClassCastException e) {
        log.error(sm.getString("contextConfig.cce", event.getLifecycle()), e);
        return;
    }

    // Process the event that has occurred
    if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
        configureStart();
    } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
        beforeStart();
    } else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
        // Restore docBase for management tools
        if (originalDocBase != null) {
            context.setDocBase(originalDocBase);
        }
    } else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) {
        configureStop();
    } else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
        init();
    } else if(event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) { destroy(); }}Copy the code

Wrapper container

The Wrapper container represents a Servlet, including Servlet loading, initialization, execution, and resource recycling. The Wrapper is the lowest level container and has no child containers.

The Wrapper implementation class is StandardWrapper, whose main task is to load and instantiate the Servlet class. But the StandardWrapper class does not call the Servlet’s service method. Instead, the StandardWrapperValue class obtains the corresponding servlet by calling StandardWrpper’s ALLOCATE method, and then calls the corresponding servlet’s service method after filtering it through the interceptor

conclusion

There are many design ideas worth studying in the Tomcat container, such as the template design pattern that extracts the invariant and then changes the subclass to implement, the composite design pattern that maintains a bunch of parent-child relationships, the observer design pattern that events occur along with the listener’s corresponding actions, and so on.

When learning a framework, sometimes it’s not necessary to dig into the lines of code inside, but to learn its ideas. Know how it works and then look for problems or extend the framework accordingly. At this time to further study the code inside will be twice the result with half the effort.

Refer to the article

  • www.cnblogs.com/ChenD/p/Tom…
  • www.ibm.com/developerwo…
  • Take apart Tomcat in depth

The articles

How to debug Tomcat source code breakpoint

Tomcat Series 1 — Overall architecture

Tomcat series 2 – EndPoint source code parsing

Tomcat series (3) — How does Tomcat start and stop with one click

Deadknock Tomcat series (4) — Class loaders in Tomcat

A weird trip to find StackOverflowError problems

A simple RPC framework for hand – to – hand

A Simple RPC Framework (2) – Project transformation