《SpringBoot 3:入门与应用实战》第 11 章 嵌入式容器 阅读笔记 29 《SpringBoot 3入门与应用实战》第 11 章 嵌入式容器 阅读笔记 29对于传统的 Web 应用而言在开发完毕后需要将工程打包成 war 包部署到独立的 Web 容器中如 Tomcat、Jetty、Undertow 等运行工程时也需要启动独立的 Web 容器。而在前面两章的学习中自始至终都没有将 Spring Boot 应用部署到外置的 Web 容器中这背后的功臣是 Spring Boot 整合的嵌入式 Web 容器。有了嵌入式 Web 容器启动 Spring Boot 的Web应用时便可以独立启动而不再依赖任何外置 Web 容器。本章主要研究的就是 Spring Boot 内部集成的嵌入式容器整体来讲难度不大以熟悉为主。11.1 Web 容器对比首先对外置 Web 容器与嵌入式 Web 容器进行简单了解和对比。仅从名称上也能分辨出外置 Web 容器是作为操作系统中一个可执行程序出现的而嵌入式 Web 容器则会嵌入应用程序中成为其一部分整合嵌入式Web容器的应用不借助外部容器和资源即可独立启动 Web 应用。也正因如此嵌入式 Web 容器支持成为 Spring Boot 最强大的特性之一。以 Tomcat 为例普通的外置 Tomcat 与嵌入式 Tomcat 从核心、本质上看没有任何区别它们都可以承载 Web 应用的运行但是 Spring Boot整合嵌入式 Tomcat 容器时考虑了一些特殊的因素它会在底层设定一些额外的限制需要读者简单了解。部署应用的限制。由于嵌入式 Tomcat 不是独立的 Web 容器它需要嵌入特定应用中该特性使得嵌入式 Tomcat 只能一次部署一个 Web 应用。web.xml 的限制。Spring Boot 整合嵌入式 Tomcat 后不再对 web.xml 文件予以支持转而使用 Bean 配合 ServletRegistrationBean 实现Servlet、Filter、Listener 的编程式注册对应 10.3.6 节中讲解的内容​。原生 Servlet 三大组件的限制。原生的基于 Servlet 3.0 及以上规范的 Web 项目其类路径下的 Servlet、Filter、Listener 可以被自动扫描并注册而 Spring Boot 整合嵌入式 Tomcat 后该特性会消失如果需要开启此特性需要配合 ServletComponentScan 使用对应 10.3.6 节中讲解的内容​。JSP 的限制。Spring Boot 整合嵌入式 Tomcat 后如果以独立 jar 包的方式启动项目则项目中编写的 JSP 会失效如果以 war 包的方式部署到外置 Tomcat 容器则 JSP 可以正常运行出现该现象的原因是嵌入式 Tomcat 没有引入 JSP 引擎依赖​。11.2 使用嵌入式 Tomcat在默认情况下当 Spring Boot 引入 WebMvc 依赖时会自动将嵌入式 Tomcat 引入工程可以借助 IDEA 的 Maven 依赖工具查看。可以看到Spring Boot 4.1.1 版本中依赖的 Tomcat 版本为 11.0.24。可以看到 3 个嵌入式 Tomcat 的依赖包分别是核心包 core、支持 EL 表达式的 el 包以及支持 websocket 的包。对 Spring Boot 引入嵌入式Tomcat 而言这 3 个依赖包已经完全足够。从控制台的输出中也能看出引入的 Tomcat 版本并且默认运行在 8080 端口。仔细观察日志的内容还可以发现其实日志中已经体现出使用的 ApplicationContext 也适配了嵌入式 Web 容器的实现第6行​。11.3 定制嵌入式容器默认情况下虽然使用嵌入式 Web 容器后不需要做什么配置就可以用于开发但置于真实的生产环境中时还是需要一些配置比如端口的配置、SSL 证书的配置等。Spring Boot 提供了两种定制嵌入式 Web 容器的方式下面分别讲解。11.3.1 修改配置属性Spring Boot 提供的全局配置文件针对嵌入式 Web 容器已经提供了非常多的配置属性直接在 application.properties 中配置即可它们大多以server.* 格式为主代码展示了部分配置属性的设置包括端口设置、上下文路径修改等还有针对 Tomcat 单独设置的配置属性包括 Tomcat的工作线程数量、最大连接数、静态资源缓存等。server.port9090 server.servlet.context-path/embedded server.servlet.session.timeout60m# 最小工作线程数量默认10server.tomcat.threads.min-spare20# 最大工作线程数量默认200server.tomcat.threads.max500# 日志保存的最大日期默认-1代表永久保存server.tomcat.accesslog.max-days15# form表单最大允许提交的大小默认2server.tomcat.max-http-form-post-size2MB# 最大连接数默认8192server.tomcat.max-connections8192# 是否开启静态资源缓存默认开启server.tomcat.resource.allow-cachingtrue# 静态资源的缓存过期时间默认5秒server.tomcat.resource.cache-ttl30m值得关注的一个配置属性是 server.port通过这个属性可以修改嵌入式 Web 容器的端口号如果不做任何配置那么 Web 容器会默认运行在8080 端口上。通常来讲不推荐将端口号设置为 1024 以内的数值而将端口设置为 0 时会随机产生一个与当前系统不冲突的端口号作为 Web容器的监听端口。11.3.2 使用定制器如果 Spring Boot 提供的配置属性不足以满足诉求还可以使用 Spring Boot 提供的另一种方式定制器。如果说配置属性的方式属于声明式配置那么定制器的方式就可以称作编程式配置。相较于使用配置属性而言使用定制器的特点是更加灵活但同时也增加了代码量。下面用一个简单示例来演示定制器的使用对嵌入式 Web 容器而言使用的定制器是一个固定的接口 WebServerFactoryCustomizer它需要指定一个定制的泛型关于这个泛型可以选择与具体容器无关的 ConfigurableWebServerFactory也可以是当前工程中引入的某个嵌入式容器的派生子类如 Tomcat 对应的派生类是 ConfigurableTomcatWebServerFactory​。如果指定的泛型是 ConfigurableWebServerFactory那么在定制器的内部中调用的方法都是嵌入式 Web 容器共有的属性如端口号、SSL 配置、HTTP2 配置等而如果指定的是具体派生类如ConfigurableTomcatWebServerFactory那么可以调用更多的与 Tomcat 相关的方法以供更加细致地定制嵌入式Tomcat。packagecom.yangjunbo.springbootembedded.container.config;importorg.springframework.boot.tomcat.ConfigurableTomcatWebServerFactory;importorg.springframework.boot.web.server.ConfigurableWebServerFactory;importorg.springframework.boot.web.server.WebServerFactoryCustomizer;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;/** * ClassName: WebServerConfigure * Package: com.yangjunbo.springbootembedded.container.config * Description: * * Author 杨钧博 * Create 2026/9/2 10:31 * Version 1.0 */ConfigurationpublicclassWebServerConfigure{BeanpublicWebServerFactoryCustomizerConfigurableWebServerFactorywebServerFactoryCustomizer(){returnfactory-{factory.set};}}packagecom.yangjunbo.springbootembedded.container.config;importorg.springframework.boot.tomcat.ConfigurableTomcatWebServerFactory;importorg.springframework.boot.web.server.ConfigurableWebServerFactory;importorg.springframework.boot.web.server.WebServerFactoryCustomizer;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;/** * ClassName: WebServerConfigure * Package: com.yangjunbo.springbootembedded.container.config * Description: * * Author 杨钧博 * Create 2026/9/2 10:31 * Version 1.0 */ConfigurationpublicclassWebServerConfigure{BeanpublicWebServerFactoryCustomizerConfigurableTomcatWebServerFactorywebServerFactoryCustomizer(){returnfactory-{factory.set};}}packagecom.yangjunbo.springbootembedded.container.config;//import org.springframework.boot.tomcat.ConfigurableTomcatWebServerFactory;//import org.springframework.boot.web.server.ConfigurableWebServerFactory;//import org.springframework.boot.web.server.WebServerFactoryCustomizer;//import org.springframework.context.annotation.Bean;//import org.springframework.context.annotation.Configuration;/** * ClassName: WebServerConfigure * Package: com.yangjunbo.springbootembedded.container.config * Description: * * Author 杨钧博 * Create 2026/9/2 10:31 * Version 1.0 *///ConfigurationpublicclassWebServerConfigure{// Bean// public WebServerFactoryCustomizerConfigurableTomcatWebServerFactory webServerFactoryCustomizer() {// return factory - {// factory.set// };// }}请读者注意这个定制器中的一个概念WebServer。在 Spring Boot 的内部所有嵌入式 Web 容器统称为 WebServer而创建嵌入式 Web 容器的工厂被称为 WebServerFactory。如果读者想要更加深入地学习 Spring Boot 的嵌入式 Web 容器原理和机制就需要牢记这个概念在笔者的另一本著作《Spring Boot源码解读与原理分析》第 8 章中有关于 WebServer 的详细讲解感兴趣的读者可以查阅​。11.4 替换嵌入式容器Spring Boot 选择使用 Tomcat 作为嵌入式 Web 容器的默认实现。如果不想使用 Tomcat 运行应用还可以选择 Jetty 或 Undertow 取而代之。替换的方式非常简单只需要将 spring-boot-starter-web 依赖中默认的 spring-boot-starter-tomcat 依赖移除并添加 Jetty 或 Undertow 的依赖代码演示了替换为 Undertow 的方法。Spring Boot 4.0 起已经正式移除了对 Undertow 的支持。原因是 Undertow 未适配 Servlet 6.1 规范Spring Boot 4 强制依赖该规范。?xml version1.0 encodingUTF-8?projectxmlnshttp://maven.apache.org/POM/4.0.0xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersionparentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion4.1.1/versionrelativePath/!-- lookup parent from repository --/parentgroupIdcom.yangjunbo/groupIdartifactIdspringboot-embeddedcontainer-a/artifactIdversion0.0.1-SNAPSHOT/versionnamespringboot-embeddedcontainer-a/namedescriptionspringboot-embeddedcontainer-a/descriptionurl/licenseslicense//licensesdevelopersdeveloper//developersscmconnection/developerConnection/tag/url//scmpropertiesjava.version17/java.version/propertiesdependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-webmvc/artifactIdexclusionsexclusiongroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-tomcat/artifactId/exclusion/exclusions/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-jetty/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-webmvc-test/artifactIdscopetest/scope/dependency/dependenciesbuildpluginsplugingroupIdorg.springframework.boot/groupIdartifactIdspring-boot-maven-plugin/artifactId/plugin/plugins/build/project替换完毕后重新运行主启动类可以发现控制台中打印了 Jetty 容器的启动日志说明嵌入式 Web 容器已经被成功替换。11.5 SSL 配置目前为止使用的嵌入式 Web 容器提供的都是 HTTP 服务由于这种协议在数据传输时使用明文因此是一种不安全的协议。可以使用 HTTPS来取代 HTTP 进行相对安全的通信。HTTPS 可以理解为 HTTPSSL它利用 SSL 的加密机制使传输的数据被加密处理因此 HTTPS 被更多地应用于金融、电商等数据敏感等级较高的网站。Spring Boot 提供了非常方便的 SSL 配置方式通过 server.ssl.* 系列配置就可以快速实现。SSL 配置的前提是拥有一个 SSL 证书读者可以利用 JDK 提供的 keytool 工具生成一个本地测试用的证书也可以通过向专门的服务商如国内的云服务提供商阿里云、华为云、腾讯云等申请获得证书。下面演示使用 keytool 工具生成证书。打开一个命令行窗口输入命令 keytool -genkey -alias httpstest -keyalg RSA -storetype PKCS12 -keystore httpstest.p12 后按 Enter 键会看到命令行窗口提示输入密钥库口令按照提示依次输入口令、姓名、组织等信息后即可生成一个密钥整个过程都是交互式的操作起来比较方便。最终生成的证书文件会在当前命令行提示的文件夹中。keytool-genkey-aliashttpstest-keyalgRSA-storetypePKCS12-keystorehttpstest.p12使用 keytool 生成的证书仅适用于本地测试学习不建议将该证书应用于生产环境因为自行生成的证书存在诸多安全隐患容易被不法分子攻击。如果需要相对可靠的证书需要去云服务商申请。拿到证书文件 httpstest.p12 后接下来将其复制到工程的 resources 目录下并在 application.yaml 中配置 SSL 证书的信息即可。注意配置项 server.ssl.key-store-password 的内容就是生成SSL证书时填入的密钥库口令。spring:application:name:springboot-embeddedcontainer-aserver:port:8443ssl:key-store:classpath:httpstest.p12key-store-password:123456之后使用 Chrome 访问 https://localhost:8443会发现 Chrome 弹出了连接不安全的提示如果继续访问则浏览器的地址栏上会标注 “不安全”字样这印证了刚才说的自建 SSL 证书不建议用于生产环境。注意当配置 SSL 之后嵌入式 Web 容器将不再支持 HTTP 请求的访问如果需要同时支持 HTTP 和 HTTPS则需要单独添加相应的配置。代码提供了基于嵌入式 Tomcat 的 HTTP 与 HTTPS 兼顾方案它向 IOC 容器中注册了一个 TomcatServletWebServerFactory 组件这个组件可以让 Tomcat 多监听一个 8080 端口这样就可以同时使用 https://localhost:8443 访问 HTTPS 的端口使用 http://localhost:8080 访问 HTTP的端口。编写完毕后重启应用在控制台中可以看到同时支持两个协议的日志证明双协议支持已经完成。packagecom.yangjunbo.springbootembedded.container.config;//import org.springframework.boot.tomcat.ConfigurableTomcatWebServerFactory;//import org.springframework.boot.web.server.ConfigurableWebServerFactory;//import org.springframework.boot.web.server.WebServerFactoryCustomizer;//import org.springframework.context.annotation.Bean;//import org.springframework.context.annotation.Configuration;importorg.apache.catalina.connector.Connector;importorg.apache.catalina.startup.Tomcat;importorg.apache.coyote.http11.Http11NioProtocol;importorg.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory;importorg.springframework.boot.web.server.servlet.ServletWebServerFactory;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;/** * ClassName: WebServerConfigure * Package: com.yangjunbo.springbootembedded.container.config * Description: * * Author 杨钧博 * Create 2026/9/2 10:31 * Version 1.0 */ConfigurationpublicclassWebServerConfigure{// Bean// public WebServerFactoryCustomizerConfigurableTomcatWebServerFactory webServerFactoryCustomizer() {// return factory - {// factory.set// };// }BeanpublicServletWebServerFactoryhttpServletWebServerFactory(){TomcatServletWebServerFactoryfactorynewTomcatServletWebServerFactory();ConnectorconnectornewConnector(newHttp11NioProtocol());connector.setPort(8080);factory.addAdditionalConnectors(connector);returnfactory;}}11.6 小结嵌入式 Web 容器与普通的外置 Web 容器在运行效果上大致相同但也存在一些差异比如部署应用的限制、原生组件的限制等Spring Boot默认使用 Tomcat 作为嵌入式 Web 容器的实现同时还支持 Jetty、Undertow 和响应式的 Netty (WebFlux)Spring Boot 同时提供了全局配置属性和定制器两种方式对嵌入式 Web 容器进行定制并可以根据引入的不同 Web 容器类型编写对应的配置项。