1. 程式人生 > >Spring Boot WAR包執行原理分析

Spring Boot WAR包執行原理分析

Spring Boot應用支援用jar方式獨立執行(官方推薦)。當然了,也支援打包成war放到web容器中執行

下面,講簡單的演示一下打包成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容器的啟動