在Spring Boot 初體驗一文中咱們學習了以 JAR 形式快速啓動一個Spring Boot
程序,而 Spring Boot
也支持傳統的部署方式: 將項目打包成 WAR
,而後由 Web
服務器進行加載啓動,此次以 Tomcat
爲例,咱們就快速學習下如何以 WAR
方式部署一個 Spring Boot
項目,代碼託管於 Github, 並作一些簡單的源碼分析.java
利用Spring Initializr 工具下載基本的 Spring Boot
工程,選擇 Maven
方式構建, 版本爲正式版1.5.16, 只選擇一個 Web
依賴.git
SpringBootServletInitializer
加載打開下載的工程後,對啓動類 SpringbootTomcatApplication
進行修改, 繼承 SpringBootServletInitializer
這個抽象類,而且重寫父類方法 SpringApplicationBuilder configure(SpringApplicationBuilder builder)
.github
@SpringBootApplication
public class SpringbootTomcatApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(SpringbootTomcatApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(SpringbootTomcatApplication.class, args);
}
}
複製代碼
SpringBootServletInitializer
類將在 Servlet
容器啓動程序時容許咱們對程序自定義配置,而這裏咱們將須要讓 Servlet
容器啓動程序時加載這個類.web
接下來在 pom.xml
文件中,修改打包方式爲 WAR
,讓 Maven
構建時以 WAR
方式生成.spring
<groupId>com.one</groupId>
<artifactId>springboot-tomcat</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
複製代碼
另外要注意的是:爲了確保嵌入式 servlet
容器不會影響部署war文件的servlet容器,此處爲 Tomcat
。咱們還須要將嵌入式 servlet
容器的依賴項標記爲 provided
。設計模式
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
複製代碼
爲了驗證 WAR
部署是否成功,咱們實現一個最基礎的處理 Web
請求的功能,在啓動類添加一些 Spring MVC
的代碼瀏覽器
@SpringBootApplication
@RestController
public class SpringbootTomcatApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(SpringbootTomcatApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(SpringbootTomcatApplication.class, args);
}
@RequestMapping(value = "/")
public String hello() {
return "hello tomcat";
}
}
複製代碼
如今就能夠打包 Spring Boot
程序成 WAR
, 而後讓 Tomcat
服務器加載了,在當前項目路徑下使用構建命令tomcat
mvn clean package
複製代碼
出現 BUILD SUCCESS
就說明打包成功了springboot
而後就能夠項目的 target
目錄下看到生成的 WAR
.bash
將 springboot-tomcat-0.0.1-SNAPSHOT.war
放在 Tomcat程序的文件夾 **webapps**
下,而後運行Tomcat
, 啓動成功就能夠在瀏覽器輸入 http://localhost:8080/springboot-tomcat-0.0.1-SNAPSHOT/ ,請求這個簡單 Web
程序了.
到這裏, WAR
方式部署的 Spring Boot
程序就完成了. 🎉🎉🎉
完成到這裏, 不由有個疑問: 爲什麼繼承了 SpringBootServletInitializer
類,並覆寫其 configure 方法就能以 war 方式去部署了呢 ? 帶着問題,咱們從源碼的角度上去尋找答案.
在啓動類 SpringbootTomcatApplication 覆寫的方法進行斷點,看下 Tomcat 運行項目時這個方法調用過程.
經過 Debug 方式運行項目,當運行到這行代碼時,能夠看到兩個重要的類 SpringBootServletInitializer
和 SpringServletContainerInitializer
.
從圖能夠看到 configure 方法調用是在父類的 createRootApplicationContext
,具體代碼以下,非關鍵部分已省略,重要的已註釋出來.
protected WebApplicationContext createRootApplicationContext( ServletContext servletContext) {
SpringApplicationBuilder builder = createSpringApplicationBuilder(); // 新建用於構建SpringApplication 實例的 builder
builder.main(getClass());
// ....
builder.initializers(
new ServletContextApplicationContextInitializer(servletContext));
builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class);
builder = configure(builder); // 調用子類方法,配置當前 builder
builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext));
SpringApplication application = builder.build(); // 構建 SpringApplication 實例
if (application.getSources().isEmpty() && AnnotationUtils
.findAnnotation(getClass(), Configuration.class) != null) {
application.getSources().add(getClass());
}
//...
return run(application); // 運行 SpringApplication 實例
}
複製代碼
SpringApplicationBuilder
實例, 應該是遵循建造者設計模式,來完成SpringApplication
的構建組裝.
而 createRootApplicationContext
方法的調用仍是在這個類內完成的,這個就比較熟悉, 由於傳統的 Spring Web
項目啓動也會建立一個 WebApplicationContext
實例.
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// Logger initialization is deferred in case a ordered
// LogServletContextInitializer is being used
this.logger = LogFactory.getLog(getClass());
WebApplicationContext rootAppContext = createRootApplicationContext(
servletContext); // 建立一個 WebApplicationContext 實例.
// ...
}
複製代碼
問題又來了,這裏的 onStartup
方法又是如何執行到的呢? SpringServletContainerInitializer
類就登場了.
SpringServletContainerInitializer
類實現 Servlet 3.0
規範的 ServletContainerInitializer
接口, 也就意味着當 Servlet
容器啓動時,就以調用 ServletContainerInitializer
接口的 onStartup
方法通知實現了這個接口的類.
public interface ServletContainerInitializer {
void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
}
複製代碼
如今咱們來看下 SpringServletContainerInitializer
的 onStarup
方法的具體實現以下, 關鍵代碼23~24行裏 initializers
是一個 LinkedList
集合,有着全部實現 WebApplicationInitializer
接口的實例,這裏進行循環遍歷將調用各自的 onStartup
方法傳遞 ServletContext
實例,以此來完成 Web
服務器的啓動通知.
@Override
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
// 提取webAppInitializerClasses集合中 實現 WebApplicationInitializer 接口的實例
initializers.add((WebApplicationInitializer) waiClass.newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
// ...
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext); // 調用全部實現 WebApplicationInitializer 實例的onStartup 方法
}
}
複製代碼
追蹤執行到SpringServletContainerInitializer
類的22行, 咱們能夠看到集合裏就包含了咱們的啓動類,所以最後調用了其父類的 onStartup
方法完成了 WebApplicationContext
實例的建立.
看到這裏,咱們總結下這幾個類調用流程,梳理下 Spring Boot
程序 WAR
方式啓動過程:
SpringServletContainerInitializer#onStartup
=> SpringBootServletInitializer#onStartup
=> ``SpringBootServletInitializer#createRootApplicationContext =>
SpringbootTomcatApplication#configure`
另外,我還收穫了一點就是: 當執行 SpringBootServletInitializer
的 createRootApplicationContext
方法最後,調用了run(application)
.
這也說明了當 WAR
方式部署 Spring Boot
項目時, 固定生成的 Main
方法不會再被執行到,是能夠去掉.
//當項目以WAR方式部署時,這個方法就是無用代碼
public static void main(String[] args) {
SpringApplication.run(SpringbootTomcatApplication.class, args);
}
複製代碼
本文主要實戰學習如何讓 Spring Boot
以 WAR
方式啓動,而且進行簡單的源碼分析,幫助咱們更好地理解 Spring Boot
.但願有所幫助,後續仍會更多的實戰和分析,敬請期待哈. 😁😁😁.