Spring Boot WAR包執行原理分析
下面,講簡單的演示一下打包成war包執行的步驟
1:新建maven專案
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.pp</groupId> <artifactId>spring-boot-mall</artifactId> <version>1.0.0</version> <!-- 注意這裡要是war --> <packaging>war</packaging> <name>spring-boot-mall</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.4.3.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <!-- 注意這裡是 provided --> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <!-- 配置這個外掛之後,就支援直接用命令的方式啟動(maven clean jetty:run)應用而無需單獨準備一個web容器 --> <plugin> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-maven-plugin</artifactId> <version>9.3.3.v20150827</version> <configuration> <stopKey>foo</stopKey> <stopPort>9999</stopPort> <httpConnector> <port>8080</port> </httpConnector> <webApp> <contextPath>/</contextPath> </webApp> </configuration> </plugin> <plugin> <artifactId>maven-war-plugin</artifactId> <configuration> <!-- 本例子由於專案裡面沒有web.xml,所以要配置這個 --> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> </build> </project>
package com.pp.mall;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyController {
@GetMapping("/hello")
public String hello() {
return "Hello World!";
}
}
package com.pp.mall; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.support.SpringBootServletInitializer; @SpringBootApplication public class SampleWarApplication extends SpringBootServletInitializer { }
最後,執行maven clean package打包,把打好的war包仍到tomcat等web容器中後,啟動容器。
當然了,也可以直接在專案的根目錄執行 mvn clean jetty:run 執行專案
之後,就可以訪問 /${contextPath}/hello
那麼現在問題來了,這個web專案裡面沒有web.xml,也就是說,沒有配置任何初始化、過濾器或者其他Servlet,為什麼能啟動spring容器呢?
首先要看看Servlet3.0中的一個新介面javax.servlet.ServletContainerInitializer
ServletContainerInitializer 是 Servlet 3.0 新增的一個介面,容器在啟動時使用 JAR 服務 API(JAR Service API) 來發現 ServletContainerInitializer 的實現類,並且容器將 WEB-INF/lib 目錄下 JAR 包中的類都交給該類的 onStartup() 方法處理,我們通常需要在該實現類上使用 @HandlesTypes 註解來指定希望被處理的類,過濾掉不希望給 onStartup() 處理的類。
也就是說,這裡用到了Java的SPI機制。
SPI的全名為Service Provider Interface。這個是針對廠商或者外掛的。在java.util.ServiceLoader的文件裡有比較詳細的介紹。簡單的總結下java spi機制的思想。
我們系統裡抽象的各個模組,往往有很多不同的實現方案,比如日誌模組的方案,xml解析模組、jdbc模組的方案等。
面向的物件的設計裡,我們一般推薦模組之間基於介面程式設計,模組之間不對實現類進行硬編碼。一旦程式碼裡涉及具體的實現類,就違反了可拔插的原則,如果需要替換一種實現,就需要修改程式碼。
為了實現在模組裝配的時候能不在程式裡動態指明,這就需要一種服務發現機制。 java spi就是提供這樣的一個機制:為某個介面尋找服務實現的機制。
有點類似IOC的思想,就是將裝配的控制權移到程式之外,在模組化設計中這個機制尤其重要。
java spi的具體約定為:當服務的提供者,提供了服務介面的一種實現之後,在jar包的META-INF/services/目錄裡同時建立一個以服務介面命名的檔案。該檔案裡就是實現該服務介面的具體實現類。
而當外部程式裝配這個模組的時候,就能通過該jar包META-INF/services/裡的配置檔案找到具體的實現類名,並裝載例項化,完成模組的注入。
基於這樣一個約定就能很好的找到服務介面的實現類,而不需要再程式碼裡制定。jdk提供服務實現查詢的一個工具類:java.util.ServiceLoader
在spring-web-${version}.jar檔案裡面,可以看到
META-INF/services/javax.servlet.ServletContainerInitializer
檔案裡面的內容是:
org.springframework.web.SpringServletContainerInitializer
意思就是web容器在啟動的時候,會去載入所有的javax.servlet.ServletContainerInitializer介面的實現類,然後呼叫onStartup方法做初始化工作。
所以,在沒有web.xml的情況下,也能進行spring容器的啟動