緣起
在前面幾章的講解中,我們知道,當我們執行以下代碼時,springboot會啟動一個内置的tomcat,并且加載對應的starter.那麼如果我們不采用java -jar的方式啟動springboot的應用,他也就沒有去執行run方法,那麼他又是如何做到自動裝配的呢?
SpringBoot通過war的方式是如何啟動的
關于SPI
在說這些之前,我們先要了解一個東西,SPI。關于SPI可以去了解我的另一篇文章
Java SPI 機制詳解
SPI在springboot中的應用
我們看spring-web這個項目的spi檔案javax.servlet.ServletContainerInitializer

檔案内容如下
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
方法調用一遍。我們看看這個類的所有實作類
看到實作類中有一個
SpringBootServletInitializer
,這個類是我們要重點關注的對象,先來看看這個類的注釋
也就是說這個類是當我們以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方法啟動了。為了友善了解,我畫了下圖