<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
添加如上 Web 的依賴,Spring Boot 就幫我們內置了 Servlet 容器,默認使用的是 Tomcat,同樣也支持修改,比如可以使用 jetty、Undertow 等。
因為內置了啟動容器,應用程序可以直接通過 Maven 命令將項目編譯成可執行的 jar 包,通過 java -jar 命令直接啟動,不需要再像以前一樣,打包成 War 包,然后部署在 Tomcat 中。
那么:你知道內置的 Tomcat 在 Spring Boot 中是怎么啟動的嗎?
從啟動入口分析
如果不知道從哪開始,那么至少應該知道 Spring Boot 其實運行的就是一個 main 方法,
本文環境:Spring Boot:2.2.2.RELEASE
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
我們點進這個 SpringApplication.run() 方法:
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class[]{primarySource}, args);
}
這列的 run() 方法返回的是 ConfigurableApplicationContext 對象,我們繼續跟蹤這個 run() 方法:
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
又套了一層,繼續點擊這個返回的 run() 方法:
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
/*
* 1、配置屬性
* 設置系統屬性 java.awt.headless,為 true 則啟用headless模式
* headless模式是應用的一種配置模式,在服務器缺少顯示設備、鍵盤、鼠標等外設的情況下可以使用該模式
* 比如我們使用的Linux服務器就是缺少前述的這些設備,但是又需要使用這些設備提供的能力
*/
configureHeadlessProperty();
/*
* 2、獲取監聽器,發布應用開始啟動事件
* 通過SpringFactoriesLoader檢索META-INF/spring.factories,
* 找到聲明的所有SpringApplicationRunListener的實現類並將其實例化,
* 之后逐個調用其started()方法,廣播SpringBoot要開始執行了
*/
SpringApplicationRunListeners listeners = getRunListeners(args);
/* 發布應用開始啟動事件 */
listeners.starting();
try {
/* 3、初始化參數 */
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
/*
* 4、配置環境,輸出banner
* 創建並配置當前SpringBoot應用將要使用的Environment(包括配置要使用的PropertySource以及Profile),
* 並遍歷調用所有的SpringApplicationRunListener的environmentPrepared()方法,廣播Environment准備完畢。
*/
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
/* 打印banner,如果在resources目錄下創建了我們自己的banner就會進行打印,否則默認使用spring的 */
Banner printedBanner = printBanner(environment);
/* 5、創建應用上下文 */
context = createApplicationContext();
/* 通過SpringFactoriesLoader檢索META-INF/spring.factories,獲取並實例化異常分析器。 */
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
/*
* 6、預處理上下文
* 為ApplicationContext加載environment,之后逐個執行ApplicationContextInitializer的initialize()方法來進一步封裝ApplicationContext,
* 並調用所有的SpringApplicationRunListener的contextPrepared()方法,【EventPublishingRunListener只提供了一個空的contextPrepared()方法】,
* 之后初始化IoC容器,並調用SpringApplicationRunListener的contextLoaded()方法,廣播ApplicationContext的IoC加載完成,
* 這里就包括通過@EnableAutoConfiguration導入的各種自動配置類。
*/
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
/* 7、刷新上下文 */
refreshContext(context);
/* 8、再一次刷新上下文,其實是空方法,可能是為了后續擴展。 */
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
/* 9、發布應用已經啟動的事件 */
listeners.started(context);
/*
* 遍歷所有注冊的ApplicationRunner和CommandLineRunner,並執行其run()方法。
* 我們可以實現自己的ApplicationRunner或者CommandLineRunner,來對SpringBoot的啟動過程進行擴展。
*/
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
/* 10、發布應用已經啟動完成的監聽事件 */
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
如果覺得這個方法看的雲里霧里的,那么可以概括為如下幾步:
- 配置系統屬性
- 獲取監聽,發布應用開始啟動時間
- 初始化輸入參數
- 配置環境,輸出banner
- 創建上下文
- 預處理上下文
- 刷新上下文
- 再次刷新上下文
- 發布應用已經啟動事件
- 發布應用啟動完成事件
而我們 Tomcat 的啟動主要是在第5步創建上下文,以及第 7步刷新上下文實現的。
創建上下文
第5步中,創建上下文主要是調用的 createApplicationContext() 方法:
protected ConfigurableApplicationContext createApplicationContext() {
/** 1. 根據Web應用類型,獲取對應的ApplicationContext子類 **/
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch(this.webApplicationType) {
case SERVLET:
contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
break;
case REACTIVE:
contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
break;
default:
contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
}
} catch (ClassNotFoundException var3) {
throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
}
}
/** 2. 實例化子類 **/
return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
}
代碼中主要就是通過 switch 語句,根據 webApplicationType 的類型來創建不同的 ApplicationContext:
- SERVLET:Web類型,實例化 AnnotationConfigServletWebServerApplicationContext
- REACTIVE:響應式Web類型,實例化 AnnotationConfigReactiveWebServerApplicationContext
- default:非Web類型,實例化 AnnotationConfigApplicationContext
因為我們的應用是 Web 類型,所以實例化的是 AnnotationConfigServletWebServerApplicationContext,如下是該類的關系圖(由Diagram截圖):

