與應(yīng)用實(shí)戰(zhàn)》第 11 章 嵌入式容器 閱讀筆記 29)
《SpringBoot 3入門(mén)與應(yīng)用實(shí)戰(zhàn)》第 11 章 嵌入式容器 閱讀筆記 29對(duì)于傳統(tǒng)的 Web 應(yīng)用而言在開(kāi)發(fā)完畢后需要將工程打包成 war 包部署到獨(dú)立的 Web 容器中如 Tomcat、Jetty、Undertow 等運(yùn)行工程時(shí)也需要啟動(dòng)獨(dú)立的 Web 容器。而在前面兩章的學(xué)習(xí)中自始至終都沒(méi)有將 Spring Boot 應(yīng)用部署到外置的 Web 容器中這背后的功臣是 Spring Boot 整合的嵌入式 Web 容器。有了嵌入式 Web 容器啟動(dòng) Spring Boot 的Web應(yīng)用時(shí)便可以獨(dú)立啟動(dòng)而不再依賴任何外置 Web 容器。本章主要研究的就是 Spring Boot 內(nèi)部集成的嵌入式容器整體來(lái)講難度不大以熟悉為主。11.1 Web 容器對(duì)比首先對(duì)外置 Web 容器與嵌入式 Web 容器進(jìn)行簡(jiǎn)單了解和對(duì)比。僅從名稱上也能分辨出外置 Web 容器是作為操作系統(tǒng)中一個(gè)可執(zhí)行程序出現(xiàn)的而嵌入式 Web 容器則會(huì)嵌入應(yīng)用程序中成為其一部分整合嵌入式Web容器的應(yīng)用不借助外部容器和資源即可獨(dú)立啟動(dòng) Web 應(yīng)用。也正因如此嵌入式 Web 容器支持成為 Spring Boot 最強(qiáng)大的特性之一。以 Tomcat 為例普通的外置 Tomcat 與嵌入式 Tomcat 從核心、本質(zhì)上看沒(méi)有任何區(qū)別它們都可以承載 Web 應(yīng)用的運(yùn)行但是 Spring Boot整合嵌入式 Tomcat 容器時(shí)考慮了一些特殊的因素它會(huì)在底層設(shè)定一些額外的限制需要讀者簡(jiǎn)單了解。部署應(yīng)用的限制。由于嵌入式 Tomcat 不是獨(dú)立的 Web 容器它需要嵌入特定應(yīng)用中該特性使得嵌入式 Tomcat 只能一次部署一個(gè) Web 應(yīng)用。web.xml 的限制。Spring Boot 整合嵌入式 Tomcat 后不再對(duì) web.xml 文件予以支持轉(zhuǎn)而使用 Bean 配合 ServletRegistrationBean 實(shí)現(xiàn)Servlet、Filter、Listener 的編程式注冊(cè)對(duì)應(yīng) 10.3.6 節(jié)中講解的內(nèi)容?。原生 Servlet 三大組件的限制。原生的基于 Servlet 3.0 及以上規(guī)范的 Web 項(xiàng)目其類路徑下的 Servlet、Filter、Listener 可以被自動(dòng)掃描并注冊(cè)而 Spring Boot 整合嵌入式 Tomcat 后該特性會(huì)消失如果需要開(kāi)啟此特性需要配合 ServletComponentScan 使用對(duì)應(yīng) 10.3.6 節(jié)中講解的內(nèi)容?。JSP 的限制。Spring Boot 整合嵌入式 Tomcat 后如果以獨(dú)立 jar 包的方式啟動(dòng)項(xiàng)目則項(xiàng)目中編寫(xiě)的 JSP 會(huì)失效如果以 war 包的方式部署到外置 Tomcat 容器則 JSP 可以正常運(yùn)行出現(xiàn)該現(xiàn)象的原因是嵌入式 Tomcat 沒(méi)有引入 JSP 引擎依賴?。11.2 使用嵌入式 Tomcat在默認(rèn)情況下當(dāng) Spring Boot 引入 WebMvc 依賴時(shí)會(huì)自動(dòng)將嵌入式 Tomcat 引入工程可以借助 IDEA 的 Maven 依賴工具查看??梢钥吹絊pring Boot 4.1.1 版本中依賴的 Tomcat 版本為 11.0.24??梢钥吹?3 個(gè)嵌入式 Tomcat 的依賴包分別是核心包 core、支持 EL 表達(dá)式的 el 包以及支持 websocket 的包。對(duì) Spring Boot 引入嵌入式Tomcat 而言這 3 個(gè)依賴包已經(jīng)完全足夠。從控制臺(tái)的輸出中也能看出引入的 Tomcat 版本并且默認(rèn)運(yùn)行在 8080 端口。仔細(xì)觀察日志的內(nèi)容還可以發(fā)現(xiàn)其實(shí)日志中已經(jīng)體現(xiàn)出使用的 ApplicationContext 也適配了嵌入式 Web 容器的實(shí)現(xiàn)第6行?。11.3 定制嵌入式容器默認(rèn)情況下雖然使用嵌入式 Web 容器后不需要做什么配置就可以用于開(kāi)發(fā)但置于真實(shí)的生產(chǎn)環(huán)境中時(shí)還是需要一些配置比如端口的配置、SSL 證書(shū)的配置等。Spring Boot 提供了兩種定制嵌入式 Web 容器的方式下面分別講解。11.3.1 修改配置屬性Spring Boot 提供的全局配置文件針對(duì)嵌入式 Web 容器已經(jīng)提供了非常多的配置屬性直接在 application.properties 中配置即可它們大多以server.* 格式為主代碼展示了部分配置屬性的設(shè)置包括端口設(shè)置、上下文路徑修改等還有針對(duì) Tomcat 單獨(dú)設(shè)置的配置屬性包括 Tomcat的工作線程數(shù)量、最大連接數(shù)、靜態(tài)資源緩存等。server.port9090 server.servlet.context-path/embedded server.servlet.session.timeout60m# 最小工作線程數(shù)量默認(rèn)10server.tomcat.threads.min-spare20# 最大工作線程數(shù)量默認(rèn)200server.tomcat.threads.max500# 日志保存的最大日期默認(rèn)-1代表永久保存server.tomcat.accesslog.max-days15# form表單最大允許提交的大小默認(rèn)2server.tomcat.max-http-form-post-size2MB# 最大連接數(shù)默認(rèn)8192server.tomcat.max-connections8192# 是否開(kāi)啟靜態(tài)資源緩存默認(rèn)開(kāi)啟server.tomcat.resource.allow-cachingtrue# 靜態(tài)資源的緩存過(guò)期時(shí)間默認(rèn)5秒server.tomcat.resource.cache-ttl30m值得關(guān)注的一個(gè)配置屬性是 server.port通過(guò)這個(gè)屬性可以修改嵌入式 Web 容器的端口號(hào)如果不做任何配置那么 Web 容器會(huì)默認(rèn)運(yùn)行在8080 端口上。通常來(lái)講不推薦將端口號(hào)設(shè)置為 1024 以內(nèi)的數(shù)值而將端口設(shè)置為 0 時(shí)會(huì)隨機(jī)產(chǎn)生一個(gè)與當(dāng)前系統(tǒng)不沖突的端口號(hào)作為 Web容器的監(jiān)聽(tīng)端口。11.3.2 使用定制器如果 Spring Boot 提供的配置屬性不足以滿足訴求還可以使用 Spring Boot 提供的另一種方式定制器。如果說(shuō)配置屬性的方式屬于聲明式配置那么定制器的方式就可以稱作編程式配置。相較于使用配置屬性而言使用定制器的特點(diǎn)是更加靈活但同時(shí)也增加了代碼量。下面用一個(gè)簡(jiǎn)單示例來(lái)演示定制器的使用對(duì)嵌入式 Web 容器而言使用的定制器是一個(gè)固定的接口 WebServerFactoryCustomizer它需要指定一個(gè)定制的泛型關(guān)于這個(gè)泛型可以選擇與具體容器無(wú)關(guān)的 ConfigurableWebServerFactory也可以是當(dāng)前工程中引入的某個(gè)嵌入式容器的派生子類如 Tomcat 對(duì)應(yīng)的派生類是 ConfigurableTomcatWebServerFactory?。如果指定的泛型是 ConfigurableWebServerFactory那么在定制器的內(nèi)部中調(diào)用的方法都是嵌入式 Web 容器共有的屬性如端口號(hào)、SSL 配置、HTTP2 配置等而如果指定的是具體派生類如ConfigurableTomcatWebServerFactory那么可以調(diào)用更多的與 Tomcat 相關(guān)的方法以供更加細(xì)致地定制嵌入式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// };// }}請(qǐng)讀者注意這個(gè)定制器中的一個(gè)概念WebServer。在 Spring Boot 的內(nèi)部所有嵌入式 Web 容器統(tǒng)稱為 WebServer而創(chuàng)建嵌入式 Web 容器的工廠被稱為 WebServerFactory。如果讀者想要更加深入地學(xué)習(xí) Spring Boot 的嵌入式 Web 容器原理和機(jī)制就需要牢記這個(gè)概念在筆者的另一本著作《Spring Boot源碼解讀與原理分析》第 8 章中有關(guān)于 WebServer 的詳細(xì)講解感興趣的讀者可以查閱?。11.4 替換嵌入式容器Spring Boot 選擇使用 Tomcat 作為嵌入式 Web 容器的默認(rèn)實(shí)現(xiàn)。如果不想使用 Tomcat 運(yùn)行應(yīng)用還可以選擇 Jetty 或 Undertow 取而代之。替換的方式非常簡(jiǎn)單只需要將 spring-boot-starter-web 依賴中默認(rèn)的 spring-boot-starter-tomcat 依賴移除并添加 Jetty 或 Undertow 的依賴代碼演示了替換為 Undertow 的方法。Spring Boot 4.0 起已經(jīng)正式移除了對(duì) Undertow 的支持。原因是 Undertow 未適配 Servlet 6.1 規(guī)范Spring Boot 4 強(qiáng)制依賴該規(guī)范。?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替換完畢后重新運(yùn)行主啟動(dòng)類可以發(fā)現(xiàn)控制臺(tái)中打印了 Jetty 容器的啟動(dòng)日志說(shuō)明嵌入式 Web 容器已經(jīng)被成功替換。11.5 SSL 配置目前為止使用的嵌入式 Web 容器提供的都是 HTTP 服務(wù)由于這種協(xié)議在數(shù)據(jù)傳輸時(shí)使用明文因此是一種不安全的協(xié)議??梢允褂?HTTPS來(lái)取代 HTTP 進(jìn)行相對(duì)安全的通信。HTTPS 可以理解為 HTTPSSL它利用 SSL 的加密機(jī)制使傳輸?shù)臄?shù)據(jù)被加密處理因此 HTTPS 被更多地應(yīng)用于金融、電商等數(shù)據(jù)敏感等級(jí)較高的網(wǎng)站。Spring Boot 提供了非常方便的 SSL 配置方式通過(guò) server.ssl.* 系列配置就可以快速實(shí)現(xiàn)。SSL 配置的前提是擁有一個(gè) SSL 證書(shū)讀者可以利用 JDK 提供的 keytool 工具生成一個(gè)本地測(cè)試用的證書(shū)也可以通過(guò)向?qū)iT(mén)的服務(wù)商如國(guó)內(nèi)的云服務(wù)提供商阿里云、華為云、騰訊云等申請(qǐng)獲得證書(shū)。下面演示使用 keytool 工具生成證書(shū)。打開(kāi)一個(gè)命令行窗口輸入命令 keytool -genkey -alias httpstest -keyalg RSA -storetype PKCS12 -keystore httpstest.p12 后按 Enter 鍵會(huì)看到命令行窗口提示輸入密鑰庫(kù)口令按照提示依次輸入口令、姓名、組織等信息后即可生成一個(gè)密鑰整個(gè)過(guò)程都是交互式的操作起來(lái)比較方便。最終生成的證書(shū)文件會(huì)在當(dāng)前命令行提示的文件夾中。keytool-genkey-aliashttpstest-keyalgRSA-storetypePKCS12-keystorehttpstest.p12使用 keytool 生成的證書(shū)僅適用于本地測(cè)試學(xué)習(xí)不建議將該證書(shū)應(yīng)用于生產(chǎn)環(huán)境因?yàn)樽孕猩傻淖C書(shū)存在諸多安全隱患容易被不法分子攻擊。如果需要相對(duì)可靠的證書(shū)需要去云服務(wù)商申請(qǐng)。拿到證書(shū)文件 httpstest.p12 后接下來(lái)將其復(fù)制到工程的 resources 目錄下并在 application.yaml 中配置 SSL 證書(shū)的信息即可。注意配置項(xiàng) server.ssl.key-store-password 的內(nèi)容就是生成SSL證書(shū)時(shí)填入的密鑰庫(kù)口令。spring:application:name:springboot-embeddedcontainer-aserver:port:8443ssl:key-store:classpath:httpstest.p12key-store-password:123456之后使用 Chrome 訪問(wèn) https://localhost:8443會(huì)發(fā)現(xiàn) Chrome 彈出了連接不安全的提示如果繼續(xù)訪問(wèn)則瀏覽器的地址欄上會(huì)標(biāo)注 “不安全”字樣這印證了剛才說(shuō)的自建 SSL 證書(shū)不建議用于生產(chǎn)環(huán)境。注意當(dāng)配置 SSL 之后嵌入式 Web 容器將不再支持 HTTP 請(qǐng)求的訪問(wèn)如果需要同時(shí)支持 HTTP 和 HTTPS則需要單獨(dú)添加相應(yīng)的配置。代碼提供了基于嵌入式 Tomcat 的 HTTP 與 HTTPS 兼顧方案它向 IOC 容器中注冊(cè)了一個(gè) TomcatServletWebServerFactory 組件這個(gè)組件可以讓 Tomcat 多監(jiān)聽(tīng)一個(gè) 8080 端口這樣就可以同時(shí)使用 https://localhost:8443 訪問(wèn) HTTPS 的端口使用 http://localhost:8080 訪問(wèn) HTTP的端口。編寫(xiě)完畢后重啟應(yīng)用在控制臺(tái)中可以看到同時(shí)支持兩個(gè)協(xié)議的日志證明雙協(xié)議支持已經(jīng)完成。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 小結(jié)嵌入式 Web 容器與普通的外置 Web 容器在運(yùn)行效果上大致相同但也存在一些差異比如部署應(yīng)用的限制、原生組件的限制等Spring Boot默認(rèn)使用 Tomcat 作為嵌入式 Web 容器的實(shí)現(xiàn)同時(shí)還支持 Jetty、Undertow 和響應(yīng)式的 Netty (WebFlux)Spring Boot 同時(shí)提供了全局配置屬性和定制器兩種方式對(duì)嵌入式 Web 容器進(jìn)行定制并可以根據(jù)引入的不同 Web 容器類型編寫(xiě)對(duì)應(yīng)的配置項(xiàng)。