SpringBoot集成tomcat詳解實現(xiàn)過程
spring boot 支持目前主流的 servlet 容器,包括 tomcat、jetty、undertow,可以在我們的項目中方便地集成這些 servlet 容器,減少了開發(fā)、運維的工作量。而傳統(tǒng)的應(yīng)用開發(fā),需要經(jīng)過繁鎖的操作步驟:安裝 tomcat –> 修改 tomcat 配置 –> 部署 war 包 –> 啟動 tomcat –> 運維……,這個工作量不小,尤其是集群部署、應(yīng)用遷移的時候。而采用 spring boot 之后,一切變得如此簡單,打包 –> java -jar –> 運維,只需要一個 jar 包便可以隨意部署安裝。這篇文章,將對 spring boot 集成 tomcat 的源碼進行分析,探索其內(nèi)部的原理
SPI
在分析源碼前,我們先來了解下 spring 的 SPI 機制。我們知道,jdk 為了方便應(yīng)用程序進行擴展,提供了默認的 SPI 實現(xiàn)(ServiceLoader),dubbo 也有自己的 SPI。spring 也是如此,他為我們提供了SpringFactoriesLoader,允許開發(fā)人員通過META-INF/spring.factories文件進行擴展,下面舉一個例子方便理解
假如,我想要往 spring 容器中添加一個ApplicationContextInitializer做一些初始化工作,我們可以借助 spring 提供的這個 SPI 功能完成這個需求。
首先,在項目中創(chuàng)建META-INF/spring.factories文件,文件內(nèi)容如下所示:
org.springframework.context.ApplicationContextInitializer=\
我們再寫個 test case,便可以通過 SPI 的方式獲取我們定義的ApplicationContextInitializer??此坪芎唵蔚囊粋€功能,但是 spring boot 正是利用這個強大的擴展點,在 spring framework 的基礎(chǔ)上為我們集成了常用的開源框架
@Test
public void testSpringSpi() {
List<ApplicationListener> listeners = SpringFactoriesLoader.loadFactories( ApplicationListener.class,
ClassUtils.getDefaultClassLoader() );
System.out.println( listeners );我們再來看看這個SpringFactoriesLoader,關(guān)鍵代碼如下所示,它通過讀取META-INF/spring.factories文件,并且查找方法參數(shù)指定的 class,然后創(chuàng)建對應(yīng)的實例對象,并且返回。此外,還支持排序,可以使用以下幾種方式進行排序
- org.springframework.core.Ordered:實現(xiàn)該接口
- org.springframework.core.annotation.Order:注解
- javax.annotation.Priority:注解
public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) {
List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
List<T> result = new ArrayList<T>(factoryNames.size());
for (String factoryName : factoryNames) {
result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
}
AnnotationAwareOrderComparator.sort(result);
return result;接下來,我們來分析下 spring boot 是如何利用 SPI 機制集成 tomcat
SpringBoot for Tomcat
在分析 tomcat 集成的源碼之前,我們先來了解下 EmbeddedServletContainer
EmbeddedServletContainer:
spring 用EmbeddedServletContainer封裝了內(nèi)嵌的 servlet 容器,提供了start、stop等接口用于控制容器的生命周期,并且 spring 內(nèi)置了 tomcat、jetty、undertow 容器的實現(xiàn),類圖所下所示
我們再來看看 spring boot 中最常用的SpringBootApplication注解,原來是多個注解的綜合體,而這個EnableAutoConfiguration便是 spring boot 用做自動化配置的注解
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
// code......我們在spring-boot-autoconfigure模塊可以看到大量的 SPI 配置,部分如下所示
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration,\
原來EnableAutoConfiguration注解引入了EmbeddedServletContainerAutoConfiguration,而這個便是內(nèi)嵌 servlet 容器的配置類,tomcat、jetty、undertow 都在這個類上面,通過@ConditionalOnClass注解加載不同的 servlet 容器。但是,這個類僅僅是注冊了TomcatEmbeddedServletContainerFactory,不足以幫助我們解除所有的困惑。不要急,我們先來看看TomcatEmbeddedServletContainerFactory的類圖。
由上面的類圖可知,它實現(xiàn)了以下接口:
- EmbeddedServletContainerFactory:它是一個工廠模式,用于創(chuàng)建
EmbeddedServletContainer,即用于創(chuàng)建一個內(nèi)嵌的 Servlet 容器,這個接口里面只有一個getEmbeddedServletContainer方法 - ConfigurableEmbeddedServletContainer:用于配置
EmbeddedServletContainer,比如說端口、上下文路徑等
分析了上面兩個接口,原來創(chuàng)建 servlet 容器的工作是由EmbeddedServletContainerFactory完成的,看下getEmbeddedServletContainer方法的調(diào)用棧。在EmbeddedWebApplicationContext中重寫了GenericWebApplicationContext#onRefresh()方法,并且調(diào)用getEmbeddedServletContainer方法創(chuàng)建 servlet 容器,我們接下來分析這個創(chuàng)建過程。
關(guān)鍵代碼如下(省略異常處理):
EmbeddedWebApplicationContext.java
@Override
protected void onRefresh() {
super.onRefresh();
createEmbeddedServletContainer();
}
private void createEmbeddedServletContainer() {
EmbeddedServletContainer localContainer = this.embeddedServletContainer;
ServletContext localServletContext = getServletContext();
if (localContainer == null && localServletContext == null) {
// 從容器中獲取bean,如果使用tomcat則返回TomcatEmbeddedServletContainerFactory
EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(getSelfInitializer());
}
else if (localServletContext != null) {
getSelfInitializer().onStartup(localServletContext);
}
initPropertySources();我們先畫出主要的流程圖

由上圖可知,EmbeddedWebApplicationContext在執(zhí)行onRefresh方法的時候,首先調(diào)用父類的onRefresh,然后從容器中獲取EmbeddedServletContainerFactory的實現(xiàn)類。由于我們在 classpath 下面可以獲取 tomcat 的 jar 包,因此EmbeddedServletContainerAutoConfiguration會在 spring 容器中注冊TomcatEmbeddedServletContainerFactory這個 bean。然后,由它創(chuàng)建TomcatEmbeddedServletContainer,我們來看看具體的創(chuàng)建過程,代碼如下所示:
TomcatEmbeddedServletContainerFactory.java
@Override
public EmbeddedServletContainer getEmbeddedServletContainer(
ServletContextInitializer... initializers) {
Tomcat tomcat = new Tomcat(); // 實例化 apache Tomcat
File baseDir = (this.baseDirectory != null ? this.baseDirectory
: createTempDir("tomcat"));
tomcat.setBaseDir(baseDir.getAbsolutePath());
// 創(chuàng)建 Connector 組件,默認使用org.apache.coyote.http11.Http11NioProtocol
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
// 支持對 Connector 進行自定義設(shè)置,比如設(shè)置線程池、最大連接數(shù)等
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 getTomcatEmbeddedServletContainer(tomcat);首先是實例化Tomcat對象,然后創(chuàng)建Connector組件,并且對Connector進行相關(guān)的參數(shù)設(shè)置,同時也允許我們通過TomcatConnectorCustomizer接口進行自定義的設(shè)置。OK,創(chuàng)建了Tomcat實例之后,需要創(chuàng)建TomcatEmbeddedServletContainer,它依賴Tomcat對象,在構(gòu)造方法中便會啟動 Tomcat 容器,從而完成各個組件的啟動流程
public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
initialize();
}
private void initialize() throws EmbeddedServletContainerException {
synchronized (this.monitor) {
addInstanceIdToEngineName();
// Remove service connectors to that protocol binding doesn't happen yet
removeServiceConnectors();
// Start the server to trigger initialization listeners
this.tomcat.start();
// We can re-throw failure exception directly in the main thread
rethrowDeferredStartupExceptions();
Context context = findContext();
ContextBindings.bindClassLoader(context, getNamingToken(context),
getClass().getClassLoader());
// Unlike Jetty, all Tomcat threads are daemon threads. We create a
// blocking non-daemon to stop immediate shutdown
startDaemonAwaitThread();
}到此這篇關(guān)于SpringBoot集成tomcat詳解實現(xiàn)過程的文章就介紹到這了,更多相關(guān)SpringBoot集成tomcat內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用RocketMQTemplate發(fā)送帶tags的消息
這篇文章主要介紹了使用RocketMQTemplate發(fā)送帶tags的消息,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07
java Person,Student,GoodStudent 三個類的繼承、構(gòu)造函數(shù)的執(zhí)行
這篇文章主要介紹了java Person,Student,GoodStudent 三個類的繼承、構(gòu)造函數(shù)的執(zhí)行,需要的朋友可以參考下2017-02-02
Java面試之動態(tài)規(guī)劃與組合數(shù)
這篇文章主要介紹了Java面試之動態(tài)規(guī)劃與組合數(shù)的相關(guān)知識,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2019-09-09
springboot2.0如何通過fastdfs實現(xiàn)文件分布式上傳
這篇文章主要介紹了springboot2.0如何通過fastdfs實現(xiàn)文件分布式上傳,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-12-12
Springboot獲取前端反饋信息并存入數(shù)據(jù)庫的實現(xiàn)代碼
這篇文章主要介紹了Springboot獲取前端反饋信息并存入數(shù)據(jù)庫的實現(xiàn)代碼,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03
IDEA2020 Plugins不能用的解決辦法及Plugins 搜索不了插件的問題
這篇文章主要介紹了IDEA2020 Plugins不能用的解決辦法,文中給大家介紹了Intellij IDEA 2020.1 的Plugins 搜索不了插件,連接超時的問題,本文給大家介紹的非常詳細,需要的朋友可以參考下2020-06-06
詳解SpringBoot+Thymeleaf 基于HTML5的現(xiàn)代模板引擎
本篇文章主要介紹了SpringBoot+Thymeleaf 基于HTML5的現(xiàn)代模板引擎,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-10-10

