为了方便开发和部署,Spring Boot 在内部启动了一个嵌入式的 Web 容器。我们知道 Tomcat 和 Jetty 是组件化的设计,要启动 Tomcat 或者 Jetty 其实就是启动这些组件。在 Tomcat 独立部署的模式下,我们通过 startup 脚本来启动 Tomcat,Tomcat 中的 Bootstrap 和 Catalina 会负责初始化类加载器,并解析<font style="color:rgb(53, 53, 53);">server.xml</font>和启动这些组件。

在内嵌式的模式下,Bootstrap 和 Catalina 的工作就由 Spring Boot 来做了,Spring Boot 调用了 Tomcat 和 Jetty 的 API 来启动这些组件。那 Spring Boot 具体是怎么做的呢?而作为程序员,我们如何向 SpringBoot 中的 Tomcat 注册 Servlet 或者 Filter 呢?我们又如何定制内嵌式的 Tomcat?今天我们就来聊聊这些话题。

Spring Boot 中 Web 容器相关的接口

既然要支持多种 Web 容器,Spring Boot 对内嵌式 Web 容器进行了抽象,定义了WebServer接口:

public interface WebServer {

    void start() throws WebServerException;

    void stop() throws WebServerException;

    int getPort();

}

各种 Web 容器比如 Tomcat 和 Jetty 需要去实现这个接口。

Spring Boot 还定义了一个工厂ServletWebServerFactory来创建 Web 容器,返回的对象就是上面提到的 WebServer。

public interface ServletWebServerFactory {

    WebServer getWebServer(ServletContextInitializer... initializers);

}

可以看到 getWebServer 有个参数,类型是ServletContextInitializer。它表示 ServletContext 的初始化器,用于 ServletContext 中的一些配置:

public interface ServletContextInitializer {

    void onStartup(ServletContext servletContext) throws ServletException;

}

这里请注意,上面提到的 getWebServer 方法会调用 ServletContextInitializer 的 onStartup 方法,也就是说如果你想在 Servlet 容器启动时做一些事情,比如注册你自己的 Servlet,可以实现一个 ServletContextInitializer,在 Web 容器启动时,Spring Boot 会把所有实现了 ServletContextInitializer 接口的类收集起来,统一调它们的 onStartup 方法。

为了支持对内嵌式 Web 容器的定制化,Spring Boot 还定义了WebServerFactoryCustomizerBeanPostProcessor接口,它是一个 BeanPostProcessor,它在 postProcessBeforeInitialization 过程中去寻找 Spring 容器中 WebServerFactoryCustomizer

类型的 Bean,并依次调用 WebServerFactoryCustomizer

接口的 customize 方法做一些定制化。

public interface WebServerFactoryCustomizer<T extends WebServerFactory> {

    void customize(T factory);

}

内嵌式 Web 容器的创建和启动

铺垫了这些接口,我们再来看看 Spring Boot 是如何实例化和启动一个 Web 容器的。我们知道,Spring 的核心是一个 ApplicationContext,它的抽象实现类 AbstractApplicationContext

实现了著名的refresh方法,它用来新建或者刷新一个 ApplicationContext,在 refresh 方法中会调用 onRefresh 方法,AbstractApplicationContext 的子类可以重写这个方法 onRefresh 方法,来实现特定 Context 的刷新逻辑,因此 ServletWebServerApplicationContext 就是通过重写 onRefresh 方法来创建内嵌式的 Web 容器,具体创建过程是这样的:

@Override

protected void onRefresh() {

    super.onRefresh();

    try {

        // 重写 onRefresh 方法,调用 createWebServer 创建和启动 Tomcat

        createWebServer();

    }

    catch (Throwable ex) {

    }

}



//createWebServer 的具体实现

private void createWebServer() {

    // 这里 WebServer 是 Spring Boot 抽象出来的接口,具体实现类就是不同的 Web 容器

    WebServer webServer = this.webServer;

    ServletContext servletContext = this.getServletContext();



    // 如果 Web 容器还没创建

    if (webServer == null && servletContext == null) {

        // 通过 Web 容器工厂来创建

        ServletWebServerFactory factory = this.getWebServerFactory();

        // 注意传入了一个 "SelfInitializer"

        this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});



    } else if (servletContext != null) {

        try {

            this.getSelfInitializer().onStartup(servletContext);

        } catch (ServletException var4) {

            ...

        }

    }



    this.initPropertySources();

}

