📢 本文由 gemini-2.5-flash 翻譯
設定
YAML、YML、Properties 檔案都可以進行設定,也可以透過 Java 系統屬性及命令列參數。
優先順序:命令列參數 > Java 系統屬性 > Properties 檔案 > YML 檔案 > YAML 檔案
命令列使用時,需要先執行 Maven 的包裝指令,然後在命令列執行。
1
2
3
4
5
| java -jar path_to_jar.jar
# Java 系統屬性,以埠號為例
java -Dserver.port=9000 -jar path_to_jar.jar
# 命令列參數,以埠號為例
java -jar path_to_jar.jar --server.port=9000
|
SpringBoot 專案進行包裝時需要引入 spring-boot-maven-plugin 外掛程式(如果基於官方樣板建立專案,會自動加入該外掛程式)。
Bean 管理
取得 Bean
對於預設的單例非延遲載入 Bean 而言,Spring 專案啟動時,會把 Bean 都建立好並放置在 IOC 容器中(例如加上 @Lazy 註解後,將會在第一次被使用時實例化)。
如果想主動取得這些 Bean,可以透過以下方式。
- 根據名稱取得
1
| Object getBean(String name)
|
- 根據類型取得
1
| <T> T getBean(Class<T> requiredType)
|
- 根據名稱及類型取得(類型轉換)
1
| <T> T getBean(String name, Class<T> requiredType)
|
為了使用此方法,需要先取得 IOC 容器物件。
1
2
3
4
5
6
7
8
9
10
11
12
13
| @Autowired
private ApplicationContext applicationContext; //IOC 容器物件
public void testGetBean(){
// 根據 Bean 的名稱取得
DeptController beanl = (DeptController) applicationContext.getBean("deptController");
// 根據 Bean 的類型取得
DeptController bean2 = applicationContext.getBean(DeptController.class);
// 根據 Bean 的名稱及類型取得
DeptController bean3 = applicationContext.getBean("deptController", DeptController.class);
}
|
Bean 作用域
Spring 支援五種作用域,其中後三種只在 Web 環境中生效。
| 作用域 | 描述 |
|---|
| singleton | 容器內同名稱的 Bean 只有一個實例(單例) |
| prototype | 每次使用該 Bean 時會建立新的實例(非單例) |
| request | 每個請求範圍內會建立新的實例 |
| session | 每個會話範圍內會建立新的實例 |
| application | 每個應用程式範圍內會建立新的實例 |
使用註解 @Scope 設定作用域
1
2
3
4
5
6
| // 設定為非單例
@Scope("prototype")
@RestController
public class xxxController{
}
|
在實際開發中,絕大部分的 Bean 都是單例的,也就是說絕大部分的 Bean 不需要設定 scope 屬性。
第三方 Bean
如果要管理的 Bean 物件來自於第三方(並非自訂),就無法使用 @Component 及衍生註解來宣告 Bean,這時就需要用到 @Bean 註解。例如解析 XML 檔案的 dom4j。
1
2
3
4
5
| <dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
|
依賴如上所示。
1
2
3
4
5
6
7
| @SpringBootApplication
public class xxxApplication{
@Bean // 將方法回傳值交給 IOC 容器管理,使其成為 IOC 容器的 Bean 物件
public SAXReader saxReader(){
return new SAXReader();
}
}
|
不過,若要管理第三方 Bean 物件,建議對這些 Bean 進行集中分類設定,可以透過 @Configuration 註解宣告一個設定類。
1
2
3
4
5
6
7
| @Configuration
public class CommonConfig {
@Bean
public SAXReader saxReader(){
return new SAXReader();
}
}
|
透過 @Bean 註解的 name 或 value 屬性可以宣告 Bean 的名稱,如果不指定,預設 Bean 的名稱就是方法名。如果第三方 Bean 需要依賴其他 Bean 物件,直接在 Bean 定義方法中設定形參即可,容器會根據類型自動組裝。
1
2
3
4
5
6
7
| @Configuration
public class CommonConfig {
@Bean
public SAXReader saxReader(XxService xxService){
return new SAXReader();
}
}
|
起始依賴
在開發中,若直接使用 Spring 需要引入相關依賴,並且保證版本匹配;而使用 SpringBoot 則只需要引入起始依賴即可。其原理是 Maven 的傳遞依賴,其他的依賴都會自動透過 Maven 的依賴機制傳遞進來。
自動配置
SpringBoot 的自動配置就是當 Spring 容器啟動後,一些設定類、Bean 物件會自動儲存到 IOC 容器中,不需要我們手動去宣告,從而簡化了開發,省去了繁瑣的設定操作。
設定類 @Configuration 的底層是 @Component,也是容器中的一個 Bean 物件。
在引入依賴之後,是如何將依賴 Jar 檔案中所定義的設定類以及 Bean 載入到 Spring IOC 容器中的呢?
@ComponentScan
使用 @ComponentScan 可以指定要掃描的套件,例如依賴匯入了 com.example 套件。
1
2
| @SpringBootApplication
@ComponentScan({"net.yexca","com.example"})
|
不過,當需要引入大量的第三方依賴時,上方需要設定大量的套件,而大面積的掃描效能也比較低。
@Import
可以匯入普通類別、設定類以及 ImportSelector 介面實作類。
普通類別
1
2
| @Import(TokenParser.class) //匯入普通類別
@SpringBootApplication
|
設定類
設定類內容
1
2
3
4
5
6
7
8
9
10
11
12
| @Configuration
public class HeaderConfig {
@Bean
public HeaderParser headerParser(){
return new HeaderParser();
}
@Bean
public HeaderGenerator headerGenerator(){
return new HeaderGenerator();
}
}
|
啟動類
1
2
| @Import(HeaderConfig.class) //匯入設定類
@SpringBootApplication
|
ImportSelector 介面實作類
ImportSelector 介面實作類內容
1
2
3
4
5
6
| public class MyImportSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//回傳值為字串陣列(陣列中封裝了具完整限定名稱的類別)
return new String[]{"com.example.HeaderConfig"};
}
}
|
啟動類
1
2
| @Import(MyImportSelector.class) //匯入 `ImportSelector` 介面實作類
@SpringBootApplication
|
@EnableXxxxx
上述 @Import 需要首先知道第三方依賴中有哪些設定類或 Bean 才行;而第三方依賴可以提供 @EnableXxxxx 註解,封裝 @Import 註解以提供一些常用 Bean,使用時只需要 @EnableXxxxx 註解即可。
例如上述 @Import 的設定類,可以封裝一個 @EnableHeaderConfig 註解。
1
2
3
4
5
| @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyImportSelector.class)//指定要匯入哪些 Bean 物件或設定類
public @interface EnableHeaderConfig {
}
|
然後只需要在啟動類加上 @EnableHeaderConfig 註解即可匯入相應的 Bean。
1
2
| @EnableHeaderConfig //使用第三方依賴提供的以 Enable 開頭的註解
@SpringBootApplication
|
此方法也是 SpringBoot 所採用的方式。
SpringBoot 的自動配置
在 @SpringBootApplication 註解裡有 @EnableAutoConfiguration,其中 @Import({AutoConfigurationImportSelector.class}) 匯入了 ImportSelector 介面的實作類 AutoConfigurationImportSelector.class。
在該實作類中覆寫了 selectImports() 方法。
1
2
3
4
5
6
7
8
9
| public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
// 取得自動配置的設定類資訊集合
AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
|
呼叫 getAutoConfigurationEntry() 方法取得了自動配置的設定類資訊集合。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// 取得在設定檔中設定的所有自動配置類別的集合
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
}
|
其中 getCandidateConfigurations(annotationMetadata, attributes) 方法會取得在設定檔中設定的所有自動配置類別的集合。
1
2
3
4
5
6
| protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));
ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
|
可以看到,它是取得 META-INF/spring.factories 和 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 檔案中設定類別的集合。
上述兩個檔案通常在引入的起始依賴中。
也就是說,當 SpringBoot 程式啟動時,就會載入設定檔當中所定義的設定類,並將這些設定類資訊(類別的完整限定名稱)封裝到 String 類型的陣列中,最終透過 @Import 註解將這些設定類全部載入到 Spring 的 IOC 容器中,交由 IOC 容器管理。
@Conditional
但是檔案中的設定類那麼多,每個 Bean 都會註冊到 IOC 容器中嗎?並非如此,使用 @Conditional 註解可以讓 Bean 物件依照條件進行組裝。
@Conditional 是一個父註解,底下有許多子註解。
@ConditionalOnClass
判斷環境中是否存在對應的位元碼檔案,才會將 Bean 註冊到 IOC 容器。
1
2
3
4
5
6
7
8
9
10
11
| @Configuration
public class HeaderConfig {
@Bean
//環境中存在指定的這個類別時,才會將該 Bean 加入 IOC 容器
@ConditionalOnClass(name="io.jsonwebtoken.Jwts")
public HeaderParser headerParser(){
return new HeaderParser();
}
}
|
上述 Bean 需要引入 JWT 權杖的依賴才會注入到 IOC 容器中。
1
2
3
4
5
6
| <!--JWT 權杖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
|
測試
1
2
3
4
5
6
7
8
9
10
11
| @SpringBootTest
public class AutoConfigurationTests {
@Autowired
private ApplicationContext applicationContext;
@Test
public void testHeaderParser(){
System.out.println(applicationContext.getBean(HeaderParser.class));
}
}
|
@ConditionalOnMissingBean
判斷環境中沒有對應的 Bean(類型或名稱),才會將 Bean 註冊到 IOC 容器。
1
2
3
4
5
6
7
8
9
10
11
| @Configuration
public class HeaderConfig {
@Bean
//不存在該類型的 Bean 時,才會將該 Bean 加入 IOC 容器
@ConditionalOnMissingBean
public HeaderParser headerParser(){
return new HeaderParser();
}
}
|
上述當 IOC 中沒有 HeaderConfig 類型的 Bean 時才會建立。
也可以在註解中指定其他 Bean 名稱。
1
2
3
4
5
6
7
8
9
10
11
| @Configuration
public class HeaderConfig {
@Bean
//不存在指定名稱的 Bean 時,才會將該 Bean 加入 IOC 容器
@ConditionalOnMissingBean(name="deptController2")
public HeaderParser headerParser(){
return new HeaderParser();
}
}
|
上例在不存在名稱為 deptController2 的 Bean 物件時,才會建立 HeaderConfig 物件並註冊到 IOC。
還可以指定類型。
1
2
3
4
5
6
7
8
9
10
11
| @Configuration
public class HeaderConfig {
@Bean
//不存在指定類型的 Bean 時,才會將 Bean 加入 IOC 容器
@ConditionalOnMissingBean(HeaderConfig.class)
public HeaderParser headerParser(){
return new HeaderParser();
}
}
|
上例執行時呼叫該 Bean 會引發 NoSuchBeanDefinitionException 異常,因為 @Configuration 中有 @Component,所以會自動建立 HeaderConfig 的 Bean,因此不會建立 HeaderParser 的 Bean。
@ConditionalOnProperty
判斷設定檔中存在對應屬性與值時,才會將 Bean 註冊到 IOC 容器。
設定檔
設定類
1
2
3
4
5
6
7
8
9
10
11
| @Configuration
public class HeaderConfig {
@Bean
//設定檔中存在指定屬性名與值時,才會將 Bean 加入 IOC 容器
@ConditionalOnProperty(name ="name",havingValue = "header")
public HeaderParser headerParser(){
return new HeaderParser();
}
}
|
自訂起始依賴
例如自訂一個阿里雲 OSS 的起始依賴。
首先是命名,SpringBoot 官方 Starter 命名為 spring-boot-starter-xxx,而第三方組織提供的則為 xxx-spring-boot-starter。
然後是模組,需要按照規範定義兩個模組:
- Starter 模組,進行依賴管理,將程式開發所需的依賴都定義在 Starter 起始依賴中。
- Autoconfigure 模組,用於自動配置。
定義好這兩個模組後,其他專案只需要引入起始依賴即可,自動配置模組會透過依賴傳遞引入。
模組 POM 檔案
aliyun-oss-spring-boot-starter 模組
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
27
28
29
30
31
32
33
34
| <?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-oss-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<!--引入autoconfigure模組-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-oss-spring-boot-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
|
aliyun-oss-spring-boot-autoconfigure 模組
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
| <?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-oss-spring-boot-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--引入Web起始依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--阿里雲OSS-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.15.1</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<!-- no more than 2.3.3-->
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.3</version>
</dependency>
</dependencies>
</project>
|
自動配置
AliOSSAutoConfiguration 類別
1
2
3
4
5
6
7
8
9
10
11
12
13
| @Configuration
//匯入 AliOSSProperties 類別,並交給 Spring IOC 管理
@EnableConfigurationProperties(AliOSSProperties.class)
public class AliOSSAutoConfiguration {
//建立 AliOSSUtils 物件,並交給 Spring IOC 容器
@Bean
public AliOSSUtils aliOSSUtils(AliOSSProperties aliOSSProperties){
AliOSSUtils aliOSSUtils = new AliOSSUtils();
aliOSSUtils.setAliOSSProperties(aliOSSProperties);
return aliOSSUtils;
}
}
|
AliOSSProperties 類別
1
2
3
4
5
6
7
8
9
10
11
12
13
| /*阿里雲 OSS 相關設定*/
@Data
@ConfigurationProperties(prefix = "aliyun.oss")
public class AliOSSProperties {
//區域
private String endpoint;
//身份 ID
private String accessKeyId ;
//身份密鑰
private String accessKeySecret ;
//儲存空間
private String bucketName;
}
|
AliOSSUtils 類別
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
27
28
| @Data
public class AliOSSUtils {
private AliOSSProperties aliOSSProperties;
/**
* 實作上傳圖片到 OSS
*/
public String upload(MultipartFile multipartFile) throws IOException {
// 取得上傳檔案的輸入流
InputStream inputStream = multipartFile.getInputStream();
// 避免檔案覆蓋
String originalFilename = multipartFile.getOriginalFilename();
String fileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf("."));
//上傳檔案到 OSS
OSS ossClient = new OSSClientBuilder().build(aliOSSProperties.getEndpoint(),
aliOSSProperties.getAccessKeyId(), aliOSSProperties.getAccessKeySecret());
ossClient.putObject(aliOSSProperties.getBucketName(), fileName, inputStream);
//檔案存取路徑
String url =aliOSSProperties.getEndpoint().split("//")[0] + "//" + aliOSSProperties.getBucketName() + "." + aliOSSProperties.getEndpoint().split("//")[1] + "/" + fileName;
// 關閉 ossClient
ossClient.shutdown();
return url;// 回傳上傳到 OSS 的路徑
}
}
|
新建自動設定檔 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
1
| com.aliyun.oss.AliOSSAutoConfiguration
|
使用
引入依賴
1
2
3
4
5
6
| <!--引入阿里雲 OSS 起始依賴-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-oss-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
|
上例阿里雲 OSS 相關設定需要從設定檔讀取。
1
2
3
4
5
6
7
| #設定阿里雲 OSS 參數
aliyun:
oss:
endpoint: your_oss_region
accessKeyId: your_key_id
accessKeySecret: your_key_secret
bucketName: your_bucker_name
|
測試
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| @RestController
public class UploadController {
@Autowired
private AliOSSUtils aliOSSUtils;
@PostMapping("/upload")
public String upload(MultipartFile image) throws Exception {
//上傳檔案到阿里雲 OSS
String url = aliOSSUtils.upload(image);
return url;
}
}
|