天天看點

【SpringBoot源碼解析】第三章:SpringBoot通過打成war包的方式是如何啟動的

緣起

在前面幾章的講解中,我們知道,當我們執行以下代碼時,springboot會啟動一個内置的tomcat,并且加載對應的starter.那麼如果我們不采用java -jar的方式啟動springboot的應用,他也就沒有去執行run方法,那麼他又是如何做到自動裝配的呢?

SpringBoot通過war的方式是如何啟動的

關于SPI

在說這些之前,我們先要了解一個東西,SPI。關于SPI可以去了解我的另一篇文章

​​Java SPI 機制詳解​​

SPI在springboot中的應用

我們看spring-web這個項目的spi檔案javax.servlet.ServletContainerInitializer

【SpringBoot源碼解析】第三章:SpringBoot通過打成war包的方式是如何啟動的

檔案内容如下

org.springframework.web.SpringServletContainerInitializer      

​SpringServletContainerInitializer​

​代碼如下

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
//省略其餘代碼
}      

可以看到這個類繼承了一個叫​

​ServletContainerInitializer​

​的接口

public interface ServletContainerInitializer {
  public void onStartup(Set<Class<?>> c, ServletContext ctx)
          throws ServletException; 
}      

這個接口是幹嘛的呢?​

​ServletContainerInitializer​

​​ 是 Servlet 3.0 新增的一個接口,主要用于在容器啟動階段通過程式設計風格注冊​

​Filter​

​​, ​

​Servlet​

​​以及​

​Listener​

​​,以取代通過​

​web.xml​

​​配置注冊。這樣就利于開發内聚的web應用架構。容器啟動階段依據​

​java spi​

​​擷取到所有​

​ServletContainerInitializer​

​​的實作類,然後執行其​

​onStartup​

​方法.

也就是說,我們把 ​

​ServletContainerInitializer​

​​的實作類寫在 ​

​META-INF / services / javax.servlet.ServletContainerInitializer​

​​ 檔案中,那麼Tomcat等容器啟動的時候就會去調用所有實作類的​

​onStartup​

​方法。

@HandlesTypes注解是幹嘛的呢?

​SpringServletContainerInitializer​

​就是​

​ServletContainerInitializer​

​的實作類,可以看到​

​SpringServletContainerInitializer​

​加上了一個​

​@HandlesTypes(WebApplicationInitializer.class)​

​的注解,這個注解的作用就是容器啟動的時候調用實作類的​

​onStartup​

​方法的時候,會把注解中标注的接口的實作類當做參數傳遞進去。

我們看​

​SpringServletContainerInitializer​

​​的​

​onStartup​

​​方法,在容器啟動的時候會調用這個方法,同時​

​set​

​​集合參數​

​webAppInitializerClasses​

​​即為​

​@HandlesTypes​

​​中标注的​

​WebApplicationInitializer​

​的實作類

public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
      throws ServletException {
  //省略其餘代碼
  for (WebApplicationInitializer initializer : initializers) {
    //依次回調參數實作類的onStartup方法
    initializer.onStartup(servletContext);
  }
}      

最後一段代碼​

​initializer.onStartup(servletContext);​

​就是把所有的​

​WebApplicationInitializer​

​的實作類的​

​onStartup​

​方法調用一遍。我們看看這個類的所有實作類

【SpringBoot源碼解析】第三章:SpringBoot通過打成war包的方式是如何啟動的

看到實作類中有一個​

​SpringBootServletInitializer​

​,這個類是我們要重點關注的對象,先來看看這個類的注釋

【SpringBoot源碼解析】第三章:SpringBoot通過打成war包的方式是如何啟動的

也就是說這個類是當我們以war包的方式讓外部tomcat運作時才需要關注的類。

我們接着看這個類的​

​onStartup​

​方法

@Override
public void onStartup(ServletContext servletContext) throws ServletException {
  WebApplicationContext rootAppContext = createRootApplicationContext(
      servletContext);
  if (rootAppContext != null) {
    servletContext.addListener(new ContextLoaderListener(rootAppContext) {
      @Override
      public void contextInitialized(ServletContextEvent event) {
        // no-op because the application context is already initialized
      }
    });
  }
}      

​createRootApplicationContext​

​方法

protected WebApplicationContext createRootApplicationContext(
    ServletContext servletContext) {
  //其餘代碼略
  return run(application);
}      

​run​

​方法

protected WebApplicationContext run(SpringApplication application) {
  return (WebApplicationContext) application.run();
}      

可以看到最終上訴會調用到SpringApplication的無參run方法,那麼到了這一步,就跟我們通過main方法啟動是一個道理了

public static void main(String[] args) {
  SpringApplication.run(HppaApplication.class, args);
}      

總結

SpringBoot通過打成war包的方式運作,其本質上是利用了Servlet3.0規範中的Tomcat啟動時會去調用​

​ServletContainerInitializer​

​​接口的​

​onStartup​

​​方法,同時把使用類注解​

​@HandlesTypes​

​​中标注的接口的實作類作為參數傳入到​

​onStartup​

​​中,并依次調用其實作類的​

​onStartup​

​​方法。而​

​SpringServletContainerInitializer​

​​實作了​

​ServletContainerInitializer​

​​,同時标注了​

​@HandlesTypes(WebApplicationInitializer.class)​

​,

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
//省略其餘代碼
}      

那麼也就是說,Tomcat啟動時,最終會去調用​

​WebApplicationInitializer​

​​的實作類的​

​onStartup​

​​方法,而​

​SpringBootServletInitializer​

​​實作了​

​WebApplicationInitializer​

public abstract class SpringBootServletInitializer implements WebApplicationInitializer      

那也就是最終會調用​

​SpringBootServletInitializer​

​的​

​onStartup​

​方法,而這個​

​onStartup​

​方法最終其實是調用了​

​application.run()​

​,也就類似于我們通過Main方法啟動了。為了友善了解,我畫了下圖