再来看看 getWebSever 具体做了什么,以 Tomcat 为例,主要调用 Tomcat 的 API 去创建各种组件:

public WebServer getWebServer(ServletContextInitializer... initializers) {

    //1. 实例化一个 Tomcat,可以理解为 Server 组件。

    Tomcat tomcat = new Tomcat();



    //2. 创建一个临时目录

    File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");

    tomcat.setBaseDir(baseDir.getAbsolutePath());



    //3. 初始化各种组件

    Connector connector = new Connector(this.protocol);

    tomcat.getService().addConnector(connector);

    this.customizeConnector(connector);

    tomcat.setConnector(connector);

    tomcat.getHost().setAutoDeploy(false);

    this.configureEngine(tomcat.getEngine());



    //4. 创建定制版的 "Context" 组件。

    this.prepareContext(tomcat.getHost(), initializers);

    return this.getTomcatWebServer(tomcat);

}

你可能好奇 prepareContext 方法是做什么的呢?这里的 Context 是指Tomcat 中的 Context 组件,为了方便控制 Context 组件的行为,Spring Boot 定义了自己的 TomcatEmbeddedContext,它扩展了 Tomcat 的 StandardContext:

class TomcatEmbeddedContext extends StandardContext {}

注册 Servlet 的三种方式

1. Servlet 注解

在 Spring Boot 启动类上加上 @ServletComponentScan 注解后,使用 @WebServlet、@WebFilter、@WebListener 标记的 Servlet、Filter、Listener 就可以自动注册到 Servlet 容器中,无需其他代码,我们通过下面的代码示例来理解一下。

@SpringBootApplication

@ServletComponentScan

public class xxxApplication

{}
@WebServlet("/hello")

public class HelloServlet extends HttpServlet {}

在 Web 应用的入口类上加上 @ServletComponentScan, 并且在 Servlet 类上加上 @WebServlet,这样 SpringBoot 会负责将 Servlet 注册到内嵌的 Tomcat 中。

2. ServletRegistrationBean

同时 Spring Boot 也提供了 ServletRegistrationBean、FilterRegistrationBean 和 ServletListenerRegistrationBean 这三个类分别用来注册 Servlet、Filter、Listener。假如要注册一个 Servlet,可以这样做:

@Bean

public ServletRegistrationBean servletRegistrationBean() {

    return new ServletRegistrationBean(new HelloServlet(),"/hello");

}

这段代码实现的方法返回一个 ServletRegistrationBean,并将它当作 Bean 注册到 Spring 中,因此你需要把这段代码放到 Spring Boot 自动扫描的目录中,或者放到 @Configuration 标识的类中。

3. 动态注册

你还可以创建一个类去实现前面提到的 ServletContextInitializer 接口,并把它注册为一个 Bean,Spring Boot 会负责调用这个接口的 onStartup 方法。

@Component

public class MyServletRegister implements ServletContextInitializer {



    @Override

    public void onStartup(ServletContext servletContext) {



        //Servlet 3.0 规范新的 API

        ServletRegistration myServlet = servletContext

        .addServlet("HelloServlet", HelloServlet.class);



        myServlet.addMapping("/hello");



        myServlet.setInitParameter("name", "Hello Servlet");

    }



}

这里请注意两点:

  • ServletRegistrationBean 其实也是通过 ServletContextInitializer 来实现的,它实现了 ServletContextInitializer 接口。
  • 注意到 onStartup 方法的参数是我们熟悉的 ServletContext,可以通过调用它的 addServlet 方法来动态注册新的 Servlet,这是 Servlet 3.0 以后才有的功能。

Web 容器的定制

我们再来考虑一个问题,那就是如何在 Spring Boot 中定制 Web 容器。在 Spring Boot 2.0 中,我们可以通过两种方式来定制 Web 容器。

第一种方式是通过通用的 Web 容器工厂 ConfigurableServletWebServerFactory,来定制一些 Web 容器通用的参数:

@Component