我們在上圖的底部觸發,可以看到 AnnotationConfigServletWebServerApplicationContext > ServletWebServerApplicationContext > ... AbstractApplicationContext(>表示繼承),總之,最終繼承到了 AbstractApplicationContext,這個類是 ApplicationContext 的抽象實現類,該抽象類實現應用上下文的一些具體操作。
至此,並沒有看到 Tomcat 的相關代碼,其實這一步主要就是「創建上下文」,拿到「上下文」之后需要傳遞給「刷新上下文」,交由刷新上下文創建 Web 服務。
刷新上下文
第7步中,刷新上下文時調用的 refreshContext(context) 方法,其中 context 就是第5步創建的上下文,方法如下:
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext) applicationContext).refresh();
}
refreshContext() 方法傳遞的 context,經由 refresh() 方法強轉成父類 AbstractApplicationContext,具體調用過程如下:
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
this.prepareRefresh();
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
this.prepareBeanFactory(beanFactory);
try {
this.postProcessBeanFactory(beanFactory);
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
this.initMessageSource();
this.initApplicationEventMulticaster();
/** 主要關系 onRefresh() 方法 ------------- **/
this.onRefresh();
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var9) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
}
this.destroyBeans();
this.cancelRefresh(var9);
throw var9;
} finally {
this.resetCommonCaches();
}
}
}
在這個方法中我們主要關心 onRefresh() 方法,onRefresh() 方法是調用其子類實現的,也就是 ServletWebServerApplicationContext,

如下是子類的 onRefresh() 方法:
protected void onRefresh() {
super.onRefresh();
try {
this.createWebServer();
} catch (Throwable var2) {
throw new ApplicationContextException("Unable to start web server", var2);
}
}
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = this.getServletContext();
if (webServer == null && servletContext == null) {
/** 得到Servlet工廠 **/
ServletWebServerFactory factory = this.getWebServerFactory();
this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});
} else if (servletContext != null) {
try {
this.getSelfInitializer().onStartup(servletContext);
} catch (ServletException var4) {
throw new ApplicationContextException("Cannot initialize servlet context", var4);
}
}
this.initPropertySources();
}
其中 createWebServer() 方法是用來啟動web服務的,但是還沒有真正啟動 Tomcat,只是通過ServletWebServerFactory 創建了一個 WebServer,我們繼續來看這個 ServletWebServerFactory:

ServletWebServerFactory 有4個實現類,其中我們最常用的是 TomcatServletWebServerFactory和JettyServletWebServerFactory,而默認的 Web 環境就是 TomcatServletWebServerFactory。
而到這總算是看到 Tomcat 相關的字眼了。
來看一下 TomcatServletWebServerFactory 的 getWebServer() 方法:
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
/** 1、創建Tomcat實例 **/
Tomcat tomcat = new Tomcat();
File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
tomcat.getService().addConnector(connector);
this.customizeConnector(connector);
/** 2、給創建好的tomcat設置連接器connector **/
tomcat.setConnector(connector);
/** 設置不自動部署 **/
tomcat.getHost().setAutoDeploy(false);
/** 3、配置Tomcat容器引擎 **/
this.configureEngine(tomcat.getEngine());
Iterator var5 = this.additionalTomcatConnectors.iterator();
while(var5.hasNext()) {
Connector additionalConnector = (Connector)var5.next();
tomcat.getService().addConnector(additionalConnector);
}
/**
* 准備Tomcat的StandardContext,並添加到Tomcat中,同時把initializers 注冊到類型為
* TomcatStarter的ServletContainerInitializer中
**/
this.prepareContext(tomcat.getHost(), initializers);
/** 將創建好的Tomcat包裝成WebServer返回**/
return this.getTomcatWebServer(tomcat);
}
public Engine getEngine() {
Service service = this.getServer().findServices()[0];
if (service.getContainer() != null) {
return service.getContainer();
} else {
Engine engine = new StandardEngine();
engine.setName("Tomcat");
engine.setDefaultHost(this.hostname);
engine.setRealm(this.createDefaultRealm());
service.setContainer(engine);
return engine;
}
}
getWebServer() 這個方法創建了 Tomcat 對象,並且做了兩件重要的事情:
- 把連接器 Connector 對象添加到 Tomcat 中;
- 配置容器引擎,configureEngine(tomcat.getEngine());
首先說一下這個 Connector 連接器,Tomcat 有兩個核心功能:
- 處理 Socket 連接,負責網絡字節流與 Request 和 Response 對象的轉化。
- 加載和管理 Servlet,以及具體處理 Request 請求。
針對這兩個功能,Tomcat 設計了兩個核心組件來分別完成這兩件事,即:連接器(Connector)和容器(Container)。
整個過程大致就是:Connector 連接器接收連接請求,創建Request和Response對象用於和請求端交換數據,然后分配線程讓Engine(也就是Servlet容器)來處理這個請求,並把產生的Request和Response對象傳給Engine。當Engine處理完請求后,也會通過Connector將響應返回給客戶端。
這里面提到了 Engine,這個是 Tomcat 容器里的頂級容器(Container),我們可以通過 Container 類查看其他的子容器:Engine、Host、Context、Wrapper

