Tomcat is the Servlet container that Java WEB development comes into contact with most, but it is not only a Servlet container, but also a WEB application server. Under the micro-service architecture, in order to reduce deployment cost and resource overhead, Tomcat pursues lightweight and stability. Tomcat is a lightweight application server and is naturally accepted by many developers.

Tomcat has a lot to learn for all of us Java WEB developers, and it’s safe to say that Java WEB development is no secret to you once you understand how Tomcat is designed. This article is mainly about the internal architecture of Tomcat to give you a general understanding of Tomcat.

As I said earlier, Tomcat is essentially a WEB server and a Servlet container, so it must deal with network connection and Servlet management. Therefore, Tomcat has designed two core components to achieve these two functions, namely connector and container. Connectors are used to handle external network connections, containers are used to handle internal servlets, and I use a diagram to show their relationship:

Tomcat represents a Server Server. A Server Server can contain multiple services. The default Tomcat Service is Catalina, and a Service Service can contain multiple connectors. Tomcat supports a variety of network protocols, including HTTP/1.1, HTTP/2, AJP, and so on. A Service also includes a container, which is surrounded by an Engine Engine, responsible for processing the requests and responses of the connector. The connector communicates with the container through ServletRequest and ServletResponse objects.

You can also see the internal structure of Tomcat as a whole from the configuration structure of server.xml:

<Server port="8005" shutdown="SHUTDOWN">  <Service name="Catalina">    <Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443" URIEncoding="UTF-8"/>< Connector port = "8009" protocol = "AJP / 1.3" redirectPort = "8443" / >    <Engine defaultHost="localhost" name="Catalina">      <Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true">        <Context docBase="handler-api" path="/handler" reloadable="true" source="org.eclipse.jst.jee.server:handler-api"/>      </Host>    </Engine>  </Service></Server>Copy the code

Connector

The connector encapsulates various network protocols, shields the network connection and IO processing details from the outside, and passes the processed Request object to the container for processing. Tomcat encapsulates the details of processing requests to the ProtocolHandler. ProtocolHandler is an interface type that implements a ProtocolHandler to handle various protocols, such as Http11AprProtocol:

ProtocolHandler uses a component-mode design to process network connections, encapsulate byte streams into a Request object, and convert the Request into servlets to process ServletRequest objects. The ProtocolHandler consists of three components: Endpoint, Processor, and Adapter.

The Endpoint is created in the constructor of the ProtocolHandler implementation class as follows:

public Http11AprProtocol() {  super(new AprEndpoint());}Copy the code

The Endpoint component handles the underlying Socket network connection. AprEndpoint has an internal class called SocketProcessor that converts incoming Socket requests into A Request object for AprEndpoint. SocketProcessor implements the Runnable interface, which is handled by a dedicated thread pool. I’ll look at the design principles of the Endpoint component separately from the source code.

org.apache.tomcat.util.net.AprEndpoint.SocketProcessor#doRun:

// Process the request from this socketSocketState state = getHandler().process(socketWrapper, event);Copy the code

The process method creates a Processor object, and its process method wraps the Socket stream into a Request object. When creating a Processor, Adapter is added to the Processor:

Org. Apache. Coyote. Http11. AbstractHttp11Protocol # createProcessor:

