从 01 开始 从 01 开始
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • RSS
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金等
  • 职场规划
  • 关于离职
  • 杂谈
  • 📖 读书

    • 读书工具
    • 读书笔记
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档

晓林

程序猿,自由职业者,博主,英语爱好者,健身达人
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • RSS
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金等
  • 职场规划
  • 关于离职
  • 杂谈
  • 📖 读书

    • 读书工具
    • 读书笔记
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档
  • JavaSE

  • JavaSenior

  • JavaEE

  • JavaWeb

  • Spring

  • 主流框架

  • SpringMVC

  • SpringBoot

    • SpringBoot教程-尚硅谷

      • SpringBoot 课程介绍
      • Spring 和 SpringBoot
      • HelloWorld
      • 了解自动配置原理
      • 底层注解-@Configuration 详解
      • 底层注解-@Import 导入组件
      • 底层注解-@Conditional 条件装配
      • 原生配置文件引入-@ImportResource
      • 底层注解-配置绑定 @ConfigurationProperties
      • 自动配置原理
      • 自动配置流程
      • Lombok 简化开发
      • DevTools
      • Spring-Initailizr
      • 配置文件-Yaml 用法
      • Web 开发简介
      • web 开发-静态资源规则于定制化
      • 静态资源配置原理
      • Rest 映射及源码解析
      • 请求映射原理
      • 常用参数注解使用
      • MatrixVariable:矩阵变量
      • 各种类型参数解析原理
      • Servlet-API 参数解析原理
      • Model、Map 参数解析原理
      • 自定义对象参数绑定原理
      • 自定义 Converter 原理
      • 数据响应原理
      • 内容协商原理
      • 基于请求参数的内容原理
      • 自定义 MessageConverter 原理
      • Thymeleaf 初体验
      • web 实验-后台管理系统
      • web 实验-抽取公共页面
      • web 实验-遍历数据
      • 源码分析-视图解析器与视图
      • 拦截器-登录检查与静态资源放行
      • 拦截器的执行时机和原理
      • 单文件和多文件上传的使用
      • 文件上传原理
      • 错误处理机制
      • 错误处理-底层组件源码分析
      • 异常处理流程
      • 几种异常处理原理
      • Web 原生对象注入
      • 嵌入式 Servlet 容器
        • 原理
        • 切换其他服务器
        • 定制服务器
      • 定制化原理
      • 数据库场景的自动配置分析和整合测试
      • 自定义方式整合 Druid
      • 通过 starter 整合 Druid
      • 整合 Mybatis
      • 使用注解整合 Mybatis
      • 整合 MybatisPlus 操作数据库
      • MybatisPlus-列表分页展示
      • 整合 Redis
      • 单元测试-Junit5
      • 单元测试-断言机制
      • 单元测试-前置条件
      • 单元测试-嵌套测试
      • 单元测试-参数化测试
      • 指标监控-基本概念
      • 指标监控-配置 EndPoint
      • 指标监控-可视化
      • 原理解析-Profile 功能
      • 配置文件深入
      • 自定义 Starter
      • SpringApplication 初始化过程
      • SpringBoot 完整启动过程
  • Java
  • SpringBoot
  • SpringBoot教程-尚硅谷
2023-08-22
目录

嵌入式 Servlet 容器

# 460.嵌入式 Servlet 容器

之前我们说过,SpringBoot 内置了 Tomcat,只需启动应用即可监听端口,本文就来讲讲其原理

‍

ps:实用性不高,了解即可 ‍

# 原理

在官网文档,是这样说的:

The ServletWebServerApplicationContext

Under the hood, Spring Boot uses a different type of ApplicationContext for embedded servlet container support. The ServletWebServerApplicationContext is a special type of WebApplicationContext that bootstraps itself by searching for a single ServletWebServerFactory bean. Usually a TomcatServletWebServerFactory, JettyServletWebServerFactory, or UndertowServletWebServerFactory has been auto-configured. ‍ 大意:如果 SpringBoot 应用启动的时候,发现当前应用是 Web 应用,并且导入了 Tomcat,那么就会一个 Web 版本的 IoC 容器,名字是 ServletWebServerApplicationContext;