4者的關系是:Engine 是最高級別的容器,Engine 子容器是 Host,Host 的子容器是 Context,Context 子容器是 Wrapper,所以這4個容器的關系就是父子關系,即:Wrapper > Context > Host > Engine (>表示繼承)
至此我們了解了 Engine 這個就是個容器,然后我們再看一下這個 configureEngine(tomcat.getEngine()) 具體干了啥:
private void configureEngine(Engine engine) {
engine.setBackgroundProcessorDelay(this.backgroundProcessorDelay);
Iterator var2 = this.engineValves.iterator();
while(var2.hasNext()) {
Valve valve = (Valve)var2.next();
engine.getPipeline().addValve(valve);
}
}
其中 engine.setBackgroundProcessorDelay(this.backgroundProcessorDelay) 是指定背景線程的執行間隔,例如背景線程會在每隔多長時間后判斷session是否失效之類。
再回到 getWebServer() 方法,最終 getWebServer() 方法返回了 TomcatWebServer。
return this.getTomcatWebServer(tomcat);
通過 getTomcatWebServer() 方法,繼續下沉:
/**
* 構造函數實例化 TomcatWebServer
**/
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
this.monitor = new Object();
this.serviceConnectors = new HashMap();
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
this.initialize();
}
private void initialize() throws WebServerException {
/** 我們在啟動 Spring Boot 時經常看到打印這句話 **/
logger.info("Tomcat initialized with port(s): " + this.getPortsDescription(false));
synchronized(this.monitor) {
try {
this.addInstanceIdToEngineName();
Context context = this.findContext();
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource()) && "start".equals(event.getType())) {
this.removeServiceConnectors();
}
});
/** 啟動 tomcat **/
this.tomcat.start();
this.rethrowDeferredStartupExceptions();
try {
ContextBindings.bindClassLoader(context, context.getNamingToken(), this.getClass().getClassLoader());
} catch (NamingException var5) {
}
this.startDaemonAwaitThread();
} catch (Exception var6) {
this.stopSilently();
this.destroySilently();
throw new WebServerException("Unable to start embedded Tomcat", var6);
}
}
}
至此,Tomcat 啟動過程就很清晰了,總結一下。
總結
SpringBoot的啟動主要是通過實例化SpringApplication來啟動的,啟動過程主要做了如下幾件事情:
- 配置系統屬性
- 獲取監聽,發布應用開始啟動時間
- 初始化輸入參數
- 配置環境,輸出banner
- 創建上下文
- 預處理上下文
- 刷新上下文
- 再次刷新上下文
- 發布應用已經啟動事件
- 發布應用啟動完成事件
而啟動 Tomcat 是在第7步 刷新上下文 這一步。
從整個流轉過程中我們知道了 Tomcat 的啟動主要是實例化兩個組件:Connector、Container。
-
Spring Boot 創建 Tomcat 時,會先創建一個根上下文,將 WebApplicationContext 傳給 Tomcat;
-
啟動 Web 容器,需要調用 getWebserver(),因為默認的 Web 環境就是 TomcatServletWebServerFactory,所以會創建 Tomcat 的 Webserver,這里會把根上下文作為參數給 TomcatServletWebServerFactory 的 getWebServer();
-
啟動 Tomcat,調用 Tomcat 中 Host、Engine 的啟動方法。
