Spring-Boot原理及應用佈署 Spring-Boot原理及應用佈署
1.FAT JAR目錄結構
解壓後結果
drwxr-xr-x 5 hjq staff 160 Dec 3 09:57 .
drwxr-xr-x 10 hjq staff 320 Dec 4 11:42 ..
drwxr-xr-x 5 hjq staff 160 Dec 2 23:41 BOOT-INF
drwxr-xr-x 5 hjq staff 160 Dec 2 23:41 META-INF
drwxr-xr-x 3 hjq staff 96 Feb 1 1980 org
//BOOT-INF目錄下
drwxr-xr-x 5 hjq staff 160 Dec 2 23:41 .
drwxr-xr-x 5 hjq staff 160 Dec 3 09:57 ..
drwxr-xr-x 5 hjq staff 160 Dec 2 23:41 classes
-rw-r--r-- 1 hjq staff 5466 Dec 2 23:41 classpath.idx
drwxr-xr-x 166 hjq staff 5312 Dec 2 23:41 lib
//META-INF目錄下
drwxr-xr-x 5 hjq staff 160 Dec 2 23:41 .
drwxr-xr-x 5 hjq staff 160 Dec 3 09:57 ..
-rw-r--r-- 1 hjq staff 399 Dec 2 23:41 MANIFEST.MF
drwxr-xr-x 3 hjq staff 96 Dec 2 23:41 maven
-rw-r--r-- 1 hjq staff 109 Dec 2 17:57 spring.factories
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2檢視MANIFEST.MF
已知jar採用java的Fat jar啟動規範。是讀取MANIFEST.MF檔案
Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Archiver-Version: Plexus Archiver
Built-By: hjq
Start-Class: com.hjq.whyshare.home.DemoApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Version: 2.3.2.RELEASE
Created-By: Apache Maven 3.6.1
Build-Jdk: 1.8.0_131
Main-Class: org.springframework.boot.loader.JarLauncher
1
2
3
4
5
6
7
8
9
10
11
12
命令列java-jar 會讀取到Main-Class,作為啟動類。
證明了main入口,在JarLauncher上
觀察到Start-Class是我們編寫的程式入口。檢視其中實現邏輯。
JarLauncher的依賴,在pom上並沒有新增,是spring-boot-maven-plugin外掛打包時新增。
為了檢視原始碼分析,新增引導項的依賴。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-loader</artifactId>
</dependency>
1
2
3
4
3.分析JarLauncher實現原理
檢視類繼承關係。可知spring-boot打包有兩種,傳統的war包和jar包。該篇文章主要分析jar包方式。
3.1JarLauncher的main方法分析
//JarLauncher的main方法
public static void main(String[] args) throws Exception {
new JarLauncher().launch(args);
}
//構建JarLauncher物件前,先執行父類ExecutableArchiveLauncher建構函式
public class JarLauncher extends ExecutableArchiveLauncher
public ExecutableArchiveLauncher() {
try {
this.archive = createArchive();
this.classPathIndex = getClassPathIndex(this.archive);
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
archive是一個springboot loader庫的類,表示檔案。當生成jar時,createArchive()是指向該jar包的物件。後續archive上讀取MANIFEST.MF等資訊。以下是原始碼
protected final Archive createArchive() throws Exception {
ProtectionDomain protectionDomain = getClass().getProtectionDomain();
CodeSource codeSource = protectionDomain.getCodeSource();
URI location = (codeSource != null) ? codeSource.getLocation().toURI() : null;
String path = (location != null) ? location.getSchemeSpecificPart() : null;
if (path == null) {
throw new IllegalStateException("Unable to determine code source archive");
}
File root = new File(path);
if (!root.exists()) {
throw new IllegalStateException("Unable to determine code source archive from " + root);
}
//ExplodedArchive是檔案以解壓後的形式執行;JarFileArchive是以jar的形式
return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
再檢視launch()實現原始碼
protected void launch(String[] args) throws Exception {
//jar包形式才執行
if (!isExploded()) {
//用於系統設定java.protocol.handler.pkgs為org.springframework.boot.loader
JarFile.registerUrlProtocolHandler();
}
//Iterator迭代器是用於迭代jar包中的第三方依賴包,返回包含依賴包的classLoader。
ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());
String jarMode = System.getProperty("jarmode");
//從MANIFEST.MF上讀取到start-class配置
String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass();
launch(args, launchClass, classLoader);
}
protected String getMainClass() throws Exception {
Manifest manifest = this.archive.getManifest();
String mainClass = null;
if (manifest != null) {
mainClass = manifest.getMainAttributes().getValue(START_CLASS_ATTRIBUTE);
}
if (mainClass == null) {
throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this);
}
return mainClass;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
獲取到包含依賴的classLoader,啟動引數還有我們編寫的啟動類(start-class),就會採用反射進行呼叫。
//基類Launcher的實現
protected void launch(String[] args, String launchClass, ClassLoader classLoader) throws Exception {
Thread.currentThread().setContextClassLoader(classLoader);
//呼叫業務編寫的啟動類入口main
createMainMethodRunner(launchClass, args, classLoader).run();
}
//MainMethodRunner類
public MainMethodRunner(String mainClass, String[] args) {
this.mainClassName = mainClass;
this.args = (args != null) ? args.clone() : null;
}
public void run() throws Exception {
Class<?> mainClass = Class.forName(this.mainClassName, false, Thread.currentThread().getContextClassLoader());
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
mainMethod.setAccessible(true);
mainMethod.invoke(null, new Object[] { this.args });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
從run()方法,可以看到,通過反射start-class對應的類,再呼叫main方法。
這個過程中最複雜的實現是JarFileArchive的內部實現,讀取MANIFEST.MF,載入第三方依賴。後續再詳細分析。
一、Spring Boot的理念
從最根本上來講,Spring Boot就是一些庫的集合,它能夠被任意專案的構建系統所使用。簡便起見,該框架也提供了命令列介面,它可以用來執行和測試Boot應用。框架的釋出版本,包括整合的CLI(命令列介面),可以在Spring倉庫中手動下載和安裝。
實際中springboot將應用打包之後,會生成一個fat jar,裡面包含了應用依賴的jar包,還有Spring boot loader相關的類Fat jar的啟動Main函式是JarLauncher,它負責建立一個LaunchedURLClassLoader來載入/lib下面的jar,並以一個新執行緒啟動應用的Main函式。LaunchedURLClassLoader和普通的URLClassLoader的不同之處是,它提供了從Archive里加載.class的能力。結合Archive提供的getEntries函式,就可以獲取到Archive裡的Resource。當然裡面的細節還是很多的。
二、Spring Boot 實現
Spring Boot 推薦採用基於 Java 註解的配置方式,而不是傳統的 XML。只需要在主配置 Java 類上新增“@EnableAutoConfiguration”註解就可以啟用自動配置。Spring Boot 的自動配置功能是沒有侵入性的,只是作為一種基本的預設實現。開發人員可以通過定義其他 bean 來替代自動配置所提供的功能。比如當應用中定義了自己的資料來源 bean 時,自動配置所提供的 HSQLDB 就不會生效。這給予了開發人員很大的靈活性。既可以快速的建立一個可以立即執行的原型應用,又可以不斷的修改和調整以適應應用開發在不同階段的需要。可能在應用最開始的時候,嵌入式的記憶體資料庫(如 HSQLDB)就足夠了,在後期則需要換成 MySQL 等資料庫。Spring Boot 使得這樣的切換變得很簡單。
@EnableAutoConfiguration”註解的作用在於讓 Spring Boot 根據應用所宣告的依賴來對 Spring 框架進行自動配置,這就減少了開發人員的工作量。
@EnableAutoConfiguration註解會告知Boot要採用一種特定的方式來對應用進行配置。這種方法會將其他樣板式的配置均假設為框架預設的約定,因此能夠聚焦於如何儘快地使應用準備就緒以便執行起來。
Spring Boot 的這個配置優先順序看似複雜,其實是快速地修改配置引數值,而不需要重新打包和部署應用。
Spring的執行方式包括:
第一種方式:通過在UserController中加上@EnableAutoConfiguration開啟自動配置,然後通過SpringApplication.run(UserController.class);執行這個控制器;這種方式只執行一個控制器比較方便。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
@RestController
@EnableAutoConfiguration
public class Application {
@RequestMapping( "user" )
public static StringSayHello() {
return "Hello Word!" ;
}
public static voidmain(String[] args){
SpringApplication.run(Application. class ,args);
}
}
|
第二種方式:通過@Configuration+@ComponentScan開啟註解掃描並自動註冊相應的註解Bean。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@Configuration
@ComponentScan
@EnableAutoConfiguration
public class Application {
public static voidmain(String[] args){
SpringApplication.run(Application. class ,args);
}
|
將工程打包成獨立執行jar包,進入cmd 定位到專案目錄下然後執行 mvn clean package –DskipTests
然後會在專案的target資料夾下出現jar包例如(spingboot-demo-0.0.1-SNAPSHOT.jar)
然後再cmd 的C盤 使用者目錄下執行命令$ java –jar (jar包的目錄)E:\program\spingboot-demo\target\spingboot-demo-0.0.1-SNAPSHOT.jar
即可完成佈署。
三、Spring Boot佈署
在開發Spring Boot應用的過程中,Spring Boot直接執行public static void main()函式並啟動一個內嵌的應用伺服器(取決於類路徑上的以來是Tomcat還是jetty)來處理應用請求。對於生產環境,這樣的部署方式同樣有效,同時Spring Boot也支援傳統的部署方式——將war包放入應用伺服器中啟動執行.
A.內嵌應用伺服器
在使用Maven或Gradle構建Spring Boot應用的過程中,Spring Boot外掛提供了巨大的幫助,除了生命各類預定義的依賴,它還能夠構建可以直接執行的jar包——包含了所有的依賴以及內嵌應用伺服器。應用的分發也就變得非常簡單,任何人拿到了這個jar包,只需要簡單執行java -jar your.jar就可以啟動應用,無需任何構建工具、安裝過程以及應用伺服器。
B.內嵌應用伺服器配置
在生產環境中,應用伺服器需要各類配置,Spring Boot本身提供了一種非常簡單的配置機制——application.properties/application.yaml
1 2 3 4 5 |
server.port=8080 # 監聽埠
server.address= # 繫結的地址
server.session-timeout= #session有效時長
server.context-path= #預設為/
server.ssl.* #ssl相關配置
|
Tomcat
預設情況下,Spring Boot啟動的內嵌容器就是Tomcat,對於Tomcat有幾個非常重要的配置:
1 |
server.tomcat.basedir=/tmp
|
tomcat的baseDir,日誌、dump等檔案都存在於這個目錄中,一般是系統的臨時資料夾/tmp,但也可以按照自己的需求變更位置.
1 2 |
server.tomcat.access-log-pattern= # log pattern of the access log
server.tomcat.access-log-enabled= false # is access logging enabled
|
這兩個配置開啟Tomcat的Access日誌,並可以設定日誌格式。
Jetty
如果你不喜歡Tomcat,Jetty也是一個非常不錯的選擇。使用Jetty的方式也非常簡單——把tomcat依賴從Maven或Gradle中移除,加入Jetty內嵌容器的依賴:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependencies>
|
C.Java EE應用伺服器
除了內嵌容器的部署模式,Spring Boot也支援將應用部署至已有的Tomcat容器, 或JBoss, WebLogic等傳統Java EE應用伺服器。以Maven為例,首先需要將<packaging>從jar改成war,然後取消spring-boot-maven-plugin,然後修改Application.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Configuration
@ComponentScan
@EnableAutoConfiguration
public class Application extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(applicationClass, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(applicationClass);
}
private static Class<Application> applicationClass = Application. class ;
}
|
接下來打包應用,將生成的war包放入應用伺服器目錄即可。
D.使用外部配置檔案
在應用程式中有很多配置項,例如資料庫連線地址、日誌檔案位置、應用伺服器配置等等。為了安全與靈活性,我們推薦將Spring Boot的配置檔案放在生產環境的伺服器上,並嚴格控制訪問許可權。在執行應用時可以通過命令列引數指定配置檔案:
1 |
java -jar location_of_your_jar_file.jar --spring.config.location=location_of_your_config_file.properties
|