该 IoC 容器在启动的时候会搜索 ServletWebServerFactory,直译就是 Servlet 的 web 容器工厂,这个工厂生产的就是 Web 容器,例如 TomcatServletWebServerFactory,JettyServletWebServerFactory 或 UndertowServletWebServerFactory,对应生成的 Web 容器就是 Tomcat,Jetty 和 Undertow

注意,Undertow 不支持 JSP ‍ 这些工厂是 SpringBoot 底层默认有的,在配置类 ServletWebServerFactoryAutoConfiguration 中配置;直接由自动配置类帮我们自动配置好

@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
		ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
		ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
		ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
1
2
3
4
5
6
7
8
9
10

‍ 可以看到 Import 了 ServletWebServerFactoryConfiguration,而该类又注入了对应的容器工厂:

@Configuration(proxyBeanMethods = false)
class ServletWebServerFactoryConfiguration {

        @Configuration(proxyBeanMethods = false)
	@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
	static class EmbeddedTomcat {  //.....


	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class })
	static class EmbeddedJetty {  //.....

        @Configuration(proxyBeanMethods = false)
	@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
	static class EmbeddedUndertow {  //.....
//..... 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

如果我们导入了 Tomcat,那么根据 @ConditionalOnClass(Tomcat.class),就会注入 Tomcat 的工厂类;

由于 web-starter 默认会导入 tomcat 包,因此容器中就有 TomcatServletWebServerFactory,然后创建出 Tomcat 服务器并启动。

在 IoC 容器中,启动的时候会顺便启动服务器:

@Override
protected void onRefresh() {
	super.onRefresh();
	try {
		createWebServer();
	}
	catch (Throwable ex) {
		throw new ApplicationContextException("Unable to start web server", ex);
	}
}


private void createWebServer() {
	WebServer webServer = this.webServer;
	ServletContext servletContext = getServletContext();
	if (webServer == null && servletContext == null) {
		ServletWebServerFactory factory = getWebServerFactory();
		this.webServer = factory.getWebServer(getSelfInitializer());
		getBeanFactory().registerSingleton("webServerGracefulShutdown",
				new WebServerGracefulShutdownLifecycle(this.webServer));
		getBeanFactory().registerSingleton("webServerStartStop",
				new WebServerStartStopLifecycle(this, this.webServer));
	}
	else if (servletContext != null) {
		try {
			getSelfInitializer().onStartup(servletContext);
		}
		catch (ServletException ex) {
			throw new ApplicationContextException("Cannot initialize servlet context", ex);
		}
	}
	initPropertySources();
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

‍ 我们可以在 createWebServer 方法上打一个断点,然后启动。首先会获取工厂:

ServletWebServerFactory factory = getWebServerFactory();
1

其源码如下:也就是在 IoC 容器中找,找不到则抛异常;如果数量大于 1 也抛异常

protected ServletWebServerFactory getWebServerFactory() {
	// Use bean names so that we don't consider the hierarchy
	String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);

	if (beanNames.length == 0) {
		throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing " + "ServletWebServerFactory bean.");
	}

	if (beanNames.length > 1) {
		throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple " + "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
	}

	return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

拿到工厂后,下一步就是创建服务器了:

ServletWebServerFactory factory = getWebServerFactory();
this.webServer = factory.getWebServer(getSelfInitializer());
1
2

‍ 我们步入 getWebServer 方法:

@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
    if (this.disableMBeanRegistry) {
    	Registry.disableRegistry();
    }
    Tomcat tomcat = new Tomcat();
    File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    Connector connector = new Connector(this.protocol);
    connector.setThrowOnFailure(true);
    tomcat.getService().addConnector(connector);
    customizeConnector(connector);
    tomcat.setConnector(connector);
    tomcat.getHost().setAutoDeploy(false);
    configureEngine(tomcat.getEngine());
    for (Connector additionalConnector : this.additionalTomcatConnectors) {
	tomcat.getService().addConnector(additionalConnector);
    }
    prepareContext(tomcat.getHost(), initializers);
    return getTomcatWebServer(tomcat);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

其内部就会初始化服务器的信息,连接器,Host 和 Engine 等等(了解即可)。之前我们使用 Tomcat,都是双击 startup.bat 启动;而所谓内嵌服务器,就是在代码里完成这一步

# 切换其他服务器

如果想切换为其他服务器,例如 Undertow,怎么做呢?

首先,Tomcat 是有 web 场景导入的:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
1
2
3
4

‍ 我们点进该依赖的详情,就有 Tomcat:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-tomcat</artifactId>
  <version>2.3.4.RELEASE</version>
  <scope>compile</scope>
</dependency>
1
2
3
4
5
6

‍ 因此,我们得先排除掉 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>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

‍ 再次启动,就能看到是 Undertow 了:

‍

当然,建议还是用 Tomcat,因为 Tomcat 综合起来看还是不错的 ‍

# 定制服务器

如果我们想要修改服务器相关配置,也是很简单的,首先可以看文档:

可以看到要定制化,就是改配置文件,很多都是以 server 开头的。 ‍ 从源码方面,很多配置都是取值 ServerProperties 类:

@EnableConfigurationProperties(ServerProperties.class)
public class ServletWebServerFactoryAutoConfiguration {
1
2

‍ 该类都是以 server 作为前缀的:

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
	private Integer port;
	private InetAddress address;

    private final Servlet servlet = new Servlet();

        private final Tomcat tomcat = new Tomcat();

	private final Jetty jetty = new Jetty();

	private final Netty netty = new Netty();
        //......
1
2
3
4
5
6
7
8
9
10
11
12
13

‍ 可以看到,还可以针对不同的服务器做配置,然后 Servlet 做共有的配置。

  • 例如使用 Tomcat,端口是 9999,想要的日志路径是 ./logs2
  • 使用 undertow 的话,端口是 9999,日志路径是 ./logs
server:
  port: 9999
  tomcat:
    accesslog:
      directory: ./logs2
  undertow:
    accesslog:
      dir: ./logs
1
2
3
4
5
6
7
8

还可以自定义一个 Web 服务器工厂,在我们的自动配置类中,还注入了一个 Customizer,定制化器:

public class ServletWebServerFactoryAutoConfiguration {

	@Bean
	public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties) {
		return new ServletWebServerFactoryCustomizer(serverProperties);
	}
1
2
3
4
5
6

该定制化器,就是在 web 服务器工厂准备好后,再修改一些属性,比如 customize 方法,就会拿到配置文件里的东西,然后绑定到工厂上:

public class ServletWebServerFactoryCustomizer
	implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {


    @Override
    public void customize(ConfigurableServletWebServerFactory factory) {
	PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
	map.from(this.serverProperties::getPort).to(factory::setPort);
	map.from(this.serverProperties::getAddress).to(factory::setAddress);
	map.from(this.serverProperties.getServlet()::getContextPath).to(factory::setContextPath);
	map.from(this.serverProperties.getServlet()::getApplicationDisplayName).to(factory::setDisplayName);
	map.from(this.serverProperties.getServlet()::isRegisterDefaultServlet).to(factory::setRegisterDefaultServlet);
	map.from(this.serverProperties.getServlet()::getSession).to(factory::setSession);
	map.from(this.serverProperties::getSsl).to(factory::setSsl);
	map.from(this.serverProperties.getServlet()::getJsp).to(factory::setJsp);
	map.from(this.serverProperties::getCompression).to(factory::setCompression);
	map.from(this.serverProperties::getHttp2).to(factory::setHttp2);
	map.from(this.serverProperties::getServerHeader).to(factory::setServerHeader);
	map.from(this.serverProperties.getServlet()::getContextParameters).to(factory::setInitParameters);
	map.from(this.serverProperties.getShutdown()).to(factory::setShutdown);
	}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

‍ 这里扩展下,SpringBoot 中经常有 xxxxCustomizer 定制化器 这种模式,只需自定义一个定制化器,就能修改某个东西的默认行为 ‍

上次更新: 2025/5/17 12:26:09
Web 原生对象注入
定制化原理

← Web 原生对象注入 定制化原理→

最近更新
01
吐槽一下《僵尸校园》
05-15
02
2025 年 4 月记
04-30
03
山西大同 “订婚强奸案” 将会给整个社会带来的影响有多严重? - 知乎 转载
04-26
更多文章>
Theme by Vdoing | Copyright © 2022-2025 | 粤 ICP 备 2022067627 号 -1 | 粤公网安备 44011302003646 号 | 点击查看十年之约
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式