<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
添加如上 Web 的依賴,Spring Boot 就幫咱們內置了 Servlet 容器,默認使用的是 Tomcat,一樣也支持修改,好比可使用 jetty、Undertow 等。java
由於內置了啓動容器,應用程序能夠直接經過 Maven 命令將項目編譯成可執行的 jar 包,經過 java -jar 命令直接啓動,不須要再像之前同樣,打包成 War 包,而後部署在 Tomcat 中。react
那麼:你知道內置的 Tomcat 在 Spring Boot 中是怎麼啓動的嗎?web
若是不知道從哪開始,那麼至少應該知道 Spring Boot 其實運行的就是一個 main 方法,spring
本文環境:Spring Boot:2.2.2.RELEASEtomcat
@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() 方法:session
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
/*
* 一、配置屬性
* 設置系統屬性 java.awt.headless,爲 true 則啓用headless模式
* headless模式是應用的一種配置模式,在服務器缺乏顯示設備、鍵盤、鼠標等外設的狀況下可使用該模式
* 好比咱們使用的Linux服務器就是缺乏前述的這些設備,可是又須要使用這些設備提供的能力
*/
configureHeadlessProperty();
/*
* 二、獲取監聽器,發佈應用開始啓動事件
* 經過SpringFactoriesLoader檢索META-INF/spring.factories,
* 找到聲明的全部SpringApplicationRunListener的實現類並將其實例化,
* 以後逐個調用其started()方法,廣播SpringBoot要開始執行了
*/
SpringApplicationRunListeners listeners = getRunListeners(args);
/* 發佈應用開始啓動事件 */
listeners.starting();
try {
/* 三、初始化參數 */
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
/*
* 四、配置環境,輸出banner
* 建立並配置當前SpringBoot應用將要使用的Environment(包括配置要使用的PropertySource以及Profile),
* 並遍歷調用全部的SpringApplicationRunListener的environmentPrepared()方法,廣播Environment準備完畢。
*/
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
/* 打印banner,若是在resources目錄下建立了咱們本身的banner就會進行打印,不然默認使用spring的 */
Banner printedBanner = printBanner(environment);
/* 五、建立應用上下文 */
context = createApplicationContext();
/* 經過SpringFactoriesLoader檢索META-INF/spring.factories,獲取並實例化異常分析器。 */
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
/*
* 六、預處理上下文
* 爲ApplicationContext加載environment,以後逐個執行ApplicationContextInitializer的initialize()方法來進一步封裝ApplicationContext,
* 並調用全部的SpringApplicationRunListener的contextPrepared()方法,【EventPublishingRunListener只提供了一個空的contextPrepared()方法】,
* 以後初始化IoC容器,並調用SpringApplicationRunListener的contextLoaded()方法,廣播ApplicationContext的IoC加載完成,
* 這裏就包括經過@EnableAutoConfiguration導入的各類自動配置類。
*/
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
/* 七、刷新上下文 */
refreshContext(context);
/* 八、再一次刷新上下文,實際上是空方法,多是爲了後續擴展。 */
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
/* 九、發佈應用已經啓動的事件 */
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 {
/* 十、發佈應用已經啓動完成的監聽事件 */
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
若是以爲這個方法看的雲裏霧裏的,那麼能夠歸納爲以下幾步:app
而咱們 Tomcat 的啓動主要是在第5步建立上下文,以及第 7步刷新上下文實現的。less
第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:
由於咱們的應用是 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();
}
/** 一、建立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);
/** 二、給建立好的tomcat設置鏈接器connector **/
tomcat.setConnector(connector);
/** 設置不自動部署 **/
tomcat.getHost().setAutoDeploy(false);
/** 三、配置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 有兩個核心功能:
針對這兩個功能,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來啓動的,啓動過程主要作了以下幾件事情:
而啓動 Tomcat 是在第7步 刷新上下文 這一步。
從整個流轉過程當中咱們知道了 Tomcat 的啓動主要是實例化兩個組件:Connector、Container。
Spring Boot 建立 Tomcat 時,會先建立一個根上下文,將 WebApplicationContext 傳給 Tomcat;
啓動 Web 容器,須要調用 getWebserver(),由於默認的 Web 環境就是 TomcatServletWebServerFactory,因此會建立 Tomcat 的 Webserver,這裏會把根上下文做爲參數給 TomcatServletWebServerFactory 的 getWebServer();
啓動 Tomcat,調用 Tomcat 中 Host、Engine 的啓動方法。