上一篇教程我們使用了application.properties和application.yml進行項目的配置,這篇博客將告訴你更多Spring Boot項目配置的方式
0.項目結構及代碼
配置文件application.yml的內容如下:
student:
name: zg
is-female: false
birth: 2019/04/12
teachers: [zhang,wang]
grade: {English: 87, Math: 100}
pets:
spices: Dog
age: 1
Student.java代碼如下,內容僅含成員變量,請自動生成toString()方法、get()方法和set()方法。
@Component
@ConfigurationProperties(prefix="student")
public class Student {
private String name;
private Boolean isFemale;
@Value("2014/3/12")
private Date birth;
private List<Object> teachers;
private Map<String, Object> grade;
private Pet pets;
}
Pet.java代碼如下,內容僅含成員變量,請自動生成toString()方法、get()方法和set()方法。
public class Pet {
private String spices;
private Integer age;
}
主配置類Application.java是自動生成的代碼如下:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
測試類ApplicationTests.java代碼如下.
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {
@Autowired
Student student;
@Test
public void contextLoads() {
System.out.println(student);
}
}
1.使用@Value進行配置
1.1 簡介
1、@Value用於將外部的值動態地注入Bean中,相當於使用.xml中的如下的代碼。通常用於對某個值進行單個的注入,畢竟如果對整個Bean內的值進行注入我們有更好用的@ConfigurtionProperties可以使用。
<bean class="類名">
<property name="需要注入值的變量名" value="注入的值"></properties>
</bean>
2、我們可以點進去看一下@Value的源碼,如下,我們發現它其中只包括一個String類型的變量,也就是說我們@Value註解後面只能接一個String類型的變量,無論什麼內容都必須包括在雙引號之下。
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Value {
String value();
}
1.2 可注入類型
1.2.1 注入類型和注入方法
1、一共有7種可注入類型。字面量、數組(List)、鍵值對(Map)、其他bean屬性、URL資源、文件資源、系統屬性。可通過字符串或SpEL(Spring表達式語言,如計算表達式),也可以與配置文件一起配合使用。
(1)字符串:@Value("字符串內容")。適用於一般的自變量、數組、文件資源和URL資源,Spring Boot會根據配置的變量類型對字符串內容進行轉換。
(2)SpEL:@Value("#{SpEL內容}")。通常SpEL內的字符串使用單引號''括起來。適用於需要計算的自變量、特殊數組、鍵值對、其他bean屬性等。
(3)如果需要引入配置文件:${配置文件中的變量:默認值}.可以與字符串和SpEL進行組合,可設置默認值。如:
@Value("${web.blog:false}")
private Boolean blog;
在blog變量做值的注入,從配置文件中找是否有web.blog變量,如果沒有,爲其賦默認值false。
2、 接下來我將給出一些實例,方便進行理解。
首先我們構建一個新的bean,內容如下,請自動生成toString()方法、get()方法和set()方法。注意Resource的庫文件的選擇,有很多Resource,選擇springframework的那個。
import org.springframework.core.io.Resource;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Component
public class Website {
//點擊次數
private Integer clickNum;
//網站名稱
private String webName;
//是不是博客
private Boolean blog;
//構建時間
private Date buildDate;
//構建的人
private String buildPerson;
//關鍵字
private List<String> key;
//其他屬性
private Map<String,String> otherProperties;
//網址
private Resource address;
//介紹
private Resource intro;
}
然後我們修改一下ApplicationTests的內容,方便輸出結果進行查看,執行的時候右鍵->Run就可以了。
public class ApplicationTests {
@Autowired
Website web;
@Test
public void contextLoads() {
System.out.println(web);
}
}
1.2.2 字面量:包括布爾、數值型、字符串等。
1、比較簡單的使用@Value("字符串")即可。
@Value("true")
private Boolean blog;
2、可以通過SpEL注入,我們這裏以整型的變量clickNum爲例。代碼和二者執行後的結果如下。
@Value("#{12*15}")
private Integer clickNum;
3、 可以通過@Value結合配置文件進行注入。配置文件可以使用".properties"和".yml",配置文件內容和注入方式如下所示。這裏展示了使用配置文件的方式,所以之後如果非必要,我就不再展示這一部分的配置,並非無法使用。
#如果使用.properties文件配置
date=2011/6/11
#如果使用.yml進行配置
date: 2011/6/11
@Value("${date}")
private Date buildDate;
1.2.3 數組(List)
1、字符串:與字面量類似,字符串之間使用英文的“,”隔開。
@Value("Spring Boot, IDEA")
private List<String> key;
運行結果:
2、SpEL:比如我們需要字符串中有逗號的時候,使用SpEL就會更合適一些。代碼如下:
@Value("#{'Spring Boot;IDEA'.split(';')}")
private List<String> key;
運行結果如下,與我們前一種方式注入的結果一樣。
1.2.4 鍵值對(Map)
對於鍵值對我們不能直接使用字符串,可以使用SpEL,也可以配合配置文件來進行使用。
1、SpEL,注意第二層的括號一定要有:
@Value("#{{color:'red', language:'Chinese'}}")
private Map<String,String> otherProperties;
2、如果結合配置文件使用,我們這裏使用.properties格式和.yml格式的配置的方式有些不同,先來看.properties配置文件代碼:
valuesMap={color:'red', language:'Chinese'}
我們在上一篇博客講到過.yml在進行單行鍵值對賦值的時候使用{},所以需要加上雙引號"",因爲單引號會將字符串內容轉義,所以我們這裏使用雙引號。.yml配置文件代碼:
valuesMap: "{color:'red', language:'Chinese'}"
進行注入:
@Value("#{${valuesMap}}")
private Map<String,String> otherProperties;
1.2.5 其他bean屬性
只能使用SpEL。這裏我們將Student類型的student的name的值注入到otherProperties中。
首先我們通過.properties文件爲student的name注入值:
student.name=你猜呢
然後在Student文件中使用註解告訴Spring Boot爲其自動注入配置文件中student開頭的值,與上一篇博客一樣。
然後我們在Website.java文件中進行值注入。
@Value("#{student.name}")
private String buildPerson;
我們看一下輸出的結果:
1.2.6 URL資源
與字面量中的字符串類型變量的注入差不多,支持多種形式,我們只示例字符串的形式:
@Value("http://www.baidu.com")
private Resource address;
1.2.7 文件資源
與字面量中的字符串類型變量的注入差不多,支持多種形式,我們只示例一種。注意這裏我們一定要在文件路徑前面加上classpath,並且將路徑從文件夾處複製過來的時候,最好更改格式,把裏面的“\\”和“\”改爲“/”。當然你也可以不改,Spring Boot也能夠識別:
@Value("classpath:D:/code/application/a.txt")
private Resource intro;
1.2.8 系統屬性
這裏我們只能使用SpEL的形式進行注入,這裏演示的例子是把系統參數的值注入到webName中。
@Value("#{systemProperties['sun.desktop']}")
private String webName;
二、@Value和@ConfigurationProperties比較
2.1 作用範圍
我們分別打開兩個的源碼看一下,都很簡單。主要看@Target後面的參數內容,可以發現二者的作用範圍不同。@Value的作用範圍,但通過上面的例子可以看出,@Value主要作用到變量上,而@ConfigurationProperties,主要用於方法。具體下面的的ElementType是什麼意思,請自行百度,或者看https://blog.csdn.net/zhanggonglalala/article/details/89137065,篇幅有點大,不贅述。
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Value {
String value();
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConfigurationProperties {
@AliasFor("prefix")
String value() default "";
@AliasFor("value")
String prefix() default "";
boolean ignoreInvalidFields() default false;
boolean ignoreUnknownFields() default true;
}
2.2 獲取值的方式
我們先看一個表格然後對其中內容詳細描述
@ConfigurationProperties | @Value | |
功能 | 對配置文件中的值進行批量注入 | 一個個根據變量的需求進行指定 |
鬆散綁定 | 支持 | 不支持 |
SpEL | 不支持 | 支持 |
JSR303數據校驗 | 支持 | 不支持 |
複雜類型封裝 | 支持 | 不支持 |
1、@ConfigurationProperties的屬性名匹配規則(Relaxed binding):Spring Boot可以對下面四種類型的命名進行自動匹配,也就是說配置文件和類文件中的名稱不需要完全相同,使用下面的任意兩種,系統就會將二者匹配上
- 標準方式:變量之間通過下一個單詞的首字母大寫的方式隔開,eg:student.isFemale
- 使用“-”進行單詞之間的連接:eg:student.is-female
- 使用“_”進行單詞之間的連接:eg:student.is_female
- 全部大寫,使用“_”連接:通常對系統屬性變量推薦這種寫法。eg:STUDENT_IS_FEMALE
2、@ConfigurationProperties不支持SpEL,所以對獲取系統屬性,或者進行比較複雜的操作(比如按照某個特殊字符對字符串進行分割)的時候會無法操作。
3、JSR303是一種校驗規範,通過在類前加註解@Validated來實現。更加完整的關於JSR303的介紹在這裏。我們可以使用javax.validation.constraints包下的所有註解爲變量進行限制,展示一下我當前的這個包下的22個註解並進行解釋:@AssertFalse、@AssertTrue、@DecimalMax、@DecimalMin、@Digits、@Email、@Future、@FutureOrPresent、@Max、@Min、@Negative、@NegativeOrZero、@NotBlank、@NotEmpty、@NotNull、@Null、@Past、@PastOrPresent、@Pattern、@Positive、@PositiveOrZero、@Size。
我們這裏舉一個例子,加入我們在配置文件中爲Website進行配置。爲了簡單我們這裏僅配置clickNum一個參數,.properties配置文件代碼如下:
website.clickNum=15
Website.java的部分代碼如下,其他代碼與教程前面相同。我們要求clickNum不超過10,但是又在配置文件中爲其賦值15.
@Component
@ConfigurationProperties(prefix = "website")
@Validated
public class Website {
@Max(value = 10)
private Integer clickNum;
執行後報錯,錯誤原因如圖,出錯位置和原因通過非常簡明的方式在日誌中說明了。
4、對於複雜類型封裝我們使用@ConfigurationProperties,比如在Student中我們有一個Pet類型的變量,這個時候我們無法通過@Value對Pet類型的變量進行直接賦值。
2.3 使用場景
@Value通常用於對某個位置獲取配置文件中的的某一個變量的時候,而@ConfigurationProperties用於javaBean和配置文件進行整體映射。上述的使用場景只是說通常情況,具體的使用,還是要根據@Value和@ConfigurationProperties各自的特點來進行判斷。
三、@PropertySource、@ImportResource、@bean
大多數情況,項目需要配置的內容會比較複雜,這時候需要使用多個文件進行配置,這時需要註解@PropertySource、@ImportResource和@bean。我們先來看一下這三個註解在使用上有什麼不同,然後再來詳細說明每一個。
- @PropertySource:用於註解類,告訴當前類使用什麼配置文件,配置文件必須是.property或者.xml類型。
- @ImportResource:通過配置文件注入bean,用於註解主配置類,導入一個或多個定義bean的配置文件,配置文件必須是.xml類型。
- @bean:直接通過配置類注入bean。用於註解方法,這個方法只會被Spring調一次用,並且會生成一個Bean對象,然後將這個對象放到Spring的ioc中進行管理。是Spring Boot推薦的創建Bean的方法。
2.1 @PropertySource
2.1.1 源碼及參數解釋
由Spring框架提供。源碼如下,官方解釋在這裏,比較長,我們這裏就一些主要內容進行簡單地翻譯和解釋。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(PropertySources.class)
public @interface PropertySource {
String name() default "";
String[] value();
boolean ignoreResourceNotFound() default false;
String encoding() default "";
Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;
}
接下來我們來詳細說一下四個參數都是什麼用處。
1、value={"classpath:相對路徑","file:絕對路徑"}。配置文件的路徑及名稱,String數組類型,無默認值,必須配置。也就是說,可以有多個配置文件。並且必須以“classpath”或者“file”開頭。可以添加.property或者.xml類型的配置文件,如果添加其他類型的文件,不會報錯,但不會讀取裏面的內容,比如.yml。YAML文件如何配置,已經在上一篇博客講到了
當我們僅需要添加一個配置文件,並且其他參數都是默認值的時候可以直接寫@PropertySource("classpath:相對路徑")或者@PropertySource("file:絕對路徑")。但還是建議各位同學把內容寫全,方便未來進行修改。
比如,我們在resources文件下有一個新建的student.properties配置文件,我們可以使用@PropertySource("classpath:student.properties")或者@PropertySource("file:項目路徑/src/main/resources/student.properties")。
因爲是以classpath/file開頭的,所以最終Spring知道使用的是它封裝類中Resource類的子類ClasspathResource類還是URLResource類,通過其中的getFileName()方法來獲得。
2、name="配置文件名稱"。指定屬性源的名稱,默認爲空,如果不指定,就會根據value內的每一個字符串來生成。官方文檔中說是調用org.springframework.core.env包中的PropertySource類的getName()方法和org.springframework.core.env包中的Resource類的getFilename()方法。
但是這兩個方法基本上沒有什麼內容。實際上它是由Resource類的子類ClasspathResource類或者URLResource類方法中繼承的getFilename()方法來獲得,有興趣的同學可以在org.springframework.core.io包下看到這整個的底層實現。
3、ignoreResourceNotFound:布爾類型,默認爲false。如果找不到value路徑下的文件時,ignoreResourceNotFound爲false時會報錯,否則忽略
4、encoding:編碼方式。
2.1.2 項目實踐
1、編寫配置文件,這裏我們新建兩個配置文件:student.properties和student.xml,直接在resources右鍵新建file就可以,注意不要只寫名字,還要帶上後綴。
student.birth = 2019/04/12
student.isfemale = true
student.grade = {Art: 87, Math: 100}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<entry key="student.name">王二小</entry>
<entry key="student.birth">2019/04/13</entry>
<entry key="student.isFemale">false</entry>
</properties>
同時,我們在默認的配置文件application.yml中進行配置,如下:
student:
birth: 2019/04/11
teachers: [zhang,wang]
grade: {English: 87, Math: 100}
pets:
spices: Dog
age: 1
2、編寫Student.java文件,Student.java成員和方法不變,確認類前有下面的註解內容,注意我的file後面寫的時絕對路徑,你的內容要根據你項目的具體情況而定。
@ConfigurationProperties(prefix="student")
@PropertySource(value = {"classpath:student.properties","file:D:\\code\\application\\src\\main\\resources\\student.xml"})
3、編寫測試類,並進行測試:
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {
@Autowired
Student student;
@Test
public void contextLoads() {
System.out.println(student);
}
}
4、分析結果輸出結果是:
Student{name='王二小', isfemale=false, birth=Thu Apr 11 00:00:00 CST 2019, teachers=[zhang, wang], grade={English=87, Math=100}, pets=Pet{spices='Dog', age=1}}
我們可以看到當這三類配置文件的屬性發出衝突的時候優先級順序是:其他.yml文件>application>配置的.xml>配置的properties。但是List類型的變量衝突,且衝突的文件是其他.yml文件和默認application文件時,會綜合二者的結果。
2.2 @ImportResource
2.2.1 源碼及參數解釋
仍然是有Spring框架提供,用來註解主配置文件。代碼如下:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
public @interface ImportResource {
@AliasFor("locations")
String[] value() default {};
@AliasFor("value")
String[] locations() default {};
Class<? extends BeanDefinitionReader> reader() default BeanDefinitionReader.class;
}
其實僅有一個字符串數組的變量locations(),別名爲value(),內容填充的規則與@PropertySource相同,只不過我們這裏只能添加.xml的配置文件。
2.2.2 項目實踐
1、在resources目錄下創建一個Spring配置文件,名字爲beans.xml
爲其填充內容使其如下。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<bean id="pet" class="com.springboot.application.bean.Pet"></bean>
</beans>
注意class那裏填充的內容根據你定義的項目結構而定的Reference,比如我們如果需要Pet的Reference,我們就可以在文件名稱那裏右鍵Copy,如下
2、在主配置類上面填充註釋@ImportResource(locations = "classpath:beans.xml"),如下
@ImportResource(locations = "classpath:beans.xml")
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3、在測試類中填充代碼並進行測試:
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {
@Autowired
//應用容器
ApplicationContext ioc;
@Test
public void testApplicationService(){
//容器中是否包括名字爲pet的bean
Boolean res = ioc.containsBean("pet");
System.out.println(res);
}
}
輸出結果爲“true”
2.3 @Bean
2.2.1 源碼及參數解釋
Spring Boot推薦使用配置類代替配置文件來添加bean,除了@Bean還需要@Configuration。@Configuration用來註解類,表明當前類爲一個配置類;@Bean用於註解方法,該方法返回一個bean,且如果@Bean未配置任何參數,該組件在容器中的id是方法名。我們來看一下@Bean的源碼
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
@AliasFor("name")
String[] value() default {};
@AliasFor("value")
String[] name() default {};
Autowire autowire() default Autowire.NO;
String initMethod() default "";
String destroyMethod() default "(inferred)";
}
接下來我們來詳細說一下幾個參數都是什麼用處。
1、value與name互爲別名。配置bean的id,String數組類型,默認值爲空。也就是說,可以有多個名字。注意,一個@Bean註解只能生成一個組件,所以如果有多個名字也是一個組件的多個別名,而第一個名字作爲這個bean的主名,在日誌信息中主要使用這個名字。並且如果設置了這個參數,那麼它的id將不會是方法名。
2、autowire這個參數當前不推薦使用,我們先不講。
3、initMethod=“方法名”:默認值爲“”,@Bean可以指定組件的初始方法,這個方法必須爲無參函數。
4、destroyMethod=“方法名”:默認值爲“”,@Bean可以指定組件的銷燬方法,這個方法必須爲無參函數。
2.2.2 項目實踐
1、對Student的內容進行修改,添加初始化和銷燬方法,仍然需要你自己使用IDEA自動生成toString、get和set方法
public class Student {
private String name;
private Boolean isFemale;
private Date birth;
private List<Object> teachers;
private Map<String, Integer> grade;
private Pet pets;
public void initStudent(){
System.out.println("Student init method called");
}
public void destroyStudent(){
System.out.println("Student destroy method called");
}
//使用IDEA自動生成toString、get和set方法
}
2、在主目錄下增加文件夾config用於存放配置類,然後在這個文件夾下創建MyAppConfig方法,用於生成bean。
@Configuration
public class MyAppConfig {
@Bean(name={"cuteStu1","stu","aStu"},initMethod = "initStudent",destroyMethod = "destroyStudent")
public Student cuteStu(){
return new Student();
}
}
3、編寫測試類用於測試,我們看一下使用不同別名輸出的bean的內容是否相同。
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {
@Autowired
ApplicationContext ioc;
@Test
public void testApplicationService(){
//Boolean res = ;
Object stu1=ioc.getBean("aStu");
Object stu2=ioc.getBean("cuteStu1");
System.out.println(stu1);
System.out.println(stu2);
}
}
4、輸出結果:結果如下,我們對結果分析一下。可以看到使用不同別名輸出的內容相同,並且在構建bean之前調用了我們指定的構建方法,在項目準備結束之前調用了指定的銷燬方法。