public class MyGeneralCustomizer implements

WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {



    public void customize(ConfigurableServletWebServerFactory factory) {

        factory.setPort(8081);

        factory.setContextPath("/hello");

    }

}

第二种方式是通过特定 Web 容器的工厂比如 TomcatServletWebServerFactory 来进一步定制。下面的例子里,我们给 Tomcat 增加一个 Valve,这个 Valve 的功能是向请求头里添加 traceid,用于分布式追踪。TraceValve 的定义如下:

class TraceValve extends ValveBase {

    @Override

    public void invoke(Request request, Response response) throws IOException, ServletException {



        request.getCoyoteRequest().getMimeHeaders().

        addValue("traceid").setString("1234xxxxabcd");



        Valve next = getNext();

        if (null == next) {

            return;

        }



        next.invoke(request, response);

    }



}

跟第一种方式类似,再添加一个定制器,代码如下:

@Component

public class MyTomcatCustomizer implements

WebServerFactoryCustomizer<TomcatServletWebServerFactory> {



    @Override

    public void customize(TomcatServletWebServerFactory factory) {

        factory.setPort(8081);

        factory.setContextPath("/hello");

        factory.addEngineValves(new TraceValve() );



    }

}

本期精华

今天我们学习了 Spring Boot 如何利用 Web 容器的 API 来启动 Web 容器、如何向 Web 容器注册 Servlet,以及如何定制化 Web 容器,除了给 Web 容器配置参数,还可以增加或者修改 Web 容器本身的组件。

问题

我在文章中提到,通过 ServletContextInitializer 接口可以向 Web 容器注册 Servlet,那 ServletContextInitializer 跟 Tomcat 中的 ServletContainerInitializer 有什么区别和联系呢?

在 Spring Boot 中,我们可以不手动注册 **Servlet**,直接使用 @Controller 或 @RestController 处理 HTTP 请求,而这些 Controller 本质上是Spring MVC 框架封装的 Servlet 机制。

解答

1. Tomcat 作为 Servlet 容器

Spring Boot 默认使用内嵌 Tomcat 作为 Web 容器,而 Tomcat 运行 Spring MVC 时,本质上还是依赖 Servlet 规范,即:

  • Spring Boot 自动注册了 **DispatcherServlet** 作为前端控制器(Front Controller)。
  • 所有 **@Controller** 方法的请求都会经过 **DispatcherServlet** 进行分发处理。

2. **DispatcherServlet** 如何代替原生 **Servlet**?

通常,在传统的 Java Web 项目中,我们需要手动注册 <font style="color:rgb(53, 53, 53);">Servlet</font>,例如:

@WebServlet("/hello")
public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        resp.getWriter().write("Hello Servlet");
    }
}

但在 Spring Boot 中,我们可以直接用 <font style="color:rgb(53, 53, 53);">@Controller</font> 代替 <font style="color:rgb(53, 53, 53);">Servlet</font>:

@RestController
public class MyController {
    @GetMapping("/hello")
    public String hello() {
        return "Hello Spring Boot";
    }
}

为什么 **@Controller** 可以代替 **Servlet**?

  1. **Spring Boot 启动时自动注册 ****DispatcherServlet**
    • <font style="color:rgb(53, 53, 53);">DispatcherServlet</font> 本质上是一个特殊的 Servlet,用于接管所有请求,并交给 Spring MVC 处理。
    • 它是一个 **HttpServlet**** 的子类**,Spring Boot 启动时会自动创建并注册:
@Bean
public DispatcherServlet dispatcherServlet() {
    return new DispatcherServlet();
}
- **Spring Boot 默认将 **`**DispatcherServlet**`** 映射到 **`**/**`**(拦截所有请求)**<font style="color:rgb(53, 53, 53);">: </font>
@Bean
public ServletRegistrationBean<DispatcherServlet> dispatcherRegistration(DispatcherServlet servlet) {
    return new ServletRegistrationBean<>(servlet, "/");
}
  1. 所有 **@Controller** 请求会被 **DispatcherServlet** 处理
    • <font style="color:rgb(53, 53, 53);">DispatcherServlet</font> 作为 前端控制器(Front Controller),会拦截所有请求,并根据 URL 映射到对应的 <font style="color:rgb(53, 53, 53);">@Controller</font> 方法:
      • <font style="color:rgb(53, 53, 53);">/hello</font> → <font style="color:rgb(53, 53, 53);">@GetMapping("/hello")</font>
      • <font style="color:rgb(53, 53, 53);">/user/1</font> → <font style="color:rgb(53, 53, 53);">@GetMapping("/user/{id}")</font>
    • 它的底层实现类似:
public class DispatcherServlet extends FrameworkServlet {
    protected void doService(HttpServletRequest request, HttpServletResponse response) {
        // 解析请求路径,匹配 Controller 方法
        HandlerMethod handler = handlerMapping.getHandler(request);
        handlerAdapter.handle(request, response, handler);
    }
}

**3. **@Controller** vs 传统 ****Servlet**

对比项**传统 ****Servlet****Spring Boot ****@Controller**
注册方式需要 <font style="color:rgb(53, 53, 53);">@WebServlet</font>
或 <font style="color:rgb(53, 53, 53);">web.xml</font>
配置
Spring Boot 自动注册 <font style="color:rgb(53, 53, 53);">DispatcherServlet</font>
请求分发由 <font style="color:rgb(53, 53, 53);">HttpServlet</font>
处理请求
<font style="color:rgb(53, 53, 53);">DispatcherServlet</font>
自动解析 <font style="color:rgb(53, 53, 53);">@RequestMapping</font>
参数获取<font style="color:rgb(53, 53, 53);">request.getParameter("name")</font>方法参数自动解析,如 <font style="color:rgb(53, 53, 53);">@RequestParam("name") String name</font>
返回值需要 <font style="color:rgb(53, 53, 53);">response.getWriter().write("xxx")</font>直接返回 <font style="color:rgb(53, 53, 53);">String</font>
或 <font style="color:rgb(53, 53, 53);">JSON</font>
,Spring MVC 自动处理
扩展性逻辑代码集中在 <font style="color:rgb(53, 53, 53);">doGet()</font>支持 <font style="color:rgb(53, 53, 53);">@GetMapping</font>
、<font style="color:rgb(53, 53, 53);">@PostMapping</font>
,更清晰

4. 结论

  • Spring Boot 不需要手动注册 Servlet,因为 **DispatcherServlet**** 自动注册**,它会拦截所有 HTTP 请求。
  • **@Controller**** 只是对 Servlet 进行了封装**,本质上 <font style="color:rgb(53, 53, 53);">@Controller</font> 处理请求的方式仍然依赖 <font style="color:rgb(53, 53, 53);">Servlet</font> 机制。
  • 好处:
    • 代码更简洁,不需要手写 <font style="color:rgb(53, 53, 53);">Servlet</font> 逻辑。
    • 支持自动参数解析,不需要 <font style="color:rgb(53, 53, 53);">request.getParameter()</font>。
    • 支持 JSON 直接返回,无需手动写 <font style="color:rgb(53, 53, 53);">response.getWriter()</font>。

这样,Spring Boot 让 Web 开发变得更简单、更高效! 🚀

Spring Boot 之所以可以运行 @Controller 并处理 HTTP 请求,本质上仍然是依赖 Tomcat 提供的 Servlet 容器来运行 DispatcherServlet。它们的关系如下:


1. Tomcat 在 Spring Boot 中的作用

Spring Boot 默认使用内嵌 Tomcat 作为 Web 容器,而 Tomcat 是一个 Servlet 容器,用于管理和执行 Servlet 规范的应用。具体来说,它的作用是:

  1. 监听 HTTP 请求
    • 启动 Tomcat 时,会监听指定的端口(默认 8080),并等待 HTTP 请求的到来。
  2. **创建 **HttpServletRequest** 和 ****HttpServletResponse**
    • 当请求到达时,Tomcat 解析 HTTP 协议,封装成 HttpServletRequest(请求对象)和 HttpServletResponse(响应对象)。
  3. **调用 ****DispatcherServlet**
    • Tomcat 发现 /hello 这样的请求路径后,会调用 Spring Boot 自动注册的 DispatcherServlet 进行请求分发:
