Embedded Servlet Container 是怎樣啟動的

傳統Java Web開發中,開發者需要獨立部署Servlet容器,比如Tomcat,並將應用程式打成war包放入容器才能執行,這多多少少有點繁瑣且不方便除錯,嵌入式Servlet容器的出現改變了這個局面。當使用嵌入式Servlet容器,我們不再需要任何外部設施的支援,應用程式本身就是一個可以獨立執行的個體。作為解放生產力的典型代表,SpringBoot預設採用Embedded Tomcat來啟動Web應用程式,我們今天就來探究一下吧(基於SpringBoot 2。3。0)。

@SpringBootApplicationpublic class Application { public static void main(String[] args) { SpringApplication。run(Application。class, args); }}複製程式碼

這段程式碼相信大家都不陌生,它是應用程式的入口,一切的一切都要從這裡開始。我們直接跟進SpringApplication。run(。。。),來到

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources)。run(args);}複製程式碼

靜態的run(。。。)函式建立了一個SpringApplication的例項,並呼叫了它的run(。。。)方法,繼續跟進SpringApplication的建構函式

// 無關邏輯已刪除public SpringApplication(ResourceLoader resourceLoader, Class<?>。。。 primarySources) { // 根據類路徑下是否有對應的 class 檔案來判定當前的執行環境 // 我們引入了 spring-boot-starter-web,因此型別被推斷為 SERVLET this。webApplicationType = WebApplicationType。deduceFromClasspath();}複製程式碼

這裡我們主要關注SpringBoot是如何推斷應用程式的型別的,簡單來說是基於類路徑下是否存在指定的class檔案來判定的,在我們的例子裡型別被推斷為WebApplicationType。SERVLET。接著來到run(。。。)例項方法

// 無關邏輯已刪除public ConfigurableApplicationContext run(String。。。 args) { ConfigurableApplicationContext context = null; try { // 1。 建立 ApplicationContext context = createApplicationContext(); // 2。 重新整理 ApplicationContext refreshContext(context); } catch (Throwable ex) { throw new IllegalStateException(ex); } return context;}複製程式碼

省去無關邏輯後,run(。。。)方法主要做了兩件事:

建立ApplicationContext

重新整理ApplicationContext

嗯?嵌入式Tomcat這就啟動起來了?看來奧秘就隱藏在這兩步之中。檢視這兩步的原始碼,很容易知道ApplicationContext的具體型別是AnnotationConfigServletWebServerApplicationContext,而重新整理無非是呼叫了它的refresh()方法。

AnnotationConfigServletWebServerApplicationContext

Embedded Servlet Container 是怎樣啟動的

觀察AnnotationConfigServletWebServerApplicationContext的繼承樹,可以看到,紅圈外的是我們非常熟悉的、在傳統Web環境下使用的ApplicationContext;紅圈內的部分呢,單看名字也能猜到是我們要重點研究的物件——WebServerApplicaitonContext。

再深入一點兒AnnotationConfigServletWebServerApplicationContext,它繼承自ServletWebServerApplicationContext,並且在父類的基礎上提供了對Component Scan的支援和對@Configuration配置類的讀取、解析。

