红茶的个人站点

  • 首页
  • 专栏
  • 开发工具
  • 其它
  • 隐私政策
Awalon
Talk is cheap,show me the code.
  1. 首页
  2. 专栏
  3. Spring Boot 学习笔记
  4. 正文

Spring 源码学习 16:Tomcat

2025年7月8日 5点热度 0人点赞 0条评论

基本结构

Tomcat 的基本结构:

Server
└───Service
    ├───Connector (协议, 端口)
    └───Engine
        └───Host(虚拟主机 localhost)
            ├───Context1 (应用1, 可以设置虚拟路径, / 即 url 起始路径; 项目磁盘路径, 即 docBase )
            │   │   index.html
            │   └───WEB-INF
            │       │   web.xml (servlet, filter, listener) 3.0
            │       ├───classes (servlet, controller, service ...)
            │       ├───jsp
            │       └───lib (第三方 jar 包)
            └───Context2 (应用2)
                │   index.html
                └───WEB-INF
                        web.xml

关于结构的说明可以看这个视频。

启动内嵌 Tomcat

用编程的方式启动 Tomcat 服务器:

Tomcat tomcat = new Tomcat();
// 创建临时目录作为 Tomcat 应用的目录
File dir = Files.createTempDirectory("tomcat.").toFile();
dir.deleteOnExit(); // 程序退出后自动删除
// 添加 context,虚拟路径为根路径/,本地文件路径为临时目录
tomcat.addContext("", dir.getAbsolutePath());
// 启动 Tomcat
tomcat.start();
// 设置连接,监听 8080 端口
Connector connector = new Connector(new Http11Nio2Protocol());
connector.setPort(8080);
tomcat.setConnector(connector);
tomcat.getServer().await();
  • addContext的第一个参数是虚拟路径,即映射到 http 请求的路径,如果要映射到根路径,这里要使用空字符串。

  • Http11Nio2Protocol表示用 nio2 实现的 http1.1 协议。

可以用编程的方式添加 Servlet 到 Tomcat:

Context context = tomcat.addContext("", dir.getAbsolutePath());
// 添加 Servlet 容器的初始化器
context.addServletContainerInitializer(new ServletContainerInitializer() {
    @Override
    public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
        ctx.addServlet("myServlet", MyServlet.class).addMapping("/hello");
    }
}, Collections.emptySet());

Servlet 由 Servlet 容器(Container)管理,需要通过为上下文(Context) 添加 Servlet 容器初始化器(Container Initializer)的方式添加 Servlet,这样在之后的 Tomcat 启动时,就可以利用容器初始化器真正完成添加动作。需要注意的是,添加 Servlet 的时候,需要指定路径映射(addMapping)。

这里使用的 Servlet:

public static class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        resp.getWriter().println("<h1>Hello World</h1>");
    }
}

这个 Servlet 必须是 public 的,否则 Tomcat 无法创建。

Spring 容器整合

现在将 Spring 容器和内嵌 Tomcat 进行整合,这样就可以使用 Spring MVC 编写 Controller,而不是直接使用 Servlet。

先创建 Spring 容器:

// 创建 Spring 容器
AnnotationConfigWebApplicationContext springContext = new AnnotationConfigWebApplicationContext();
springContext.register(WebConfig.class);
springContext.refresh();

容器应当选择AnnotationConfigWebApplicationContext,这是个不带内嵌 Tomcat 的 Web 容器。

这里使用的配置类:

@Configuration
@Import(MyController.class)
static class WebConfig {
    @Bean
    public DispatcherServlet dispatcherServlet() {
        return new DispatcherServlet();
    }
​
    @Bean
    public RequestMappingHandlerAdapter handlerAdapter() {
        RequestMappingHandlerAdapter requestMappingHandlerAdapter = new RequestMappingHandlerAdapter();
        requestMappingHandlerAdapter.setMessageConverters(List.of(new MappingJackson2HttpMessageConverter()));
        return requestMappingHandlerAdapter;
    }
}

包含一个DispatcherServlet以及重写的RequestMappingHandlerAdapter。之所以要重写RequestMappingHandlerAdapter,是因为AnnotationConfigWebApplicationContext自带的RequestMappingHandlerAdapter缺少 JSON 消息转换器,会在需要解析或返回 JSON 消息时报错。

包含的 Controller:

@Controller
static class MyController {
    @GetMapping("/hello2")
    @ResponseBody
    public Map<String, String> hello2() {
        return Map.of("msg", "Hello World");
    }
}

通过 Tomcat 的 Servlet 容器初始化器添加 DispatcherServlet:

// 添加 Servlet 容器的初始化器
context.addServletContainerInitializer(new ServletContainerInitializer() {
    @Override
    public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
        ctx.addServlet("myServlet", MyServlet.class).addMapping("/hello");
        // 将 Spring 容器的 DispatcherServlet 添加到 Tomcat
        DispatcherServlet dispatcherServlet = springContext.getBean(DispatcherServlet.class);
        ctx.addServlet("dispatcherServlet", dispatcherServlet).addMapping("/");
    }
}, Collections.emptySet());

现在所有匹配不到 Servlet 的请求都将由 Spring 的 DispatcherServlet 进行处理,即转发到 Controller。

启动 Tomcat 后请求 http://localhost:8080/hello2 就能看到效果。

如果 Spring 容器中有多个 DispatcherServlet,难道要编写多行添加 Servlet 的代码?Spring 对此进行了封装,提供一个注册类DispatcherServletRegistrationBean,用于将 DispatcherServlet 注册到 Tomcat。

@Configuration
@Import(MyController.class)
static class WebConfig {
    @Bean
    DispatcherServletRegistrationBean dispatcherServletRegistrationBean(){
        return new DispatcherServletRegistrationBean(dispatcherServlet(), "/");
    }
​
    // ...
}

注册:

// 添加 Servlet 容器的初始化器
context.addServletContainerInitializer(new ServletContainerInitializer() {
    @Override
    public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
        ctx.addServlet("myServlet", MyServlet.class).addMapping("/hello");
        // 将 Spring 容器的 DispatcherServlet 添加到 Tomcat
        for (DispatcherServletRegistrationBean registrationBean : springContext.getBeansOfType(DispatcherServletRegistrationBean.class).values()) {
            registrationBean.onStartup(ctx);
        }
    }
}, Collections.emptySet());

本文的完整示例可以从这里获取。

The End.

参考资料

  • 黑马程序员Spring视频教程,深度讲解spring5底层原理

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: spring tomcat
最后更新:2025年7月8日

魔芋红茶

加一点PHP,加一点Go,加一点Python......

点赞
< 上一篇

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

COPYRIGHT © 2021 icexmoon.cn. ALL RIGHTS RESERVED.
本网站由提供CDN加速/云存储服务

Theme Kratos Made By Seaton Jiang

宁ICP备2021001508号

宁公网安备64040202000141号