protected Processor createProcessor() {  Http11Processor processor = new Http11Processor();// Set Adapter component  processor.setAdapter(getAdapter());  return processor;}Copy the code

The Adapter component is created when the connector is initialized:

Org. Apache. Catalina. Connector. Connector# initInternal:

// Initialize adapteradapter = new CoyoteAdapter(this);protocolHandler.setAdapter(adapter);Copy the code

So far, Tomcat has only one Adapter implementation class, CoyoteAdapter. The main purpose of Adapter is to adapt a Request object to a Request object that can be recognized by a container, such as a Servlet container, which can only recognize ServletRequest objects. In this case, Adapter Adapter classes are required to perform a layer of adaptation.

For each component of the connector above, I use a diagram to illustrate their direct relationship:

Container

There are altogether four containers designed in Tomcat, which are Engine, Host, Context and Wrapper. Their relationship is shown in the following figure:

•Engine: represents a virtual host Engine, a Tomcat Server only one Engine, connector all requests to the Engine processing, and the Engine will be given to the corresponding virtual host to process requests; •Host: indicates a virtual Host. A container can have multiple virtual hosts, and each Host has a corresponding domain name. In Tomcat, one Webapps represents one virtual Host. • Context: indicates an application container. A virtual host can have multiple applications. Each directory in webapps represents a Context, and each application can be configured with multiple servlets.

As shown in the figure above, the relationship between Container components is from large to small, that is, the parent-child relationship. The relationship between Container components forms a tree structure. Their implementation classes implement the Container interface, which controls the relationship between Container components in the following ways:

ublic interface Container extends Lifecycle {  Container getParent();  void setParent(Container container);  void addChild(Container child);  Container findChild(String name);  Container[] findChildren();  void removeChild(Container child);}Copy the code

Lifecycle interface Lifecycle interface Lifecycle interface Lifecycle interface Lifecycle interface Lifecycle interface

public interface Lifecycle {     public static final String INIT_EVENT = "init";    public static final String START_EVENT = "start";     public static final String BEFORE_START_EVENT = "before_start";     public static final String AFTER_START_EVENT = "after_start";     public static final String STOP_EVENT = "stop";     public static final String BEFORE_STOP_EVENT = "before_stop";     public static final String AFTER_STOP_EVENT = "after_stop";     public static final String DESTROY_EVENT = "destroy";    public void addLifecycleListener(LifecycleListener listener);     public LifecycleListener[] findLifecycleListeners();     public void removeLifecycleListener(LifecycleListener listener);     public void start() throws LifecycleException;     public void stop() throws LifecycleException;   }  Copy the code

There are many components in Tomcat that implement the Lifecycle interface, and Tomcat manages the Lifecycle of these components through an event mechanism.

The container design idea of Tomcat is actually based on the combined design mode. The biggest advantage of the combined design mode is that nodes can be added freely, which makes Tomcat container components very easy to expand and conforms to the open and closed principle of the design mode.

Now that we know how Tomcat’s container components are assembled, let’s ask a question:

When a request comes in, how does Tomcat recognize the request and hand it off to a particular Servlet for processing?

As can be seen from the combination of containers, they must be called in the following order:

Engine -> Host -> Context -> Wrapper -> ServletCopy the code

So how does Tomcat position servlets? The answer is to use Mapper components to do the job of positioning.

Mapper’s main core function is to store the mapping of access paths between container components. How does Mapper do this?

Let’s start with the source code:

Org. Apache. Catalina. Core. StandardService:

protected final Mapper mapper = new Mapper();protected final MapperListener mapperListener = new MapperListener(this);Copy the code

The Service implementation class initializes the Mapper component and its listener class. In Tomcat, the Standard implementation component is prefixed with the MapperListener class.

org.apache.catalina.core.StandardServerorg.apache.catalina.core.StandardServiceorg.apache.catalina.core.StandardEngineorg.apache.catalina.core.StandardHostorg.apache.catalina.core.StandardContextorg.apache.catalina.core.StandardWrapperCopy the code

When the Service is started, the mapperListener.start () method is called, and the startInternal method of MapperListener is executed:

Org. Apache. Catalina. Mapper. MapperListener# startInternal:

Container[] conHosts = engine.findChildren();for (Container conHost : conHosts) {  Host host = (Host) conHost;if (! LifecycleState.NEW.equals(host.getState())) {    // Registering the host will register the context and wrappers    registerHost(host);  }}Copy the code

This method registers the new virtual host, followed by the registerHost() method, which registers the context, and so on, to register the Mapper with the path directly accessed by the container component.

Flowchart for locating servlets:

The recent hot,

Actual combat | how to customize SpringBoot Starter?

The bloodbath caused by for Update

Java Concurrent AQS source code Analysis (2)

Java Concurrent AQS source code Analysis (1)

Parsing thread pool operation principle from source point of view

Some Settings you need to know about thread pools

Do you understand the parameters for creating a thread pool?

Long press to subscribe

Whether or not we’ve had problems

As long as you click on the view or forward, we are brothers