【Spring Boot課程】3.Spring Boot的配置
1 yaml簡介
1.1 基本語法
k:(空格)v: 表示一對鍵值對(空格必須有)
以空格的縮排來控制層級關係;只要是做肚臍的一列資料,都是同一個層級的。
server:
port: 8081
path: /hello
屬性和值都是大小寫敏感的。
1.2 值的寫法
1.2.1 字面量
1.2.1.1 普通的值(數字、字串、布林)
k:v;字面量直接來寫,字串不需要加上丹壹號或者雙引號;
雙引號:不會轉移字串李的特殊字元,特殊字元會作為本身想表示的意思。
name:"zhangsan\nlisi"
其輸出就是有換行。
單引號:會轉義特殊字元,特殊字元最終只是一個普通的字串資料。
name:"zhangsan\nlisi"
輸出為原樣。
1.2.1.2 物件、Map(屬性和值、鍵值對)
物件還是k:v的方式,在下一行直接寫物件的屬性和值的關係,不過要注意縮排(兩個空格)。
friends:
lastName: zhangsan
age: 20
行內寫法:
friends: {lastName: zhangsan,age: 18}
1.2.1.3 陣列(List、Set)
短橫線(空格)值 表示陣列中的一個元素。
pets:
- cat
- dog
- pig
行內寫法:
pets: [cat,dog, pig]
1.3 進階語法
1.3.1 複合結構
person:
lastName: zhangsan
age: 18
boss: false
birth: 2017/12/12
maps: {k1: v1,k2: 12}
lists:
- lisi
- zhangsan
dog:
name: xiaogou
age: 2
2 配置檔案
SB使用一個全域性配置檔案,配置檔名是固定的:
- application.properties
- application.yml
配置檔案的作用:修改SB自動配置的預設值。SB在底層都給我們自動配置好了;
配置檔案位於src/resources下面。
2.1 YAML
yaml a markup language 是一個標記語言,以資料為中心,比其他型別的標記語言更適合作為配置檔案。
標記語言:以前的配置檔案大多都使用XXX.xml檔案,
2.2 不同檔案配置方式
YAML:
server:
port: 8081
XML:
<server>
<port>8081</port>
</server>
properties:
server.port = 8081
3 例子:配置檔案實現值的注入
3.1 編寫配置檔案
person:
lastName: zhaoyi
list:
- list1
- list2
maps:
k1: v1
k2: v2
age: 25
boss: false
dog:
name: xiaohuang
age: 30
配置元件
// person類
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
private String lastName;
private List<Object> list;
private Map<String, Object> maps;
private Integer age;
private Boolean boss;
private Dog dog;
@Override
public String toString() {
return "Person{" +
"lastName='" + lastName + '\'' +
", list=" + list +
", maps=" + maps +
", age=" + age +
", boss=" + boss +
", dog=" + dog +
'}';
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public List<Object> getList() {
return list;
}
public void setList(List<Object> list) {
this.list = list;
}
public Map<String, Object> getMaps() {
return maps;
}
public void setMaps(Map<String, Object> maps) {
this.maps = maps;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Boolean getBoss() {
return boss;
}
public void setBoss(Boolean boss) {
this.boss = boss;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
}
// dog類
package com.zhaoyi.hello1.com.zhaoyi.hello1.bean;
public class Dog {
private String name;
private int age;
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
3.2 使配置擁有提示
匯入配置檔案處理器,這樣就會有提示編寫資源的資訊;
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
只有元件是容器中的元件,才能使用ConfigurationProperties提供的功能;
3.3 編寫測試類
SB單元測試:可以在測試期間很方便的類似編碼一樣進行自動注入等容器功能;
@RunWith(SpringRunner.class)
@SpringBootTest
public class Hello1ApplicationTests {
@Autowired
Person person;
@Test
public void contextLoads() {
System.out.println(person);
}
}
4 properties配置檔案的編碼問題
4.1 使用properties配置檔案
註釋掉application.yml的配置檔案資訊,新建一個application.properties檔案,寫入和applicatio.yml相同功能的配置
person.age=14
person.last-name=張三
person.boss=false
person.maps.k1=v1
person.maps.k2=v2
person.list=a,b,c
person.dog.name=小狗
person.dog.age=5
執行測試後發現有關中文部分的輸出是亂碼。
4.2 解決方法
idea使用的是utf-8編碼,在setting處查詢file encoding,設定為utf-8,並選擇在執行時轉化為ascll(勾選)
5 @configurationProperties和@Value
Spring中配置一個Bean:
<bean class="person">
<property name="lastName" value="zhangsan"></property>
</bean>
其中,value可以:
- 字面量
- ${key} 從環境變數或者配置檔案中提取變數值
#{SPEL}
而@Value其實效果和其一樣,比如,在類屬性上寫
@Value("person.lastName")
private String lastName
@Value("#{11*2}")
private Integer age
5.1 區別
@ConfigurationProperties | @Value | |
---|---|---|
功能 | 批量注入配置檔案中的屬性 | 一個個指定 |
支援鬆散繫結 | 不支援鬆散繫結 | |
SpEL | 不支援 | 支援 |
JSR303資料校驗 | 支援 | 不支援 |
複雜型別封裝 | 支援 | 不支援 |
注:
- 鬆散繫結例如:
配置檔案填寫的是last-name,而@Value必須寫一模一樣的,否則會報錯。而使用@ConfigurationProperties則可以使用駝峰式(其實就是類的屬性名) - @Validation 在類上加入此配置之後,開啟jsr303校驗,例如在某欄位上加上@Email,則配置檔案對應欄位必須符合郵箱格式。相反,如果此處我們使用@Value注入值,可以看到,可以正常的注入,即便提供的不符合郵箱格式,也不會報錯。
5.2 選擇
從上面的說明我們可以知道,兩者在配置上基本可以相互替換,彼此功能也大體一致,那麼,我們在業務場景中,該選用哪一種進行編碼呢?
- 如果說,我們只是在某個業務邏輯中需要獲取一下配置檔案的某項值,考慮使用
@Value
.
@RestController
public class HelloController {
@Value("${person.last-name}")
private String name;
@RequestMapping("/")
public String hello(){
return "hello world, " + name;
}
}
- 但是,如果我們專門去寫一個java bean來和配置檔案進行對映,那麼毫無疑問,我們應該使用
@ConfigurationProperties
;
6 @PopertySource註解
@PropertySource
載入指定的配置檔案。
我們知道,@ConfigurationProperties預設從全域性配置檔案(application.properties)中獲取值,但通常我們會將相關的配置檔案放到某個配置檔案中,例如,我們將有關person的配置資訊放到person.properties中去。為了使元件類Person能夠找到配置檔案的配置資訊,需要使用增加新的註解@PropertySource
指定從哪裡載入配置等相關資訊。
@PropertySource("classpath:person.properties")
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
}
7 @ImportResource註解
@ImportResource
匯入Spring的配置檔案,讓配置檔案裡面的內容生效。
- 新增新的服務類HelloService
package com.zhaoyi.hello1.service;
public class HelloService {
}
- 在類目錄下建立一個bean.xml,即spring的配置檔案,其中配置了一個service的bean.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="helloService" class="com.zhaoyi.hello1.service.HelloService"></bean>
</beans>
那麼問題來了,在spring-boot中顯然是不會載入此bean的,我們測試一下。
- 編寫測試類
@RunWith(SpringRunner.class)
@SpringBootTest
public class Hello1ApplicationTests {
@Autowired
ApplicationContext ioc;
@Test
public void testBean(){
System.out.println("Is have helloService bean? " + ioc.containsBean("helloService"));
}
}
測試結果:
Is have helloService bean? false
Spring boot裡面是沒有Spring的配置檔案,我們自己編寫的配置檔案,也不能自動識別,想讓Spring的配置檔案生效,則需要手動指示將其載入進行,使用@ImportResource
標註在一個配置類(例如應用程式啟動類,他也是一個配置類)上:
@ImportResource(locations = {"classpath:bean.xml"})
@SpringBootApplication
public class Hello1Application {
public static void main(String[] args) {
SpringApplication.run(Hello1Application.class, args);
}
}
這時候執行測試用例就會發現,bean已經出現在容器中了。
7 @Bean註解
@ImportResource(locations = {“classpath:bean.xml”})
一般我們不會使用6中所提到的這種方式,因為xml配置方式實在是寫了太多的無用程式碼,如果xml的標籤宣告,以及頭部的域名空間導致。因此,SpringBoot推薦給容器中新增元件的方式:全註解方式。也就是用配置類來充當bean配置檔案。如下,即為一個配置類:
/**
* @Configuration 指明當前類是一個配置類
*/
@Configuration
public class MyConfig {
// 將方法的返回值新增到容器中:容器中這個元件的id就是方法名
@Bean
public HelloService helloService(){
return new HelloService();
}
}
通過該配置類,可以為容器中添加了一個名為helloService
的bean。
8 配置檔案佔位符
無論是使用yaml還是properties都自持檔案佔位符配置方式
8.1 隨機數
${random.value}
${random.int}
${random.long}
${random.int(10)}
${random.int[1024,65536]}
8.2 屬性佔位符
- 可以在配置檔案中引用前面配置過的屬性;
- ${app.name:預設值} 若找不到屬性時,則取預設值處填寫的值;
8.3 例子
- 在person.properties中寫入如下配置
person.age=14
person.last-name=張三${random.uuid}
person.boss=false
person.maps.k1={person.xxx:novalue}
person.maps.k2=v2
person.list=a,b,c
person.dog.name=${person.last-name}_小狗狗
person.dog.age=${random.int}
- 測試輸出
Person{lastName='張三1b9fbbb1-6c58-4a35-8165-ad23800d7456', list=[a, b, c], maps={k2=v2, k1=novalue}, age=14, boss=false, dog=Dog{name='張三b3a355d7-54ce-4afd-9ae6-d3be4aeb4165_小狗狗', age=-122850975}}
注意:留意預設值那一項設定,我們如願的成功設定了預設值novalue。在實際專案中,這種情況比較常用,稍微留意一下。如果沒有預設值,則會將${xxx}這一段作為值,這顯然是錯誤的。
9 Profile
profile一般是spring用來做多環境支援的,可以通過啟用指定引數等方式快速的切換當前環境。
9.1 多Profile檔案
我們在主配置檔案編寫的時候,檔名可以是 application-{profile}.properties(yml)
例如:
application-dev.properties、application-prod.properties等。
9.2 啟用指定profile
- 在application.properties中指定:
spring.profiles.active=dev
- 命令列方式啟用,此配置的優先順序高於配置檔案處的配置。
--spring.profiles.active=dev
- 方式一 點選編譯環境右上角的下拉框,選擇第一項
edit configuration
,在environment配置節中的Program arguments
寫入spring.profiles.active=dev即可。 - 方式二 將當前的專案打包生成jar包,然後執行
java -jar your-jar-name.jar --spring.profiles.active=dev
即可。
- 虛擬機器引數方式啟用 在步驟2的老地方,
Program arguments
上一項即為虛擬機器引數啟用,不過填寫的內容為-Dspring.profiles.active=dev
,即多了一個-D
而已。
9.3 yml多文件配置檔案
若我們使用properties,則需要編寫多個不同的配置檔案,但如果我們使用yml的話,則可以通過多文件配置節實現單檔案管理。注:有關yml相關的知識參考此文件的前半部分。
可以看到,我們通過---
三個橫線實現文件分割,同時在每一個文件塊處指定了各個profile的不同配置資訊,即dev環境下啟動服務使用8082埠,prod環境下使用8083埠。而指定哪一個profile則是通過預設的文件塊(即第一塊)中的spring.profiles.active進行配置。完成如上配置之後我們啟動服務,顯然此時是以啟用的prod環境所配置的埠8083執行的,如下啟動日誌所示:
com.zhaoyi.hello1.Hello1Application : The following profiles are active: prod
o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8083 (http)
也就說,當我們選定一個profile後,對應的文件塊的配置就會全部生效。
9 配置檔案的載入位置
SpringBoot啟動會掃描以下位置的application.properties或者application.yml檔案作為SpringBoot的預設配置檔案:
- file:./config/
- file:./
- classpath:/config/
- classpath:/
以上是按照優先順序順序從高到低,所有位置的檔案都會被載入,但對於相同配置:高優先順序配置內容會覆蓋低優先順序配置的內容,其他的則互補。
我們也可以通過spring.config.location引數來設定預設的配置檔案位置。專案打包好以後,使用命令列引數形式來指定配置檔案的新位置,指定的配置檔案和預設載入的配置互補起作用。並且我們指定的該配置檔案優先順序是最高的。
10 外部配置的載入順序
SpringBoot支援多種外部配置方式,他可以從以下位置載入配置,按優先順序從高到低排列如下(高優先順序配置覆蓋低優先順序配置,所有配置會形成互補配置):
- 命令列引數
java -jar package_name_version.jar --server-port=8080 --server.context-path=/hello
多個引數之間用空格分開,用--parameter_name=value
進行配置。
- 來自java:comp/env的JNDI屬性
- java系統屬性(System.getProperties())
- 作業系統環境變數
- RandomValuePropertySource配置的random.*屬性值
都是由jar包外向jar包內進行尋找,高優先順序的配置覆蓋低優先順序的配置。然後
優先載入帶profile的:
- jar包外部的application-{profile}.properties或application.yml(帶spring.profile)配置檔案
在jar檔案的同級目錄放一個application.properties檔案,其配置內容會被載入;
- jar包內部的application-{profile}.properties或application.yml(帶spring.profile)配置檔案
再來載入不帶profile的:
- jar包外部的application.properties或application.yml(不帶spring.profile)配置檔案
- jar包內部的application.properties或application.yml(不帶spring.profile)配置檔案
- @Configuration註解類上的@PropertySource
- 通過SpringApplication.setDefaultProperties指定的預設屬性。
官方文件列出了比這裡更多的配置文件,請參考,版本更迭地址會經常變動,可自行前往官方網站進行檢視。
11 自動配置原理
配置檔案的配置屬性可以參照官方文件:前往
11.1 自動配置原理
- SB啟動的時候載入主配置類,開起了自動配置功能
@EnableAutoConfiguration
。 - @EnableAutoConfiguration作用:利用EnableAutoConfigurationImportSelector給容器中匯入一些元件,可以檢視selectImport()方法的內容:
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
掃描所有jar包類路徑下META-INF/spring.factories
檔案,吧掃描到的這些檔案的內容包裝成properties物件;從properties中獲取到EnableAutoConfiguration.class類對應的值,然後把他們新增在容器中;
說到底,就是將類路徑下META-INF/spring.factories
裡面配置的所有EnableAutoConfiguration的值加入到了容器中。
# 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,\
...(more)
每一個這樣的xxxAutoConfiguration類都會是容器中的一個元件,都加入到了容器中,用他們來做自動配置。
- 每一個自動配置類進行自動配置功能。
以org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration
為例:
@Configuration
@EnableConfigurationProperties({HttpProperties.class})
@ConditionalOnWebApplication(
type = Type.SERVLET
)
@ConditionalOnClass({CharacterEncodingFilter.class})
@ConditionalOnProperty(
prefix = "spring.http.encoding",
value = {"enabled"},
matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
return filter;
}
...
}
我們可以看到該類的註解如下:
@Configuration
毫無疑問,表明這是一個配置類,和我們要自定義一個配置檔案一樣,實現給容器中新增元件;@EnableConfigurationProperties
啟動指定類的ConfigurationPropertiesg功能,這樣就可以將配置檔案中對應的值和HttpEncodingProperties繫結起來;並把HttpEncodingProperties加入到ioc容器中;@ConditionalOnWebApplication
Spring底層@Conditional
註解,根據不同的條件,如果滿足指定的條件,整個配置類裡面的配置就會生效;此處判斷的就是當前應用是不是Web應用;否則配置不生效;@ConditionalOnClass
判斷當前專案有沒有類(CharacterEncodingFilter
),這是SpringMVC中進行亂碼解決的過濾器;@ConditionalOnProperty
判斷配置檔案中是否存在某個配置spring.http.encoding.enabled
;如果不存在,則判斷成立,如果沒有配置,則此處將其設定為true
;@Bean
給容器中新增Bean元件,該元件的某些值需要從properties中獲取,此處即為HttpEncodingProperies,顯然此刻他的取值已經和SB的properties檔案進行注入了;
根據不同的條件進行判斷當前這個配置類是否生效。一單這個配置類生效,這個配置類就會給容器中新增各種元件;這些元件的屬性均來自於其對應的Properties類的,這些Properties類裡面的每一個屬性,又是和配置檔案繫結的。
- 所有在配置檔案中能配置的屬性都是在xxxProperties類中的封裝者;配置檔案能配置什麼就可以檢視對應的屬性類。如上面的這個配置類我們就可以參考他的註解
@EnableConfigurationProperties
指定的properties類HttpProperties
:
@ConfigurationProperties(
prefix = "spring.http"
)
public class HttpProperties {
11.2 配置精髓
- SB啟動會載入大量的自動配置類;
- 我們看我們需要的功能有沒有SB預設寫好的自動配置類;
- 我們再來看這個自動配置類中到底配了那些元件,倘若已經有了,我們就不需要再編寫配置檔案進行配置了。
- 給容器中自動配置類新增元件的時候,會從對應的Properties類中獲取某些屬性。我們就可以在配置檔案中指定這些屬性的值(其已經通過註解進行了繫結);
11.3 總結
xxxAutoConfiguration 這種類就是用來做自動配置的,他會給容器中新增相關的元件,其對應的Properties則對應了配置的各種屬性;也就是說,通過這些配置類,我們以前需要在SpringMVC中寫配置類、檔案實現的東西,現在,只需要在Properites配置檔案中加入相關的配置即可,不再那麼麻煩了。
當然,一些特殊的配置還是得自己寫元件的哦。
12 @Conditional*相關注解
12.1 @Conditinal派生註解
只有其指定的條件成立,配置類的所有型別才會生效,更小範圍的,例如註釋在某個Bean元件上面的相關條件註解成立,才會生成該Bean。
12.2 常用派生註解一覽
@Conditional擴充套件註解 | 作用(判斷是否滿足當前指定條件) |
---|---|
@ConditionalOnJava | 系統的java版本是否符合要求 |
@ConditionalOnBean | 容器中存在指定Bean |
@ConditionalOnMissingBean | 容器中不存在指定Bean |
@ConditionalOnExpression | 滿足SpEL表示式指定 |
@ConditionalOnClass | 系統中有指定的類 |
@ConditionalOnMissingClass | 系統中沒有指定的類 |
@ConditionalOnSingleCandidate | 容器中只有一個指定的Bean,或者這個Bean是首選Bean |
@ConditionalOnProperty | 系統中指定的屬性是否有指定的值 |
@ConditionalOnResource | 類路徑下是否存在指定資原始 |