一、tomcat运行原理的简单分析

tomcat运行时可以简单分成两个大的模块,

(1) 负责处理socket连接并转换成request对象的部分,连接器Coyote

(2) 处理用户的请求的容器Catalina

下面简单介绍下这两个部分

1.1 Coyote

Coyote是tomcat的连接器框架的名称,是tomcat服务器提供的供客户端访问的外部接口。客户端通过Coytote与服务器建立连接,发送请求并接收响应。

Coyote封装了底层的网络通信(Scoket),为Catalina容器提供了统一的接口。Coyote将Scoket输入转换封装为Request对象交给Catalina处理,容器处理完后通过Coyote提供的Response对象将结果写入输出流中。

Coyote由如下几个部分组成。

EndPoint: Coyote通信端点,对scoket链接进行处理。

Processor: 把scoket连接中的信息转成Request对象(并不是Servlet规范中的请求对象)

Adapter: 把Request对象转换成Servlet规范中的ServletRequest

1.2 容器 catalina

Catalina是Servlet容器的实现,由Server,Service,Container组成了一个分层结构

Tomcat设计了四种容器,从大到小是父子关系,Engine,Host,Context,Wrapper。

Engine: 一个Service中只能有一个Engine

Host: 虚拟主机,一个Engine中可配置多个Host,通过配置不同的host可以在同一tomcat上部署不同的站点

Context: 表示一个应用,一个host中可以配置多个Context

Wrapper:表示一个Servlet,一个Context中可以有多个Servlet

Tomcat这些组成部分之间的关系都可以在server.xml中找到对应的标签。

二、用编码的方式启动tomcat

这一节用java代码来启动tomcat

import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat;

import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Collections;
import java.util.Set;

public class TomcatTest {
    public static void main(String[] args) throws IOException, LifecycleException {
        //创建Tomcat对象
        Tomcat tomcat = new Tomcat();
        //设置基础目录的名称,这个名称并不是磁盘上真正存在的目录
        tomcat.setBaseDir("tomcat");
        //一个Context表示一个应用,给tomcat中设置一个应用的目录就是添加一个项目,这个目录需要真实存在
        File docBase = Files.createTempDirectory("boot.").toFile();
        // 第一个参数表示应用的访问路径,给空表示用根路径访问项目
        Context context = tomcat.addContext("", docBase.getAbsolutePath());
        // 利用Servlet规范中的ServletContainerInitializer接口给Context中添加Servlet,
        // Servlet容器启动时会执行被添加的ServletContainerInitializer接口中的onStartup方法
        context.addServletContainerInitializer(new ServletContainerInitializer() {
            @Override
            public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
                ServletRegistration.Dynamic dynamic = ctx.addServlet("hello", new HttpServlet() {
                    @Override
                    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
                        resp.getWriter().println("<h1>hello tomcat</h1>");
                    }
                });
                dynamic.addMapping("/hello");
            }
        }, Collections.emptySet());

        //创建连接器
        Connector connector = new Connector("HTTP/1.1");
        connector.setPort(8080);
        //把连接器设置到tomcat上
        tomcat.setConnector(connector);
        //启动tomcat
        tomcat.start();
    }
}

这样就实现了以编码的方式启动一个Tomcat服务,并注册了一个servlet到应用中。

在springboot工程中,spring容器AnnotationConfigServletWebServerApplicationContext 的onRefresh方法中

有类似上边代码的逻辑,用编程的方式启动了一个tomcat。

关于这一点可以看下我的另一篇文章springboot的启动原理和流程