前言
開始之前呢,我們帶著幾個問題去學(xué)習(xí):
1、Spring Boot 嵌入式Web容器是什么?
2、整體流程或結(jié)構(gòu)是怎樣的?
3、核心部分是什么?
4、怎么實現(xiàn)的?
1、起源
在當(dāng)今的互聯(lián)網(wǎng)場景中,與終端用戶交互的應(yīng)用大多數(shù)是 Web 應(yīng)用,其中 Java Web 應(yīng)用尤為突出,其對應(yīng)的 Java Web 容器發(fā)展至今也分為 Servlet Web 容器和 Reactive Web 容器,前者的使用率大概占比是百分之九十左右,其具體的實現(xiàn)有 Tomcat、Jetty 和 Undertow;而后者出現(xiàn)較晚,且技術(shù)棧體系并未完全成熟,還有待時間驗證可行性,它的默認(rèn)實現(xiàn)為 Netty Web Server。其中的 Servlet 規(guī)范與三種 Servlet 容器的版本關(guān)系如下:
Servlet 規(guī)范 | Tomcat | Jetty | Undertow |
4 | 9.X | 9.X | 2.X |
3.1 | 8.X | 8.X | 1.X |
3 | 7.X | 8.X | N/A |
2.5 | 6.X | 8.X | N/A |
以上 Web 容器均被 Spring Boot 嵌入至其中作為其核心特性,來簡化 Spring Boot 應(yīng)用啟動流程。Spring Boot 通過 Maven 依賴來切換應(yīng)用的嵌入式容器類型,其對應(yīng)的 Maven jar 分別是:
<!-- Tomcat -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<!-- undertow -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<!-- jetty -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<!-- netty Web Server -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-reactor-netty</artifactId>
</dependency>
前三者是 Servlet Web 實現(xiàn),最后則是 Reactive Web 的實現(xiàn)。值得注意的是,當(dāng)我們引用的是 Servlet Web 功能模塊時,它會自動集成 Tomcat ,里面包含了 Tomcat 的 Maven 依賴包,也就是說 Tomcat 是默認(rèn)的 Servlet Web 容器。Servlet Web 模塊的 Maven 依賴如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
而如果引用的是 Reactive Web 功能模塊時,則會默認(rèn)集成 netty Web Server 。Reactive Web 模塊的依賴如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
不過,上面的三種 Servlet Web 容器也能作為 Reactive Web 容器 ,并允許替換默認(rèn)實現(xiàn) Netty Web Server,因為 Servlet 3.1+容器同樣滿足 Reactive 異步非阻塞特性。
接下來,我們重點討論嵌入式 Web 容器的啟動流程。
注:本篇文章所用到的 Spring Boot版本是 2.3.3.RELEASE
2、容器啟動流程解析
Spring Boot 嵌入式容器啟動時會先判斷當(dāng)前的應(yīng)用類型,是 Servlet Web 還是 Reactive Web ,之后會創(chuàng)建相應(yīng)類型的 ApplicationContext 上下文,在該上下文中先獲取容器的工廠類,然后利用該工廠類創(chuàng)建具體的容器。接下來,我們進行詳細(xì)討論。
從 Spring Boot 啟動類開始:
@SpringBootApplication
public class DiveInSpringBootApplication {
public static void main(String[] args){
SpringApplication.run(DiveInSpringBootApplication.class, args);
}
}
2.1獲取應(yīng)用類型
先來看看,獲取應(yīng)用類型的過程,進入 run 的重載方法:
public class SpringApplication {
// 1、該方法中,先構(gòu)造 SpringApplication 對象,再調(diào)用該對象的 run 方法。我們進入第二步查看構(gòu)造過程
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args){
return new SpringApplication(primarySources).run(args);
}
// 2、webApplicationType 存儲的就是應(yīng)用的類型,通過 deduceFromClasspath 方法返回。// 我們進入第三步查看該方法實現(xiàn)public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources){
...
this.webApplicationType = WebApplicationType.deduceFromClasspath();
...
}
}
public enum WebApplicationType {
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework." + "web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS = "org." + "springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
// 3、這里其實是根據(jù)引入的 Web 模塊 jar 包,來判斷是否包含各 Web 模塊的類,來返回相應(yīng)的應(yīng)用類型
static WebApplicationType deduceFromClasspath(){
// 當(dāng) DispatcherHandler 類存在,DispatcherServlet 和 ServletContainer 不存在時,
// 返回 Reactive ,表示當(dāng)前 Spring Boot 應(yīng)用類型是 Reactive Web 。
// 前者是 Reactice Web jar 中的類,后兩者是 Servlet Web 中的。if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
// 這里判斷是非 Web 應(yīng)用類型for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
// 以上都不滿足時,最后返回 Servlet 。return WebApplicationType.SERVLET;
}
}
以上,在 SpringApplication 的構(gòu)造階段確定了當(dāng)前應(yīng)用的類型,該類型名稱存儲在 webApplicationType 字段中。
2.2容器啟動流程
接著進入容器啟動流程,進入重載的 run 方法中:
public class SpringApplication {
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
+ "annotation.AnnotationConfigApplicationContext";
public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot."
+ "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."
+ "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";
...
private WebApplicationType webApplicationType;
...
public ConfigurableApplicationContext run(String... args){
...
ConfigurableApplicationContext context = null;
try {
...
// 1、通過 createApplicationContext 方法創(chuàng)建對應(yīng)的 ApplicationContext 應(yīng)用上下文,進入 1.1 查看具體實現(xiàn)
context = createApplicationContext();
...
// 2、該方法實質(zhì)是啟動 Spring 應(yīng)用上下文的,但 Spring Boot 嵌入式容器也在該過程中被啟動,入?yún)⑹巧舷挛膶ο?,我們進入 2.1 進行跟蹤
refreshContext(context);
...
}
...
}
// 1.1、protected ConfigurableApplicationContext createApplicationContext(){
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
// 這里就是通過 webApplicationType 屬性,判斷應(yīng)用類型,來創(chuàng)建不同的 ApplicationContext 應(yīng)用上下文switch (this.webApplicationType) {
case SERVLET:
// 返回的是 Servlet Web ,具體對象為 AnnotationConfigServletWebServerApplicationContext,// 該類有一個關(guān)鍵父類 ServletWebServerApplicationContext
contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
// 返回的是 Reactive Web,具體對象為 AnnotationConfigReactiveWebServerApplicationContext
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
// 應(yīng)用類型是非 Web 時,返回 AnnotationConfigApplicationContext
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
...
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
// 2.1private void refreshContext(ConfigurableApplicationContext context){
// 里面調(diào)用的是 refresh 方法,進入 2.2 繼續(xù)跟蹤
refresh(context);
...
}
// 2.2protected void refresh(ApplicationContext applicationContext){
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
// 最終調(diào)用了 所有應(yīng)用上下文的統(tǒng)一抽象類 AbstractApplicationContext 中的 refresh 方法,進入 3 查看實現(xiàn)
((AbstractApplicationContext) applicationContext).refresh();
}
// ...
}
AbstractApplicationContext 是 Spring 應(yīng)用上下文的核心啟動類,Spring 的 ioc 從這里就開始進入創(chuàng)建流程。在后續(xù)在 Spring 系列的文章中會進行詳細(xì)討論,我們這里只關(guān)注容器創(chuàng)建的部分。
public abstract class AbstractApplicationContext extends DefaultResourceLoaderimplements ConfigurableApplicationContext {
...
// 3
@Overridepublic void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
try {
...
// Web 容器在這個方法中啟動,但在當(dāng)前抽象類中這個方法是個空實現(xiàn),具體由 ApplicationContext 上下文的子類對象進行重寫,
// 我們進入 4 查看其中一個子類的實現(xiàn)
onRefresh();
...
}
}
...
}
...
}
這里以 Servlet web 為例,具體上下文對象是
AnnotationConfigServletWebServerApplicationContext,該類實現(xiàn)了 ServletWebServerApplicationContext 類,onRefresh 方法由該類進行重寫。
public class ServletWebServerApplicationContext extends GenericWebApplicationContextimplements ConfigurableWebServerApplicationContext {
...
// 4
@Overrideprotected void onRefresh(){
...
try {
// 里面調(diào)用了 createWebServer 方法,進入 5 查看實現(xiàn)
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
...
// 5private void createWebServer(){
// WebServer 就是容器對象,是一個接口,其對應(yīng)的實現(xiàn)類分別是:
// JettyWebServer、TomcatWebServer、UndertowWebServer、NettyWebServer。這些容器對象由對應(yīng)的容器工廠類進行創(chuàng)建
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
// 當(dāng) webServer 等于 null ,也就是容器還沒創(chuàng)建時,進入該 if 中if (webServer == null && servletContext == null) {
// 這里是獲取 創(chuàng)建 Servlet Web 容器的工廠類,也是一個接口,有三個實現(xiàn),分別是:
// JettyServletWebServerFactory、TomcatServletWebServerFactory、UndertowServletWebServerFactory
ServletWebServerFactory factory = getWebServerFactory();
// 通過容器工廠類的 getWebServer 方法,創(chuàng)建容器對象。這里以創(chuàng)建 Tomcat 為例,來繼續(xù)跟蹤容器的創(chuàng)建流程,// 跳到 6 查看 TomcatServletWebServerFactory 的 getWebServer 方法this.webServer = factory.getWebServer(getSelfInitializer());
}
...
}
}
TomcatServletWebServerFactory 是創(chuàng)建 Tomcat 的 Web 容器工廠類,但這個工廠類是如何被創(chuàng)建的呢?這里將會在文章的第三部分進行詳細(xì)討論。
public class TomcatServletWebServerFactory extends AbstractServletWebServerFactoryimplements ConfigurableTomcatWebServerFactory, ResourceLoaderAware {
...
// 6、
@Overridepublic WebServer getWebServer(ServletContextInitializer... initializers){
// 方法中先進行創(chuàng)建 Tomcat 的流程,如 Container 、Engine、Host、Servlet 幾個容器的組裝。// 后續(xù)有機會再對 Tomcat 進行詳細(xì)討論,這里就不深入了
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
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);
// 通過 getTomcatWebServer 方法返回 TomcatWebServer 容器對象。進入 7 查看接下來的流程return getTomcatWebServer(tomcat);
}
...
// 7、這里通過 TomcatWebServer 的構(gòu)造方法創(chuàng)建該對象。進入 8 繼續(xù)跟蹤protected TomcatWebServer getTomcatWebServer(Tomcat tomcat){
return new TomcatWebServer(tomcat, getPort() >= 0);
}
...
}
TomcatWebServer 是具體的容器對象,在其對應(yīng)的工廠類中進行創(chuàng)建,其實現(xiàn)了 WebServer 接口,并在該對象中進行 Tomcat 的啟動流程。
public class TomcatWebServer implements WebServer {
...
// 8、
public TomcatWebServer(Tomcat tomcat, boolean autoStart){
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
// 進入 initialize 方法中,查看實現(xiàn)
initialize();
}
private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
...
// 調(diào)用 Tomcat 的 start 方法,正式啟動this.tomcat.start();
...
// 啟動守護線程來監(jiān)聽http請求
startDaemonAwaitThread();
}
...
}
}
...
}
到這里,Web 容器的啟動流程就結(jié)束了,以上是以 Servlet Web 及其具體的 Tomcat 容器為例子進行的討論,這也是大部分開發(fā)者常用的體系。接著來對整個過程做一個總結(jié)。
首先通過引入的 Web 模塊 Maven 依賴 ,來判斷當(dāng)前應(yīng)用的類型,如 Servlet Web 或 Reactive Web。并根據(jù)應(yīng)用類型來創(chuàng)建相應(yīng)的 ApplicationContext 上下文對象。然后調(diào)用了 ApplicationContext 的父抽象類
AbstractApplicationContext 中的 refresh 方法,又在該方法中調(diào)用了子類的 onRefresh 方法。最后是在 onRefresh 中通過容器的工廠類創(chuàng)建具體容器對象,并在該容器對象中進行啟動。
3、加載 Web 容器工廠
上面說過, Web 容器對象是由其對應(yīng)的工廠類進行創(chuàng)建的,那容器工廠類又是怎么創(chuàng)建呢?我們這里就來看一看。在《Spring Boot 自動裝配(二)》的 1.2 小節(jié)說過, Spring Boot 啟動時,會讀取所有 jar 包中 META-INF 文件夾下的 spring.factories 文件,并加載文件中定義好的類,如:
...
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\\
...
其中有一個
ServletWebServerFactoryAutoConfiguration 類:
@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
...
}
該類通過 @Import 導(dǎo)入了
ServletWebServerFactoryConfiguration 中的三個內(nèi)部類,這三個內(nèi)部類就是用來創(chuàng)建容器工廠,我們進入其中查看具體實現(xiàn):
@Configuration
class ServletWebServerFactoryConfiguration {
// 通過 @ConditionalOnClass 判斷 Servlet 、Tomcat、UpgradeProtocol 這三個 Class 是否存在,
// 當(dāng)引用的是 Tomcat Maven 依賴時,則 Class 才存在,并創(chuàng)建 Tomcat 的容器工廠類 TomcatServletWebServerFactory@Configuration@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
@Beanpublic TomcatServletWebServerFactory tomcatServletWebServerFactory(){
return new TomcatServletWebServerFactory();
}
}
// 當(dāng)引用的是 Jetty Maven 依賴時,@ConditionalOnClass 條件才滿足,
// 創(chuàng)建的容器工廠類是 JettyServletWebServerFactory@Configuration@ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedJetty {
@Beanpublic JettyServletWebServerFactory JettyServletWebServerFactory(){
return new JettyServletWebServerFactory();
}
}
// 當(dāng)引用的是 Undertow Maven 依賴時,@ConditionalOnClass 條件才滿足,
// 創(chuàng)建的容器工廠類是 UndertowServletWebServerFactory@Configuration@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedUndertow {
@Beanpublic UndertowServletWebServerFactory undertowServletWebServerFactory(){
return new UndertowServletWebServerFactory();
}
}
可以看到,主要是通過引入相應(yīng) Web 容器的 Maven 依賴,來判斷容器對應(yīng)的 Class 是否存在,存在則創(chuàng)建相應(yīng)的容器工廠類。
4、總結(jié)
最后,來對 Spring Boot 嵌入式 Web 容器做一個整體的總結(jié)。Spring Boot 支持的兩大 Web 容器體系,一個是 Servlet Web ,另一個是 Reactive Web ,它們都有其具體的容器實現(xiàn),相信大多數(shù)開發(fā)者使用的都是前者,且最常用的容器實現(xiàn)也是 Tomcat,所以這篇文章主要討論的也是 Spring Boot 啟動 Tomcat 嵌入式容器的流程。