HttpServletRequest request = new RequestFacade(req);
HttpServletResponse response = new ResponseFacade(resp);
dispatcherServlet.service(request, response);
  1. 最终返回响应
    • DispatcherServlet 解析 URL,调用 @Controller 方法处理,并返回 Response,然后 Tomcat 通过 OutputStream 返回 HTTP 响应给客户端。

2. Spring Boot 和 Tomcat 之间的关系

Spring Boot 的 spring-boot-starter-web 默认使用 **Tomcat** 作为内嵌 Web 容器:

  • 这意味着 Spring Boot 项目不需要手动安装 Tomcat,它会在启动时自动创建一个 Tomcat 服务器。
  • 运行 SpringApplication.run() 时,Spring Boot 启动并初始化 Tomcat:
// Spring Boot 启动 Tomcat
TomcatWebServer webServer = new TomcatWebServer();
webServer.start(); // 监听 8080 端口

整个流程如下:

  1. 启动 Spring Boot(SpringApplication.run())
  2. 初始化 Tomcat
  3. 注册 **DispatcherServlet** 到 Tomcat
  4. Tomcat 监听 HTTP 请求
  5. 请求到达 Tomcat,交给 **DispatcherServlet** 处理
  6. **DispatcherServlet**** 调用 **@Controller** 处理请求**
  7. Tomcat 通过 **OutputStream** 返回 HTTP 响应

3. Tomcat、Servlet、Spring Boot、Spring MVC 的关系

可以用一张图来表示:

+----------------------------+
|        浏览器请求          |
+----------------------------+
           │
           ▼
+----------------------------+
|        Tomcat 服务器       |   ⬅ (Servlet 容器)
+----------------------------+
           │
           ▼
+----------------------------+
|    DispatcherServlet (前端控制器) |
+----------------------------+
           │
           ▼
+----------------------------+
|     @Controller 业务逻辑   |
+----------------------------+
           │
           ▼
+----------------------------+
|   返回数据 (HTML / JSON)   |
+----------------------------+
  • Tomcat:作为 Servlet 容器,接收 HTTP 请求,并调用 DispatcherServlet。
  • DispatcherServlet:Spring MVC 核心,拦截所有请求,并转发到 @Controller 处理。
  • @Controller:开发者定义的业务逻辑,返回数据(HTML 或 JSON)。
  • 最终:Tomcat 负责将返回数据通过 HTTP 传输回浏览器。

4. 为什么不直接使用 Tomcat?

在传统 Java Web 开发中,我们需要:

  • 手动安装 Tomcat
  • 手动部署 WAR 文件
  • 手动配置 Servlet

但 Spring Boot 内嵌了 Tomcat,使得: ✅ 不用手动安装 Tomcat,直接运行 main() 方法即可启动
✅ 不用部署 WAR,直接 java -jar 运行 Spring Boot 应用
✅ **Spring Boot 负责自动注册 ****Servlet**,简化开发流程


5. 如何替换 Tomcat?

虽然 Spring Boot 默认使用 Tomcat,但我们可以替换成 Jetty 或 Undertow:

<!-- 1. 排除 Tomcat -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<!-- 2. 添加 Jetty -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

这样 Spring Boot 就会使用 Jetty,而不是 Tomcat。


6. 总结

  • Tomcat 是 Servlet 容器,它负责解析 HTTP 请求,并调用 Servlet。
  • Spring Boot 通过 **DispatcherServlet** 代理 **Servlet** 处理逻辑,所以可以用 @Controller 代替传统 Servlet。
  • Tomcat 仍然在底层运行,它负责启动 Web 服务器,解析 HTTP 请求,然后把请求交给 DispatcherServlet。
  • Spring Boot 内嵌 Tomcat,使得 Web 应用可以用 **java -jar** 直接运行,不需要额外的 Tomcat 服务器。

所以,Spring Boot 并没有绕开 Tomcat,而是利用 Tomcat 运行 **DispatcherServlet**,最终用 **@Controller** 处理 HTTP 请求。🚀

你的总结已经很详细了,以下是进一步精炼的核心要点,方便快速理解:


