SpringBoot 基本應(yīng)用
約定優(yōu)于配置
約定優(yōu)于配置(Convention over Configuration),又稱按約定編程,是一種軟件設(shè)計(jì)范式。
本質(zhì)上是說,系統(tǒng)、類庫或框架應(yīng)該假定合理的默認(rèn)值,而非要求提供不必要的配置。比如說模型中有一個(gè)名為 User 的類,那么數(shù)據(jù)庫中對應(yīng)的表就會(huì)默認(rèn)命名為 User。只有在偏離這一個(gè)約定的時(shí)候,例如想要將該表命名為 person,才需要寫有關(guān)這個(gè)名字的配置。
比如平時(shí)架構(gòu)師搭建項(xiàng)目就是限制軟件開發(fā)隨便寫代碼,制定出一套規(guī)范,讓開發(fā)人員按統(tǒng)一的要求進(jìn)行開發(fā)編碼測試之類的,這樣就加強(qiáng)了開發(fā)效率與審查代碼效率。所以說寫代碼的時(shí)候就需要按要求命名,這樣統(tǒng)一規(guī)范的代碼就有良好的可讀性與維護(hù)性了。
約定優(yōu)于配置簡單來理解,就是遵循約定。
Spring Boot 是所有基于 Spring 開發(fā)的項(xiàng)目的起點(diǎn)。Spring Boot 的設(shè)計(jì)是為了盡可能快的跑起來 Spring 應(yīng)用程序并且盡可能減少配置文件。
SpringBoot 概念
Spring 優(yōu)缺點(diǎn)分析
優(yōu)點(diǎn):Spring 是 Java 企業(yè)版(Java Enterprise Edition,JEE,也稱 J2EE)的輕量級代替品。無需開發(fā)重量級的 Enterprise Java Bean - EJB,Spring 為企業(yè)級 Java 開發(fā)提供了一種相對簡單的方法,通過依賴注入和面向切面編程,用簡單的 Java 對象(Plain Old Java Object,POJO)實(shí)現(xiàn)了 EJB 的功能。
缺點(diǎn):雖然 Spring 的組件代碼是輕量級的,但它的配置卻是重量級的。一開始,Spring 用 XML 配置,而且是很多 XML 配 置。Spring 2.5 引入了基于注解的組件掃描,這消除了大量針對應(yīng)用程序自身組件的顯式 XML 配置。Spring 3.0 引入 了基于 Java 的配置,這是一種類型安全的可重構(gòu)配置方式,可以代替 XML。
所有這些配置都代表了開發(fā)時(shí)的損耗。因?yàn)樵谒伎?Spring 特性配置和解決業(yè)務(wù)問題之間需要進(jìn)行思維切換,所以編寫配置擠占了編寫應(yīng)用程序邏輯的時(shí)間。和所有框架一樣,Spring 實(shí)用,但與此同時(shí)它要求的回報(bào)也不少。
除此之外,項(xiàng)目的依賴管理也是一件耗時(shí)耗力的事情。在環(huán)境搭建時(shí),需要分析要導(dǎo)入哪些庫的坐標(biāo),而且還需要分析導(dǎo)入與之有依賴關(guān)系的其他庫的坐標(biāo),一旦選錯(cuò)了依賴的版本,隨之而來的不兼容問題就會(huì)嚴(yán)重阻礙項(xiàng)目的開發(fā)進(jìn)度。SSM 整合:Spring、Spring MVC、Mybatis、Spring-Mybatis 整合包、數(shù)據(jù)庫驅(qū)動(dòng),引入依賴的數(shù)量繁多、容易存在版本沖突。
Spring Boot 解決上述 Spring 的問題
SpringBoot 對上述 Spring 的缺點(diǎn)進(jìn)行的改善和優(yōu)化,基于約定優(yōu)于配置的思想,可以讓開發(fā)人員不必在配置與邏輯業(yè)務(wù)之間進(jìn)行思維的切換,全身心的投入到邏輯業(yè)務(wù)的代碼編寫中,從而大大提高了開發(fā)的效率,一定程度上縮短了項(xiàng)目周期。
- 起步依賴:
起步依賴本質(zhì)上是一個(gè) Maven 項(xiàng)目對象模型 (Project Object Model,POM),定義了對其他庫的傳遞依賴,這些東西加在一起即支持某項(xiàng)功能。
簡單的說,起步依賴就是將具備某種功能的依賴坐標(biāo)打包到一起,并提供一些默認(rèn)的功能。
- 自動(dòng)配置:
springboot 的自動(dòng)配置,指的是 springboot 會(huì)自動(dòng)將一些配置類的 bean 注冊進(jìn) ioc 容器,在需要的地方使用 @autowired 或者 @resource 等注解來使用它。
“自動(dòng)”的表現(xiàn)形式就是只需要引想用功能的包,相關(guān)的配置完全不用管,springboot 會(huì)自動(dòng)注入這些配置 bean,直接使用這些 bean 即可。
Springboot 可以簡單、快速、方便地搭建項(xiàng)目;對主流開發(fā)框架的無配置集成;極大提高了開發(fā)、部署效率。
SpringBoot 入門案例
案例需求:請求 Controller 中的方法,并將返回值響應(yīng)到頁面。
1. 依賴管理
pom.xml
<!--
所用的 springBoot 項(xiàng)目都會(huì)直接或者間接的繼承 spring-boot-starter-parent
1.指定項(xiàng)目的編碼格式為 UTF-8
2.指定 JDK 版本為 1.8
3.對項(xiàng)目依賴的版本進(jìn)行管理,當(dāng)前項(xiàng)目再引入其他常用的依賴時(shí)就需要再指定版本號(hào),避免版本沖突的問題
4.默認(rèn)的資源過濾和插件管理
-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
</parent>
<dependencies>
<!-- 引入 Spring Web 及 Spring MVC 相關(guān)的依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<!-- 可以將 project 打包為一個(gè)可以執(zhí)行的 jar -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
2. 啟動(dòng)類
com.zm.SpringBootDemo1Application
/**
* SpringBoot 的啟動(dòng)類通常放在二級包中,
* 比如:com.zm.SpringBootDemo1Application。
* 因?yàn)?SpringBoot 項(xiàng)目在做包掃描,
* 會(huì)掃描啟動(dòng)類所在的包及其子包下的所有內(nèi)容。
*
* @author ZM
* @since 2021-10-28 23:11
*/
@SpringBootApplication // 標(biāo)識(shí)當(dāng)前類為 SpringBoot 項(xiàng)目的啟動(dòng)類
public class SpringBootDemo1Application {
public static void main(String[] args) {
// 樣板代碼
SpringApplication.run(SpringBootDemo1Application.class, args);
}
}
3. Controller
com.zm.controller.HelloController
@RestController
@RequestMapping("/hello")
public class HelloController {
@RequestMapping("/boot")
public String helloSpringBoot() {
return "Hello Spring Boot";
}
}
SpringBoot 快速構(gòu)建
案例需求:請求 Controller 中的方法,并將返回值響應(yīng)到頁面。
1. 使用 Spring Initializr 方式構(gòu)建 Spring Boot 項(xiàng)目
本質(zhì)上說,Spring Initializr 是一個(gè) Web 應(yīng)用,它提供了一個(gè)基本的項(xiàng)目結(jié)構(gòu),能夠快速構(gòu)建一個(gè)基礎(chǔ)的 Spring Boot 項(xiàng)目。
注意使用快速方式創(chuàng)建 Spring Boot 項(xiàng)目時(shí),所在主機(jī)須在聯(lián)網(wǎng)狀態(tài)下;本質(zhì)上是在開發(fā)工具執(zhí)行各項(xiàng)參數(shù)后,由 Spring 提供的 URL 所對應(yīng)的服務(wù)器生成, IDEA 將服務(wù)器生成的 SpringBoot 項(xiàng)目下載到本地的工作空間中。
Project SDK 用于設(shè)置創(chuàng)建項(xiàng)目使用的 JDK 版本,這里,使用之前初始化設(shè)置好的 JDK 版本即可;在 Choose Initializr Service URL 下使用默認(rèn)的初始化服務(wù)地址 https://start.spring.io 進(jìn)行 Spring Boot 項(xiàng)目創(chuàng)建。
創(chuàng)建完成后的 pom 文件
<?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.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.zm</groupId>
<artifactId>springbootdemo2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springbootdemo2</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
創(chuàng)建完成后,可以刪除自動(dòng)生成的 .mvn、.gitignore、HELP.md、mvnw、mvnw.cmd。
創(chuàng)建好的 Spring Boot 項(xiàng)目結(jié)構(gòu)如圖:
/src/main/java/
? ? ? ?com.zm.Springbootdemo2Application - 項(xiàng)目主程序啟動(dòng)類
/src/main/resources/
? ? ?static - 靜態(tài)資源文件夾
? ? ?templates - 模板頁面文件夾
? ? ?application.properties - 全網(wǎng)配置文件
/src/test/java/
? ? ?com.zm.Springbootdemo2ApplicationTests - 項(xiàng)目測試類
使用 Spring Initializr 方式構(gòu)建的 Spring Boot 項(xiàng)目會(huì)默認(rèn)生成項(xiàng)目啟動(dòng)類、存放前端靜態(tài)資源和頁面的文件夾、編寫項(xiàng)目配置的配置文件以及進(jìn)行項(xiàng)目單元測試的測試類。
2. 創(chuàng)建一個(gè)用于 Web 訪問的 Controller
注意:確保項(xiàng)目啟動(dòng)類 Springbootdemo2Application 在 com.zm包下。
com.zm.controller.HelloController
@RestController
@RequestMapping("/hello")
public class HelloController {
@RequestMapping("/boot")
public String hello() {
return "What's up! Spring Boot!";
}
}
3. 運(yùn)行項(xiàng)目
在 application.properties 中修改 tomcat 的默認(rèn)端口號(hào):
server.port=8888
運(yùn)行主程序啟動(dòng)類 Springbootdemo2Application,項(xiàng)目啟動(dòng)成功后,在控制臺(tái)上會(huì)發(fā)現(xiàn) Spring Boot 項(xiàng)目默認(rèn)啟動(dòng)的端口號(hào)為 8888,此時(shí),可以在瀏覽器上訪問 http://localhost:8888/hello/boot。
頁面輸出的內(nèi)容是 “What's up! Spring Boot!”,至此,構(gòu)建 Spring Boot 項(xiàng)目就完成了。
單元測試與熱部署
單元測試
開發(fā)中,每當(dāng)完成一個(gè)功能接口或業(yè)務(wù)方法的編寫后,通常都會(huì)借助單元測試驗(yàn)證該功能是否正確。
1. 添加 spring-boot-starter-test 測試依賴啟動(dòng)器,在項(xiàng)目的 pom.xml 文件中添加 spring-boot-starter-test 測試依賴啟動(dòng)器,示例代碼如下 :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
注意:使用 Spring Initializr 方式搭建的 Spring Boot 項(xiàng)目,會(huì)自動(dòng)加入 spring-boot-starter-test 測試依賴啟動(dòng)器,無需再手動(dòng)添加。
2. 編寫單元測試類和測試方法:
/**
* SpringJUnit4ClassRunner.class: Spring 運(yùn)行環(huán)境
* JUnit4.class: JUnit 運(yùn)行環(huán)境
* SpringRunner.class: Spring Boot 運(yùn)行環(huán)境
*/
@RunWith(SpringRunner.class) // @RunWith: 運(yùn)行器
@SpringBootTest // 標(biāo)記為當(dāng)前類為 SpringBoot 測試類,加載項(xiàng)目的 ApplicationContext 上下文環(huán)境
class Springbootdemo2ApplicationTests {
@Autowired
private HelloController helloController;
@Test
void contextLoads() {
String result = helloController.hello();
System.out.println(result);
}
}
上述代碼中,先使用 @Autowired 注解注入了 HelloController 實(shí)例對象,然后在 contextLoads() 方法中調(diào)用了 HelloController 類中對應(yīng)的請求控制方法 hello(),并輸出打印結(jié)果。
熱部署
在開發(fā)過程中,通常會(huì)對一段業(yè)務(wù)代碼不斷地修改測試,在修改之后往往需要重啟服務(wù),有些服務(wù)需要加載很久才能啟動(dòng)成功,這種不必要的重復(fù)操作極大的降低了程序開發(fā)效率。為此,Spring Boot 框架專門提供了進(jìn)行熱部署的依賴啟動(dòng)器,用于進(jìn)行項(xiàng)目熱部署,而無需手動(dòng)重啟項(xiàng)目 。
熱部署:在修改完代碼之后,不需要重新啟動(dòng)容器,就可以實(shí)現(xiàn)更新。
使用步驟:
1)添加 spring-boot-devtools 熱部署依賴啟動(dòng)器。
在 Spring Boot 項(xiàng)目進(jìn)行熱部署測試之前,需要先在項(xiàng)目的 pom.xml 文件中添加 spring-boot-devtools 熱部署依賴啟動(dòng)器:
<!-- 引入熱部署依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
由于使用的是 IDEA 開發(fā)工具,添加熱部署依賴后可能沒有任何效果,接下來還需要針對 IDEA 開發(fā)工具進(jìn)行熱部署相關(guān)的功能設(shè)置。
2)開啟 IDEA 的自動(dòng)編譯。
選擇 IDEA 工具界面的 【File】->【Settings】 選項(xiàng),打開 Compiler 面板設(shè)置頁面。
選擇 Build 下的 Compiler 選項(xiàng),在右側(cè)勾選 “Build project automatically” 選項(xiàng)將項(xiàng)目設(shè)置為自動(dòng)編譯,單擊 【Apply】->【OK】 按鈕保存設(shè)置。
3)開啟 IDEA 的在項(xiàng)目運(yùn)行中自動(dòng)編譯的功能。
在項(xiàng)目任意頁面中使用組合快捷鍵 “Ctrl+Shift+Alt+/” 打開 Maintenance 選項(xiàng)框,選中并打開 Registry 頁面。
列表中找到 “compiler.automake.allow.when.app.running”,將該選項(xiàng)后的 Value 值勾選,用于指定 IDEA 工具在程序運(yùn)行過程中自動(dòng)編譯,最后單擊 【Close】 按鈕完成設(shè)置。
熱部署效果測試
啟動(dòng) Springbootdemo2Application http://localhost:8888/hello/boot
頁面原始輸出的內(nèi)容是 “What's up! Spring Boot!”。
為了測試配置的熱部署是否有效,接下來,在不關(guān)閉當(dāng)前項(xiàng)目的情況下,將 HelloController 類中的請求處理方法 hello() 的返回值修改為 “Hello! Spring Boot!” 并保存,查看控制臺(tái)信息會(huì)發(fā)現(xiàn)項(xiàng)目能夠自動(dòng)構(gòu)建和編譯,說明項(xiàng)目熱部署生效。
再次刷新瀏覽器,輸出了 “Hello! Spring Boot!”,說明項(xiàng)目熱部署配置成功。
全局配置文件
全局配置文件能夠?qū)σ恍┠J(rèn)配置值進(jìn)行修改。Spring Boot 使用一個(gè) application.properties 或者 application.yaml 的文件作為全局配置文件,該文件存放在 src/main/resource 目錄或者類路徑的 /config,一般會(huì)選擇 resource 目錄。
Spring Boot 配置文件的命名及其格式:
- application.properties
- application.yaml
- application.yml
application.properties 配置文件
引入相關(guān)依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.6</version>
</dependency>
使用 Spring Initializr 方式構(gòu)建 Spring Boot 項(xiàng)目時(shí),會(huì)在 resource 目錄下自動(dòng)生成一個(gè)空的 application.properties 文件,Spring Boot 項(xiàng)目啟動(dòng)時(shí)會(huì)自動(dòng)加載 application.properties 文件。
# 修改 tomcat 的版本號(hào)
server.port=8080
# 定義數(shù)據(jù)庫的連接信息 JdbcTemplate
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/zm01
spring.datasource.username=root
spring.datasource.password=password
可以在 application.properties 文件中定義 Spring Boot 項(xiàng)目的相關(guān)屬性,當(dāng)然,這些相關(guān)屬性可以是系統(tǒng)屬性、環(huán)境變量、命令參數(shù)等信息,也可以是自定義配置文件名稱和位置。
預(yù)先準(zhǔn)備了兩個(gè)實(shí)體類文件,后續(xù)會(huì)演示將 application.properties 配置文件中的自定義配置屬性注入到 Person 實(shí)體類的對應(yīng)屬性中。
- 先在項(xiàng)目的 com.zm包下創(chuàng)建一個(gè) pojo 包,并在該包下創(chuàng)建兩個(gè)實(shí)體類 Pet 和 Person。
/**
* 寵物類
*
* @author ZM
* @since 2021-10-29 1:03
*/
public class Pet {
// 品種
private String type;
// 名稱
private String name;
// setter and getter, toString ...
}
/**
* 人類
*
* @author ZM
* @since 2021-10-29 1:04
*/
@Component
/**
* 將配置文件中所有以 person 開頭的配置信息注入當(dāng)前類中
* 前提 1:必須保證配置文件中 person.xx 與當(dāng)前 Person 類的屬性名一致
* 前提 2:必須保證當(dāng)前 Person 中的屬性都具有 set 方法
*/
@ConfigurationProperties(prefix = "person")
public class Person {
// id
private int id;
// 名稱
private String name;
// 愛好
private List hobby;
// 家庭成員
private String[] family;
private Map map;
// 寵物
private Pet pet;
// setter and getter, toString ...
}
@ConfigurationProperties(prefix = "person") 注解的作用是將配置文件中以 person 開頭的屬性值通過 setXX() 方法注入到實(shí)體類對應(yīng)屬性中。
@Component 注解的作用是將當(dāng)前注入屬性值的 Person 類對象作為 Bean 組件放到 Spring 容器中,只有這樣才能被 @ConfigurationProperties 注解進(jìn)行賦值。
- 打開項(xiàng)目的 resources 目錄下的 application.properties 配置文件,在該配置文件中編寫需要對 Person 類設(shè)置的配置屬性。
# 自定義配置信息注入到 Person 對象中
person.id=100
person.name=張三
## list
person.hobby=看書,寫代碼,吃飯
## String[]
person.family=兄弟,父母
## map
person.map.k1=v1
person.map.k2=v2
## pet 對象
person.pet.type=dog
person.pet.name=旺財(cái)
- 查看 application.properties 配置文件是否正確,同時(shí)查看屬性配置效果,打開通過 IDEA 工具創(chuàng)建的項(xiàng)目測試類,在該測試類中引入 Person 實(shí)體類 Bean,并進(jìn)行輸出測試。
@RunWith(SpringRunner.class)
@SpringBootTest
class Springbootdemo2ApplicationTests {
@Autowired
private Person person;
@Test
void showPersonInfo() {
System.out.println(person);
}
}
可以看出,測試方法 showPersonInfo() 運(yùn)行成功,同時(shí)正確打印出了 Person 實(shí)體類對象。至此,說明 application.properties 配置文件屬性配置正確,并通過相關(guān)注解自動(dòng)完成了屬性注入。
- 解決瀏覽器請求出現(xiàn)中文亂碼問題。
調(diào)整文件編碼格式:Settings -> Editor -> File Encodings -> 確保 Global Encoding 和 Project Encoding 為 UTF-8,修改 Default ecoding for properties files 為 UTF-8 并勾選 Transparent native-to-ascii conversion。
application.properties 文件中增加配置:
# 解決中文亂碼
server.tomcat.uri-encoding=UTF-8
spring.http.encoding.force=true
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
application.yaml 配置文件
YAML 文件格式是 Spring Boot 支持的一種 JSON 文件格式,相較于傳統(tǒng)的 Properties 配置文件,YAML 文件以數(shù)據(jù)為核心,是一種更為直觀且容易被電腦識(shí)別的數(shù)據(jù)序列化格式。application.yaml 配置文件的工作原理和 application.properties 是一樣的,只不過 yaml 格式配置文件看起來更簡潔一些。
- YAML 文件的擴(kuò)展名可以使用 .yml 或者 .yaml。
- application.yml 文件使用 key: value 格式配置屬性,使用縮進(jìn)控制層級關(guān)系。
SpringBoot 的三種配置文件是可以共存的:application.properties、application.yaml、application.yml。
1)value 值為普通數(shù)據(jù)類型(例如數(shù)字、字符串、布爾等)
當(dāng) YAML 配置文件中配置的屬性值為普通數(shù)據(jù)類型時(shí),可以直接配置對應(yīng)的屬性值,同時(shí)對于字符串類型的屬性值,不需要額外添加引號(hào),示例代碼如下:
server:
port: 8080
servlet:
context-path: /hello
2)value 值為數(shù)組和單列集合
當(dāng) YAML 配置文件中配置的屬性值為數(shù)組或單列集合類型時(shí),主要有兩種書寫方式:縮進(jìn)式寫法和行內(nèi)式寫法。
其中,縮進(jìn)式寫法還有兩種表示形式,示例代碼如下:
person:
hobby:
- play
- read
- sleep
或者使用如下示例形式:
person:
hobby:
play,
read,
sleep
上述代碼中,在 YAML 配置文件中通過兩種縮進(jìn)式寫法對 person 對象的單列集合(或數(shù)組)類型的愛好 hobby 賦值為 play、read 和 sleep。其中一種形式為 “- 屬性值”,另一種形式為多個(gè)屬性值之前加英文逗號(hào)分隔;注意,最后一個(gè)屬性值后不要加逗號(hào)。
person:
hobby: [play,read,sleep]
通過上述示例對比發(fā)現(xiàn),YAML 配置文件的行內(nèi)式寫法更加簡明、方便。另外,包含屬性值的中括號(hào) “[]” 還可以進(jìn)一步省略,在進(jìn)行屬性賦值時(shí),程序會(huì)自動(dòng)匹配和校對。
3)value 值為 Map 集合和對象
當(dāng) YAML 配置文件中配置的屬性值為 Map 集合或?qū)ο箢愋蜁r(shí),YAML 配置文件格式同樣可以分為兩種書寫方式 - 縮進(jìn)式寫法和行內(nèi)式寫法。
其中,縮進(jìn)式寫法的示例代碼如下:
person:
map:
k1: v1
k2: v2
對應(yīng)的行內(nèi)式寫法示例代碼如下:
person:
map: {k1: v1,k2: v2}
在YAML配置文件中,配置的屬性值為Map集合或?qū)ο箢愋蜁r(shí),縮進(jìn)式寫法的形式按照YAML文件格式編寫即可,而行內(nèi)式寫法的屬性值要用大括號(hào)“{}”包含。
接下來,在 Properties 配置文件演示案例基礎(chǔ)上,注釋掉 Properties 中跟 Person 相關(guān)的注入,然后通過配置 application.yaml 配置文件對 Person 對象進(jìn)行賦值,具體使用如下。
在項(xiàng)目的 resources 目錄下,新建一個(gè) application.yaml 配置文件,在該配置文件中編寫為 Person 類設(shè)置的配置屬性:
person:
id: 1000
name: 張三
hobby:
- 跑步
- 瑜伽
- 游泳
family:
- 張小明
- 李小紅
map:
k1: 這是 k1 對應(yīng)的 value
k2: 這是 k2 對應(yīng)的 value
pet:
type: dog
name: 金毛
再次執(zhí)行測試。
可以看出,測試方法 showPersonInfo() 同樣運(yùn)行成功,并正確打印出了 Person 實(shí)體類對象。
需要說明的是,本次使用 application.yaml 配置文件進(jìn)行測試時(shí)需要提前將 application.properties 配置文件中編寫的配置注釋,這是因?yàn)?application.properties 配置文件會(huì)覆蓋 application.yaml 配置文件。
配置文件屬性值的注入
從 spring-boot-starter-parent 的 pom 文件可以知道配置文件的優(yōu)先級從低到高如下:
<includes>
<include>**/application*.yml</include>
<include>**/application*.yaml</include>
<include>**/application*.properties</include>
</includes>
使用 Spring Boot 全局配置文件設(shè)置屬性時(shí):
如果配置屬性是 Spring Boot 已有屬性,例如服務(wù)端口 server.port,那么 Spring Boot 內(nèi)部會(huì)自動(dòng)掃描并讀取這些配置文件中的屬性值并覆蓋默認(rèn)屬性。
如果配置的屬性是用戶自定義屬性,例如剛剛自定義的 Person 實(shí)體類屬性,還必須在程序中注入這些配置屬性方可生效。
Spring Boot 支持多種注入配置文件屬性的方式,下面來介紹如何使用注解 @ConfigurationProperties 和 @Value 注入屬性。
使用 @ConfigurationProperties 注入屬性
Spring Boot 提供的 @ConfigurationProperties 注解用來快速、方便地將配置文件中的自定義屬性值批量注入到某個(gè) Bean 對象的多個(gè)對應(yīng)屬性中。假設(shè)現(xiàn)在有一個(gè)配置文件,如果使用 @ConfigurationProperties 注入配置文件的屬性,示例代碼如下:
@Component
/**
* 將配置文件中所有以 person 開頭的配置信息注入當(dāng)前類中
* 前提 1:必須保證配置文件中 person.xx 與當(dāng)前 Person 類的屬性名一致
* 前提 2:必須保證當(dāng)前 Person 中的屬性都具有 set 方法
*/
@ConfigurationProperties(prefix = "person")
public class Person {
private int id;
private String name;
private List hobby;
private String[] family;
private Map map;
private Pet pet;
// setter and getter, toString ...
}
上述代碼使用 @Component 和 @ConfigurationProperties(prefix = “person”) 將配置文件中的每個(gè)屬性映射到 person 類組件中。
使用 @Value 注入屬性
@Value 注解是 Spring 框架提供的,用來讀取配置文件中的屬性值并逐個(gè)注入到 Bean 對象的對應(yīng)屬性中,Spring Boot 框架從 Spring 框架中對 @Value 注解進(jìn)行了默認(rèn)繼承,所以在 Spring Boot 框架中還可以使用該注解讀取和注入配置文件屬性值。使用 @Value 注入屬性的示例代碼如下:
@Component
public class person{
@Value("${person.id}")
private int id;
// getter setter toString ...
}
上述代碼中,使用 @Component 和 @Value 注入 Person 實(shí)體類的id屬性。其中,@Value 不僅可以將配置文件的屬性注入 Person 的 id 屬性,還可以直接給 id 屬性賦值,這點(diǎn)是@ConfigurationProperties 不支持的。
1)在 com.zm.pojo 包下新創(chuàng)建一個(gè)實(shí)體類 Student,并使用 @Value 注解注入屬性:
@Component
public class Student {
@Value("${person.id}")
private String number;
@Value("${person.name}")
private String name;
// getter setter toString ...
}
Student 類使用 @Value 注解將配置文件的屬性值讀取和注入。
從上述示例代碼可以看出,使用 @Value 注解方式需要對每一個(gè)屬性注入設(shè)置,同時(shí)又免去了屬性的 setXX() 方法。
2)再次打開測試類進(jìn)行測試:
@RunWith(SpringRunner.class)
@SpringBootTest
class Springbootdemo2ApplicationTests {
@Autowired
private Student student;
@Test
public void showStudentInfo(){
System.out.println(student);
}
}
可以看出,測試方法 showStudentInfo() 運(yùn)行成功,同時(shí)正確打印出了 Student 實(shí)體類對象。需要說明的是,本示例中只是使用 @Value 注解對實(shí)例中 Student 對象的普通類型屬性進(jìn)行了賦值演示,而 @Value 注解對于包含 Map 集合、對象以及 YAML 文件格式的行內(nèi)式寫法的配置文件的屬性注入都不支持,如果賦值會(huì)出現(xiàn)錯(cuò)誤。
自定義配置
Spring Boot 免除了項(xiàng)目中大部分的手動(dòng)配置,對于一些特定情況,可以通過修改全局配置文件以適應(yīng)具體生產(chǎn)環(huán)境,可以說,幾乎所有的配置都可以寫在 application.yml 文件中,Spring Boot 會(huì)自動(dòng)加載全局配置文件從而免除手動(dòng)加載的煩惱。但是,如果自定義配置文件,Spring Boot 是無法識(shí)別這些配置文件的,此時(shí)就需要手動(dòng)加載。
使用 @PropertySource 加載配置文件
對于這種加載自定義配置文件的需求,可以使用 @PropertySource 注解來實(shí)現(xiàn)。@PropertySource 注解用于指定自定義配置文件的具體位置和名稱。
如果需要將自定義配置文件中的屬性值注入到對應(yīng)類的屬性中,可以使用 @ConfigurationProperties 或者 @Value 注解進(jìn)行屬性值注入。
1)打開 Spring Boot 項(xiàng)目的 resources 目錄,在項(xiàng)目的類路徑下新建一個(gè) my.properties 自定義配置文件,在該配置文件中編寫需要設(shè)置的配置屬性。
# 對實(shí)體類對象 Product 進(jìn)行屬性配置
product.id=99
product.name=小米
2)在 com.zm.pojo 包下新創(chuàng)建一個(gè)配置類 Product,提供 my.properties 自定義配置文件中對應(yīng)的屬性,并根據(jù) @PropertySource 注解的使用進(jìn)行相關(guān)配置。
@Component
@PropertySource("classpath:my.properties")
@ConfigurationProperties(prefix = "product")
public class Product {
private int id;
private String name;
// getter setter toString ...
}
主要是一個(gè)自定義配置類,通過相關(guān)注解引入了自定義的配置文件,并完成了自定義屬性值的注入。針對示例中的幾個(gè)注解,具體說明如下:
-
@PropertySource("classpath:my.properties")注解指定了自定義配置文件的位置和名稱,此示例表示自定義配置文件為 classpath 類路徑下的 my.properties 文件。 -
@ConfigurationProperties(prefix = "product")注解將上述自定義配置文件 my.properties 中以 product 開頭的屬性值注入到該配置類屬性中。
3)進(jìn)行測試。
@RunWith(SpringRunner.class)
@SpringBootTest
class Springbootdemo2ApplicationTests {
@Autowired
private Product product;
@Test
public void showProductInfo() {
System.out.println(product);
}
}
使用 @Configuration 編寫自定義配置類
在 Spring Boot 框架中,推薦使用配置類的方式向容器中添加和配置組件。
在 Spring Boot 框架中,通常使用 @Configuration 注解定義一個(gè)配置類,Spring Boot 會(huì)自動(dòng)掃描和識(shí)別配置類,從而替換傳統(tǒng) Spring 框架中的 XML 配置文件。
當(dāng)定義一個(gè)配置類后,還需要在類中的方法上使用 @Bean 注解進(jìn)行組件配置,將方法的返回對象注入到 Spring 容器中,并且組件名稱默認(rèn)使用的是方法名,當(dāng)然也可以使用 @Bean 注解的 name 或 value 屬性自定義組件的名稱。
1)在項(xiàng)目下新建一個(gè) com.zm.config 包,并在該包下新創(chuàng)建一個(gè)類 MyService,該類中不需要編寫任何代碼:
package com.zm.service;
public class MyService {
}
創(chuàng)建了一個(gè)空的 MyService 類,而該類目前沒有添加任何配置和注解,因此還無法正常被 Spring Boot 掃描和識(shí)別。
2)在項(xiàng)目的 com.zm.config 包下,新建一個(gè)類 MyConfig,并使用 @Configuration 注解將該類聲明一個(gè)配置類,內(nèi)容如下:
@Configuration // 標(biāo)識(shí)當(dāng)前類是一個(gè)配置類,SpringBoot 會(huì)掃描該類,將所有標(biāo)識(shí) @Bean 注解的方法的返回值注入的容器中
public class MyConfig {
@Bean // 注入的名稱就是方法的名稱,注入的類型就是返回值的類型
public MyService myService(){
return new MyService();
}
@Bean("service_")
public MyService myService2(){
return new MyService();
}
}
MyConfig 是 @Configuration 注解聲明的配置類(類似于聲明了一個(gè) XML 配置文件),該配置類會(huì)被 Spring Boot 自動(dòng)掃描識(shí)別;使用 @Bean 注解的 myService() 方法,其返回值對象會(huì)作為組件添加到了 Spring 容器中(類似于 XML 配置文件中的標(biāo)簽配置),并且該組件的 id 默認(rèn)是方法名 myService。
3)測試類:
@RunWith(SpringRunner.class)
@SpringBootTest
class Springbootdemo2ApplicationTests {
@Autowired
private ApplicationContext applicationContext;
@Test
public void testConfig(){
System.out.println(applicationContext.containsBean("myService"));
System.out.println(applicationContext.containsBean("service_"));
}
}
上述代碼中,先通過 @Autowired 注解引入了 Spring 容器實(shí)例 ApplicationContext,然后在測試方法 testConfig() 中測試查看該容器中是否包括 id 為 myService 和 service_ 的組件。
從測試結(jié)果可以看出,測試方法 testConfig() 運(yùn)行成功,顯示運(yùn)行結(jié)果為 true,表示 Spirng 的 IOC 容器中也已經(jīng)包含了 id 為 myService 和 service_的實(shí)例對象組件,說明使用自定義配置類的形式完成了向 Spring 容器進(jìn)行組件的添加和配置。
SpringBoot 原理深入及源碼剖析
依賴管理
在 Spring Boot 入門程序中,項(xiàng)目 pom.xml 文件有兩個(gè)核心依賴,分別是 spring-boot-starter-parent 和 spring-boot-starter-web,關(guān)于這兩個(gè)依賴的相關(guān)介紹具體如下:
-
spring-boot-starter-parent 依賴
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/>
</parent>
上述代碼中,將 spring-boot-starter-parent 依賴作為 Spring Boot 項(xiàng)目的統(tǒng)一父項(xiàng)目依賴管理,并將項(xiàng)目版本號(hào)統(tǒng)一為 2.2.2.RELEASE,該版本號(hào)根據(jù)實(shí)際開發(fā)需求是可以修改的。
使用 “Ctrl+鼠標(biāo)左鍵” 進(jìn)入并查看 spring-boot-starter-parent 底層源文件,發(fā)現(xiàn) spring-boot- starter-parent 的底層有一個(gè)父依賴 spring-boot-dependencies,核心代碼具體如下
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath>../../spring-boot-dependencies</relativePath>
</parent>
繼續(xù)查看 spring-boot-dependencies 底層源文件,核心代碼具體如下:
<properties>
<activemq.version>5.15.11</activemq.version>
...
<solr.version>8.2.0</solr.version>
<spring-amqp.version>2.2.2.RELEASE</spring-amqp.version>
<spring-batch.version>4.2.1.RELEASE</spring-batch.version>
<spring-cloud-connectors.version>2.0.7.RELEASE</spring-cloud-connectors.version>
<spring-data-releasetrain.version>Moore-SR3</spring-data-releasetrain.version>
<spring-framework.version>5.2.2.RELEASE</spring-framework.version>
<spring-hateoas.version>1.0.2.RELEASE</spring-hateoas.version>
<spring-integration.version>5.2.2.RELEASE</spring-integration.version>
<spring-kafka.version>2.3.4.RELEASE</spring-kafka.version>
<spring-ldap.version>2.3.2.RELEASE</spring-ldap.version>
<spring-restdocs.version>2.0.4.RELEASE</spring-restdocs.version>
<spring-retry.version>1.2.4.RELEASE</spring-retry.version>
<spring-security.version>5.2.1.RELEASE</spring-security.version>
<spring-session-bom.version>Corn-RELEASE</spring-session-bom.version>
<spring-ws.version>3.0.8.RELEASE</spring-ws.version>
<sqlite-jdbc.version>3.28.0</sqlite-jdbc.version>
<sun-mail.version>${jakarta-mail.version}</sun-mail.version>
<thymeleaf.version>3.0.11.RELEASE</thymeleaf.version>
<thymeleaf-extras-data-attribute.version>2.0.1</thymeleaf-extras-data-attribute.version>
...
</properties>
從 spring-boot-dependencies 底層源文件可以看出,該文件通過標(biāo)簽對一些常用技術(shù)框架的依賴文件進(jìn)行了統(tǒng)一版本號(hào)管理,例如 activemq、spring、tomcat 等,都有與 Spring Boot 2.2.2 版本相匹配的版本,這也是 pom.xml 引入依賴文件不需要標(biāo)注依賴文件版本號(hào)的原因。
需要說明的是,如果 pom.xml 引入的依賴文件不是 spring-boot-starter-parent 管理的,那么在 pom.xml 引入依賴文件時(shí),需要使用標(biāo)簽指定依賴文件的版本號(hào)。
-
pring-boot-starter-web 依賴
spring-boot-starter-parent 父依賴啟動(dòng)器的主要作用是進(jìn)行版本統(tǒng)一管理;項(xiàng)目運(yùn)行依賴的 JAR 包是從 Spring Boot Starters 中導(dǎo)入。
查看 spring-boot-starter-web 依賴文件源碼,核心代碼具體如下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.2.2.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.2.2.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.2.2.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>2.2.2.RELEASE</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<artifactId>tomcat-embed-el</artifactId>
<groupId>org.apache.tomcat.embed</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.2.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.2.RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
從上述代碼可以發(fā)現(xiàn),spring-boot-starter-web 依賴啟動(dòng)器的主要作用是提供 Web 開發(fā)場景所需的底層所有依賴。
正是如此,在 pom.xml 中引入 spring-boot-starter-web 依賴啟動(dòng)器時(shí),就可以實(shí)現(xiàn) Web 場景開發(fā),而不需要額外導(dǎo)入 Tomcat 服務(wù)器以及其他 Web 依賴文件等。當(dāng)然,這些引入的依賴文件的版本號(hào)還是由 spring-boot-starter-parent 父依賴進(jìn)行的統(tǒng)一管理。
Spring Boot Starters:
https://mvnrepository.com/search?q=starter
Spring Boot 除了提供有上述介紹的 Web 依賴啟動(dòng)器外,還提供了其他許多開發(fā)場景的相關(guān)依賴,可以打開 Spring Boot 官方文檔,搜索 “Starters” 關(guān)鍵字查詢場景依賴啟動(dòng)器。
需要說明的是,Spring Boot 官方并不是針對所有場景開發(fā)的技術(shù)框架都提供了場景啟動(dòng)器,例如數(shù)據(jù)庫操作框架 MyBatis、阿里巴巴的 Druid 數(shù)據(jù)源等,Spring Boot 官方就沒有提供對應(yīng)的依賴啟動(dòng)器。為了充分利用 Spring Boot 框架的優(yōu)勢,在 Spring Boot 官方?jīng)]有整合這些技術(shù)框架的情況下,MyBatis、Druid 等技術(shù)框架所在的開發(fā)團(tuán)隊(duì)主動(dòng)與 Spring Boot 框架進(jìn)行了整合,實(shí)現(xiàn)了各自的依賴啟動(dòng)器,例如 mybatis-spring-boot-starter、druid-spring-boot-starter 等。在 pom.xml 文件中引入這些第三方的依賴啟動(dòng)器時(shí),切記要配置對應(yīng)的版本號(hào)。
自動(dòng)配置
概念:能夠在添加 jar 包依賴的時(shí)候,自動(dòng)配置一些組件的相關(guān)配置,無需手動(dòng)配置或者只需要少量手動(dòng)配置就能運(yùn)行編寫的項(xiàng)目。
Spring Boot 應(yīng)用的啟動(dòng)入口是 @SpringBootApplication 注解標(biāo)注類中的 main() 方法。
@SpringBootApplication:SpringBoot 應(yīng)用標(biāo)注在某個(gè)類上說明這個(gè)類是 SpringBoot 的主配置類, SpringBoot 就應(yīng)該運(yùn)行這個(gè)類的 main() 方法啟動(dòng) SpringBoot 應(yīng)用。
進(jìn)入到 @SpringBootApplication 內(nèi):
// 注解的適用范圍, Type 表示注解可以描述在類、接口、注解或枚舉中
@Target(ElementType.TYPE)
// 表示注解的生命周期,Runtime 運(yùn)行時(shí)
@Retention(RetentionPolicy.RUNTIME)
// 表示注解可以記錄在 javadoc 中
@Documented
// 表示可以被子類繼承該注解
@Inherited
// 標(biāo)明該類為 Spring Boot 配置類
@SpringBootConfiguration
// 啟動(dòng)自動(dòng)配置功能
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
// 根據(jù) class 來排除特定的類,使其不能加入 spring 容器,傳入?yún)?shù) value 類型是 class 類型。
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
// 根據(jù) classname 來排除特定的類,使其不能加入 spring 容器,傳入?yún)?shù) value 類型是class的全類名字符串?dāng)?shù)組。
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
// 指定掃描包,參數(shù)是包名的字符串?dāng)?shù)組。
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
// 掃描特定的包,參數(shù)類似是 Class 類型數(shù)組。
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
...
}
從上述源碼可以看出,@SpringBootApplication 注解是一個(gè)組合注解,前面 4 個(gè)是注解的元數(shù)據(jù)信息, 主要看后面 3 個(gè)注解:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan 三個(gè)核心注解。
-
@SpringBootConfiguration 注解
@SpringBootConfiguration: SpringBoot 的配置類,標(biāo)注在某個(gè)類上,表示這是一個(gè) SpringBoot 的配置類。
查看 @SpringBootConfiguration 注解源碼:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
...
}
從上述源碼可以看出,@SpringBootConfiguration 注解內(nèi)部有一個(gè)核心注解 @Configuration,該注解是 Spring 框架提供的,表示當(dāng)前類為一個(gè)配置類(XML 配置文件的注解表現(xiàn)形式),并可以被組件掃描器掃描。由此可見,@SpringBootConfiguration 注解的作用與 @Configuration 注解相同,都是標(biāo)識(shí)一個(gè)可以被組件掃描器掃描的配置類,只不過 @SpringBootConfiguration 是被 Spring Boot 進(jìn)行了重新封裝命名而已。
-
@EnableAutoConfiguration 注解
@EnableAutoConfiguration:開啟自動(dòng)配置功能,以前需要手動(dòng)配置的東西,現(xiàn)在由 SpringBoot 自動(dòng)配置,這個(gè)注解就是 Springboot 能實(shí)現(xiàn)自動(dòng)配置的關(guān)鍵。
查看該注解內(nèi)部查看源碼信息,核心代碼具體如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 自動(dòng)配置包
@AutoConfigurationPackage
// Spring 的底層注解 @Import,給容器中導(dǎo)入一個(gè)組件;導(dǎo)入的組件是AutoConfigurationPackages.Registrar.class
@Import(AutoConfigurationImportSelector.class)
// 告訴 SpringBoot 開啟自動(dòng)配置功能,這樣自動(dòng)配置才能生效。
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
// 返回不會(huì)被導(dǎo)入到 Spring 容器中的類
Class<?>[] exclude() default {};
// 返回不會(huì)被導(dǎo)入到 Spring 容器中的類名
String[] excludeName() default {};
}
可以發(fā)現(xiàn)它是一個(gè)組合注解, Spring 中有很多以 Enable 開頭的注解,其作用就是借助 @Import 來收集并注冊特定場景相關(guān)的 Bean ,并加載到 IOC 容器。@EnableAutoConfiguration 就是借助 @Import 來收集所有符合自動(dòng)配置條件的 bean 定義,并加載到 IoC 容器。
-
@AutoConfigurationPackage 注解
查看 @AutoConfigurationPackage 注解內(nèi)部源碼信息,核心代碼具體如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
從上述源碼可以看出,@AutoConfigurationPackage 注解的功能是由 @Import 注解實(shí)現(xiàn)的,它是 spring 框架的底層注解,它的作用就是給容器中導(dǎo)入某個(gè)組件類,例如 @Import(AutoConfigurationPackages.Registrar.class),它就是將 Registrar 這個(gè)組件類導(dǎo)入到容器中,可查看 Registrar 類中 registerBeanDefinitions 方法,這個(gè)方法就是導(dǎo)入組件類的具體實(shí)現(xiàn):
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImport(metadata).getPackageName());
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImport(metadata));
}
}
從上述源碼可以看出,在 Registrar 類中有一個(gè) registerBeanDefinitions() 方法,使用 Debug 模式啟動(dòng)項(xiàng)目, 可以看到選中的部分就是 com.zm。也就是說,@AutoConfigurationPackage 注解的主要作用就是將主程序類所在包及所有子包下的組件到掃描到 spring 容器中。
因此在定義項(xiàng)目包結(jié)構(gòu)時(shí),要求定義的包結(jié)構(gòu)非常規(guī)范,項(xiàng)目主程序啟動(dòng)類要定義在最外層的根目錄位置,然后在根目錄位置內(nèi)部建立子包和類進(jìn)行業(yè)務(wù)開發(fā),這樣才能夠保證定義的類能夠被組件掃描器掃描。
-
@Import({AutoConfigurationImportSelector.class}) 注解
將 AutoConfigurationImportSelector 這個(gè)類導(dǎo)入到 Spring 容器中,AutoConfigurationImportSelector 可以幫助 Springboot 應(yīng)用將所有符合條件的 @Configuration 配置都加載到當(dāng)前 SpringBoot 創(chuàng)建并使用的 IOC 容器 ApplicationContext 中。
繼續(xù)研究 AutoConfigurationImportSelector 這個(gè)類,通過源碼分析這個(gè)類中是通過selectImports 這個(gè)方法告訴 springboot 都需要導(dǎo)入那些組件:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 獲得自動(dòng)配置元信息,需要傳入 beanClassLoader 這個(gè)類加載器
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
深入研究 loadMetadata 方法 :
// 文件中為需要加載的配置類的類路徑
protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";
private AutoConfigurationMetadataLoader() {
}
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
return loadMetadata(classLoader, PATH);
}
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
try {
// 讀取 spring-boot-autoconfigure-2.1.5.RELEASE.jar 包中的 spring-autoconfigure-metadata.properties 的信息生成 url
Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
: ClassLoader.getSystemResources(path);
Properties properties = new Properties();
// 解析 urls 枚舉對象中的信息封裝成 properties 對象并加載
while (urls.hasMoreElements()) {
properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
}
// 根據(jù)封裝好的 properties 對象生成 AutoConfigurationMetadata 對象并返回
return loadMetadata(properties);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
}
}
AutoConfigurationImportSelector 類 getAutoConfigurationEntry 方法:
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
// 判斷 EnabledAutoConfiguration 注解有沒有開啟, 默認(rèn)開啟
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 獲得注解的屬性信息
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 獲取默認(rèn)支持的自動(dòng)配置類列表
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 去重
configurations = removeDuplicates(configurations);
// 去除一些多余的配置類,根據(jù) @EnabledAutoConfiguration 的 exclusions 屬性進(jìn)行排除
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
// 根據(jù) pom 文件中加入的依賴文件篩選中最終符合當(dāng)前項(xiàng)目運(yùn)行環(huán)境對應(yīng)的自動(dòng)配置類
configurations = filter(configurations, autoConfigurationMetadata);
// 觸發(fā)自動(dòng)配置導(dǎo)入監(jiān)聽事件
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
深入 getCandidateConfigurations 方法:
這個(gè)方法中有一個(gè)重要方法 loadFactoryNames,這個(gè)方法是讓 SpringFactoryLoader 去加載一些組件的名字。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
/**
* loadFactoryNames:
* 這個(gè)方法需要傳入兩個(gè)參數(shù) getSpringFactoriesLoaderFactoryClass() 和
* getBeanClassLoader()。
* getSpringFactoriesLoaderFactoryClass() 這個(gè)方法返回的是
* @EnableAutoConfiguration.class。
* getBeanClassLoader() 這個(gè)方法返回的是 beanClassLoader 類加載器
*/
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct.");
return configurations;
}
/**
* Return the class used by {@link SpringFactoriesLoader} to load configuration
* candidates.
* @return the factory class
*/
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
...
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
}
protected ClassLoader getBeanClassLoader() {
return this.beanClassLoader;
}
繼續(xù)點(diǎn)開 loadFactoryNames 方法:
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
// 獲取出入的鍵
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
// 如果類加載器不為 null,則加載類路徑下 spring.factories 文件,將其中設(shè)置的配置類的全路徑信息封裝為 Enumeration 類對象
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
// 循環(huán) Enumeration 類對象,根據(jù)相應(yīng)的節(jié)點(diǎn)信息生成 Properties 對象,通過傳入的鍵獲取值,在將值切割為一個(gè)個(gè)小的字符串轉(zhuǎn)化為 Array,加入方法 result 集合中
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
上面代碼需要讀取的 FACTORIES_RESOURCE_LOCATION 最終路徑的長這樣,而這個(gè)是 spring 提供的一個(gè)工具類:
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
它其實(shí)是去加載一個(gè)外部的文件,而這文件是在 spring-boot-autoconfigure-2.2.2.RELEASE.jar 包下的 META-INF/spring.factories,打開 spring.factories 文件:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
...
@EnableAutoConfiguration 就是從 classpath 中搜尋 META-INF/spring.factories 配置文件,并將其中 org.springframework.boot.autoconfigure.EnableutoConfiguration 對應(yīng)的配置項(xiàng)通過反射 Java Refletion 實(shí)例化為對應(yīng)的標(biāo)注了 @Configuration 的 JavaConfig 形式的配置類,并加載到 IOC 容器中。
以入門項(xiàng)目為例,在項(xiàng)目中加入了 Web 環(huán)境依賴啟動(dòng)器,對應(yīng)的 WebMvcAutoConfiguration 自動(dòng)配置類就會(huì)生效,打開該自動(dòng)配置類會(huì)發(fā)現(xiàn),在該配置類中通過全注解配置類的方式對 Spring MVC 運(yùn)行所需環(huán)境進(jìn)行了默認(rèn)配置,包括默認(rèn)前綴、默認(rèn)后綴、視圖解析器、MVC 校驗(yàn)器等。而這些自動(dòng)配置類的本質(zhì)是傳統(tǒng) Spring MVC 框架中對應(yīng)的 XML 配置文件,只不過在 Spring Boot 中以自動(dòng)配置類的形式進(jìn)行了預(yù)先配置。因此,在 Spring Boot 項(xiàng)目中加入相關(guān)依賴啟動(dòng)器后,基本上不需要任何配置就可以運(yùn)行程序,當(dāng)然,也可以對這些自動(dòng)配置類中默認(rèn)的配置進(jìn)行更改。
總結(jié):
因此 springboot 底層實(shí)現(xiàn)自動(dòng)配置的步驟是:
- springboot 應(yīng)用啟動(dòng)。
- @SpringBootApplication 起作用。
- @EnableAutoConfiguration。
- @AutoConfigurationPackage:這個(gè)組合注解主要是 @Import(AutoConfigurationPackages.Registrar.class),它通過將 Registrar 類導(dǎo)入到容器中,而 Registrar 類作用是掃描主配置類同級目錄以及子包,并將相應(yīng)的組件導(dǎo)入到 springboot 創(chuàng)建管理的容器中。
- @Import(AutoConfigurationImportSelector.class):它通過將AutoConfigurationImportSelector 類導(dǎo)入到容器中;AutoConfigurationImportSelector 類作用是通過 selectImports 方法執(zhí)行的過程中,會(huì)使用內(nèi)部工具類 SpringFactoriesLoader,查找 classpath 上所有 jar 包中的 META-INF/spring.factories 進(jìn)行加載,實(shí)現(xiàn)將配置類信息交給 SpringFactory 加載器進(jìn)行一系列的容器創(chuàng)建過程。
-
@ComponentScan 注解
@ComponentScan 注解具體掃描的包的根路徑由 Spring Boot 項(xiàng)目主程序啟動(dòng)類所在包位置決定,在掃描過程中由前面介紹的 @AutoConfigurationPackage 注解進(jìn)行解析,從而得到 Spring Boot 項(xiàng)目主程序啟動(dòng)類所在包的具體位置。
總結(jié)
@SpringBootApplication 的注解的功能簡單來說就是 3 個(gè)注解的組合注解:
|- @SpringBootConfiguration
????|- @Configuration ?? // 通過 javaConfig 的方式來添加組件到 IOC 容器中
|- @EnableAutoConfiguration
????|- @AutoConfigurationPackage ?? // 自動(dòng)配置包,與 @ComponentScan 掃描到的添加到 IOC
????|- @Import(AutoConfigurationImportSelector.class) ?? // 到 META-INF/spring.factories 中定義的 bean 添加到 IOC 容器中
|- @ComponentScan ?? // 包掃描