public class AnnotationConfigServletWebServerApplicationContext extends ServletWebServerApplicationContext implements AnnotationConfigRegistry { // 讀取並解析 @Configuration 配置類 private final AnnotatedBeanDefinitionReader reader; // 掃描指定包下的所有元件 private final ClassPathBeanDefinitionScanner scanner; // rest of codes are omitted。。。}複製程式碼

這部分功能是透過代理給AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner來實現的。這兩兄弟也是我們的老朋友了,而且和嵌入式Servlet容器的啟動也沒啥關係,我們就不多說了。

WebServerApplicaitonContext

接下來我們的重心就落在WebServerApplicaitonContext上了,先來看一下它的定義

/** * 實現此介面的 ApplicationContext 要負責對嵌入式 WebServer 的建立和生命週期管理 * * Interface to be implemented by {@link ApplicationContext application contexts} that * create and manage the lifecycle of an embedded {@link WebServer}。 */public interface WebServerApplicationContext extends ApplicationContext { /** * 返回當前 ApplicationContext 建立的 WebServer,並透過返回的 WebServer 引用來管理其生命週期 */ WebServer getWebServer(); /** * 名稱空間,當應用程式中有多個 WebServer 在執行時可以用來避免歧義 */ String getServerNamespace(); static boolean hasServerNamespace(ApplicationContext context, String serverNamespace) { return (context instanceof WebServerApplicationContext) && ObjectUtils 。nullSafeEquals(((WebServerApplicationContext) context)。getServerNamespace(), serverNamespace); }}// 子介面,可以對 ApplicationContext 進行配置public interface ConfigurableWebServerApplicationContext extends ConfigurableApplicationContext, WebServerApplicationContext { /** * 設定名稱空間 */ void setServerNamespace(String serverNamespace);}複製程式碼

再看看它關聯的WebServer的定義

/** * 代表一個已經配置好了的 WebServer,比如 Tomcat、Jetty、Netty * * Simple interface that represents a fully configured web server (for example Tomcat, * Jetty, Netty)。 Allows the server to be {@link #start() started} and {@link #stop() * stopped}。 */public interface WebServer { /** * 啟動伺服器 */ void start() throws WebServerException; /** * 停止伺服器 */ void stop() throws WebServerException; /** * 返回伺服器監聽的埠 */ int getPort(); /** * 優雅關閉 */ default void shutDownGracefully(GracefulShutdownCallback callback) { callback。shutdownComplete(GracefulShutdownResult。IMMEDIATE); }}複製程式碼

WebServer是對嵌入式Servlet容器的抽象,並且它代表的是一個已經完全配置好了的Servlet容器。換句話說,終端使用者不需要關注容器的具體細節,只需要知道怎麼啟動或關閉它;而WebServerApplicationContext負責建立WebServer,並作為終端使用者在合適的時機啟動或關閉它(也就是管理其生命週期)。

ServletWebServerApplicationContext

ServletWebServerApplicationContext實現了WebServerApplicationContext介面,而它對WebServer的建立和管理都濃縮在它自身的重新整理程序之中,也就是ConfigurableApplicationContext#refresh()被呼叫的時候。具體地說,ServletWebServerApplicationContext覆寫了onRefresh(。。。)鉤子方法,這個方法的呼叫時機是:

BeanFactory初始化完畢

BeanDefinition解析完成

Non-Lazy Init型別的Bean還未初始化

@Overrideprotected void onRefresh() { super。onRefresh(); try { // 建立 WebServer createWebServer(); } catch (Throwable ex) { throw new ApplicationContextException(“Unable to start web server”, ex); }}private void createWebServer() { WebServer webServer = this。webServer; ServletContext servletContext = getServletContext(); // webServer == null:還沒有建立 WebServer 例項 // servletContext == null:沒有使用外部容器 if (webServer == null && servletContext == null) { // 1。 獲取一個建立 WebServer 的工廠 ServletWebServerFactory factory = getWebServerFactory(); // 2。 透過工廠建立 WebServer this。webServer = factory。getWebServer(getSelfInitializer()); // 3。 監聽 ApplicationContext 的生命週期 // 在 ApplicationContext#stop() 時優雅關閉 WebServer getBeanFactory()。registerSingleton(“webServerGracefulShutdown”, new WebServerGracefulShutdownLifecycle(this。webServer)); // 4。 監聽 ApplicationContext 的生命週期 // 在 Non-Lazy Init 型別的 Bean 都初始化了之後啟動 WebServer // 在 ApplicationContext#stop() 時關閉 WebServer getBeanFactory()。registerSingleton(“webServerStartStop”, new WebServerStartStopLifecycle(this, this。webServer)); } // 外部容器 else if (servletContext != null) { try { getSelfInitializer()。onStartup(servletContext); } catch (ServletException ex) { throw new ApplicationContextException(“Cannot initialize servlet context”, ex); } } // 初始化 ServletContextPropertySource // 將 ServletContext 的 init-parameters 暴露到 Environment 中 initPropertySources();}複製程式碼

建立WebServer的時機是在Non-Lazy Init型別的Bean初始化之前,透過獲取BeanFactory中唯一的一個ServletWebServerFactory來執行建立。注意這裡攜帶了一個引數——getSelfInitializer(),這個引數很重要,我們後面再說。

緊接著往BeanFactory中註冊了兩個SmartLifecycle型別的元件來管理WebServer的生命週期,其中一個用於優雅關閉WebServer,另一個用於啟動或停止WebServer。

class WebServerGracefulShutdownLifecycle implements SmartLifecycle { private final WebServer webServer; private volatile boolean running; WebServerGracefulShutdownLifecycle(WebServer webServer) { this。webServer = webServer; } @Override public void start() { this。running = true; } @Override public void stop() { throw new UnsupportedOperationException(“Stop must not be invoked directly”); } @Override public void stop(Runnable callback) { // 優雅關閉 webServer this。running = false; this。webServer。shutDownGracefully((result) -> callback。run()); } @Override public boolean isRunning() { return this。running; }}class WebServerStartStopLifecycle implements SmartLifecycle { private final ServletWebServerApplicationContext applicationContext; private final WebServer webServer; private volatile boolean running; WebServerStartStopLifecycle(ServletWebServerApplicationContext applicationContext, WebServer webServer) { this。applicationContext = applicationContext; this。webServer = webServer; } @Override public void start() { // 啟動 webServer this。webServer。start(); this。running = true; this。applicationContext。publishEvent(new ServletWebServerInitializedEvent(this。webServer, this。applicationContext)); } @Override public void stop() { // 關閉 webServer this。webServer。stop(); } @Override public boolean isRunning() { return this。running; } @Override public int getPhase() { // 控制對 #stop() 的呼叫在 WebServerGracefulShutdownLifecycle#stop(Runnable) 之後 return Integer。MAX_VALUE - 1; }}複製程式碼

SmartLifecycle是spring-context定義的基礎元件,本篇的主題並不是它。不過為了方便理清呼叫順序,這裡還是簡單說一下:它是由LifecycleProcessor驅動的,在Non-Lazy Init型別的Bean都初始化了之後,ApplicationContext會回撥LifecycleProcessor#onRefresh(),並在其中對SmartLifecycle進行處理。

// 原始碼位於 AbstractApplicationContextprotected void finishRefresh() { // Clear context-level resource caches (such as ASM metadata from scanning)。 clearResourceCaches(); // 初始化 LifecycleProcessor initLifecycleProcessor(); // 回撥 LifecycleProcessor#onRefresh() // 在 onRefresh() 中逐個呼叫 SmartLifecycle#start() // 當然,這裡還有一些過濾條件,我們就不細說了 getLifecycleProcessor()。onRefresh(); // 釋出 ContextRefreshedEvent publishEvent(new ContextRefreshedEvent(this)); // Participate in LiveBeansView MBean, if active。 LiveBeansView。registerApplicationContext(this);}複製程式碼

至此,嵌入式Servlet容器是如何啟動的就分析完了。

ServletContextInitializer

前面提過,ServletWebServerFactory在建立WebServer時會攜帶一個引數——getSelfInitializer(),它的型別是ServletContextInitializer。

public interface ServletContextInitializer { /** * 以程式設計的方式配置 ServletContext,比如註冊 Servlet、Filter 等 */ void onStartup(ServletContext servletContext) throws ServletException;}複製程式碼

ServletContextInitializer的作用類似於ServletContainerInitializer,後者是Servlet API提供的標準初始化器。我們同樣可以在ServletContextInitializer 中對ServletContext進行配置,區別在於它的生命週期由BeanFactory管理而不是Servlet容器。

private org。springframework。boot。web。servlet。ServletContextInitializer getSelfInitializer() { return this::selfInitialize;}private void selfInitialize(ServletContext servletContext) throws ServletException { // 1。 ServletContext 和 ApplicationContext 相互繫結 prepareWebApplicationContext(servletContext); // 2。 註冊 ServletContextScope registerApplicationScope(servletContext); // 3。 往 BeanFactory 中註冊 ServletContext 及其相關的 init-parameters WebApplicationContextUtils。registerEnvironmentBeans(getBeanFactory(), servletContext); // 4。 重點:透過 ServletRegistrationBean、FilterRegistrationBean 等像 ServletContext 中動態註冊 Servlet、Filter 等元件 for (ServletContextInitializer beans : getServletContextInitializerBeans()) { beans。onStartup(servletContext); }}複製程式碼

第 1、2 和 第 3 步比較簡單,大家自己看看吧,我們重點來看看第 4 步。

// 初始化 ServletContextInitializerBeans,它繼承自 AbstractCollectionprotected Collection getServletContextInitializerBeans() { return new ServletContextInitializerBeans(getBeanFactory());}// 建構函式@SafeVarargspublic ServletContextInitializerBeans(ListableBeanFactory beanFactory, Class<? extends ServletContextInitializer>。。。 initializerTypes) { this。initializers = new LinkedMultiValueMap<>(); this。initializerTypes = (initializerTypes。length != 0) ? Arrays。asList(initializerTypes) : Collections。singletonList(ServletContextInitializer。class); // 遍歷 BeanFactory 中所有的 ServletContextInitializer // 分別處理 ServletRegistrationBean、FilterRegistrationBean 等 // 最後將它們新增到 initializers 中 addServletContextInitializerBeans(beanFactory); // 遍歷 BeanFactory 中的 Servlet、Filter 等,將它們包裝成對應的 RegistrationBean // 最後將它們新增到 initializers 中 addAdaptableBeans(beanFactory); // 按照 Ordered 介面或 @Order 註解排序 List sortedInitializers = this。initializers。values()。stream() 。flatMap((value) -> value。stream()。sorted(AnnotationAwareOrderComparator。INSTANCE)) 。collect(Collectors。toList()); // ServletContextInitializerBeans 繼承自 AbstractCollection,sortedList 就是其 back list this。sortedList = Collections。unmodifiableList(sortedInitializers); logMappings(this。initializers);}複製程式碼

ServletContextInitializerBeans在初始化的時候會檢索出BeanFactory中所有的RegistrationBean;如果BeanFactory中還存在原生的Servlet、Filter或Servlet Listener型別的Bean,則將它們包裝成對應的RegistrationBean,最後對所有的RegistrationBean進行排序。我們就以ServletRegistrationBean來看看它是如何實現向ServletContext中新增Servlet的吧。

Embedded Servlet Container 是怎樣啟動的

從繼承樹可以看到,ServletRegistrationBean同樣實現了ServletContextInitializer,檢視其onStartup(。。。)方法

// 原始碼位於 RegistrationBean@Overridepublic final void onStartup(ServletContext servletContext) throws ServletException { // 獲取描述資訊 String description = getDescription(); if (!isEnabled()) { logger。info(StringUtils。capitalize(description) + “ was not registered (disabled)”); return; } // 向 servletContext 中註冊 register(description, servletContext);}複製程式碼

DynamicRegistrationBean實現了register(。。。)方法

@Overrideprotected final void register(String description, ServletContext servletContext) { // 新增 registration D registration = addRegistration(description, servletContext); if (registration == null) { logger。info(StringUtils。capitalize(description) + “ was not registered (possibly already registered?)”); return; } // 配置 registration configure(registration);}複製程式碼

addRegistration(。。。)最終由ServletRegistrationBean實現

@Overrideprotected ServletRegistration。Dynamic addRegistration(String description, ServletContext servletContext) { String name = getServletName(); return servletContext。addServlet(name, this。servlet);}複製程式碼

不過是直接使用ServletContext。addServlet(。。。)動態添加了一個Servlet,再無其它。

後記

我們分析了嵌入式Servlet容器是何時建立和啟動的,卻沒有提它是如何建立的。以Tomcat為例,對應的TomcatWebServer封裝了Tomcat API,提供了對Connector、ErrorPage等一些列元件的配置,只不過我不太熟這些容器的架構,就不瞎BB了~~