1. ServletContextInitializer vs ServletContainerInitializer

  • ServletContextInitializer(Spring 提供)
    • 作用:用于 Spring Boot 自动注册 Servlet、Filter、Listener,无需手写 web.xml。
    • 运行时机:Spring Boot 启动时被 SpringServletContainerInitializer 调用。
    • 使用方式:通常通过 @Bean 或 SpringApplication 自动加载。
  • ServletContainerInitializer(Servlet 规范提供)
    • 作用:Tomcat 等 Web 容器初始化 Servlet 时执行,可以在 META-INF/services 注册。
    • 运行时机:Web 容器(如 Tomcat)启动时执行,扫描 @HandlesTypes 注解的类。
    • Spring Boot 适配方式:Spring 通过 SpringServletContainerInitializer 作为适配器,将其转换为 ServletContextInitializer 机制。

2. 为什么 @Controller 可以替代 Servlet?

Spring Boot 通过 **DispatcherServlet** 代理了原生 Servlet 逻辑:

  1. **Spring Boot 启动时自动注册 ****DispatcherServlet**
    • DispatcherServlet 继承自 HttpServlet,是一个特殊的 Servlet。
    • 被映射到 /,拦截所有请求并交给 Spring MVC 处理。
  2. 请求流程
    • Tomcat 监听 HTTP 请求,将其交给 DispatcherServlet。
    • DispatcherServlet 解析 URL,匹配到 @Controller 方法并执行。
    • 结果返回给 Tomcat,最终响应给客户端。

3. Spring Boot、Tomcat、Spring MVC 关系

  • Tomcat:作为 Servlet 容器,解析 HTTP 请求,调用 DispatcherServlet 处理。
  • DispatcherServlet:Spring MVC 核心,拦截请求,调用 @Controller 业务逻辑。
  • @Controller:用户编写的业务逻辑层,返回 HTML/JSON 作为响应。

请求处理流程

  1. 客户端请求(浏览器访问 http://localhost:8080/hello)。
  2. Tomcat 解析 HTTP 请求,创建 HttpServletRequest 和 HttpServletResponse。
  3. **Tomcat 将请求转交给 ****DispatcherServlet** 进行处理。
  4. **DispatcherServlet**** 解析 URL,匹配对应的 **@Controller** 方法**:
@RestController
public class MyController {
    @GetMapping("/hello")
    public String hello() {
        return "Hello Spring Boot";
    }
}
  1. 方法执行后返回数据,DispatcherServlet 处理响应,最终 Tomcat 发送回客户端。

4. Spring Boot 为什么比传统 Servlet 方便?

传统 ServletSpring Boot @Controller
注册方式@WebServlet
或 web.xml
Spring Boot 自动注册 DispatcherServlet
请求处理HttpServlet
手动解析 request.getParameter()
@RequestParam
自动解析参数
返回数据需要 response.getWriter().write("xxx")直接返回 String
或 JSON
扩展性逻辑集中在 doGet()
,难维护
支持 @GetMapping
,代码更清晰

Spring Boot 主要简化了 Servlet 开发:

  • ✅ 不需要手动注册 Servlet,DispatcherServlet 自动代理。
  • ✅ 简化参数解析,不用 request.getParameter()。
  • ✅ 支持 JSON 响应,不用 response.getWriter() 手动输出。

5. 如何替换 Tomcat?

虽然 Spring Boot 默认使用 Tomcat,但可以改为 Jetty 或 Undertow:

<!-- 移除 Tomcat -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<!-- 添加 Jetty -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

这样 Spring Boot 就会使用 Jetty,而不是 Tomcat。


6. 总结

✅ Tomcat 是 Spring Boot 内嵌的 Servlet 容器,用于解析 HTTP 请求并调用 DispatcherServlet。
✅ Spring Boot 通过 **DispatcherServlet** 代理 Servlet 逻辑,让开发者可以直接用 @Controller 处理请求。
✅ Spring Boot 让 Web 开发更简洁高效,自动完成 Servlet 注册,支持参数解析和 JSON 响应。
✅ 可以替换 Tomcat 为 Jetty 或 Undertow,适配不同的 Web 服务器需求。


这样,Spring Boot 并没有绕开 Tomcat,而是利用 Tomcat 运行 **DispatcherServlet**,最终用 **@Controller** 处理 HTTP 请求。🚀