概述
在Spring 組件中,通常使用@Value注解讀取 properties 文件的配置值。但如果在配置文件或啟動參數中未指定對應的參數值,則項目在啟動的時候會拋出異常,導致服務啟動失敗,異常信息往往提示缺少必要的屬性配置信息:
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'self.user.address' in value "${self.user.address}"
at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:180) ~[spring-core-5.3.7.jar:5.3.7]
at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:126) ~[spring-core-5.3.7.jar:5.3.7]
at org.springframework.core.env.AbstractPropertyResolver.doResolvePlaceholders(AbstractPropertyResolver.java:239) ~[spring-core-5.3.7.jar:5.3.7]
at org.springframework.core.env.AbstractPropertyResolver.resolveRequiredPlaceholders(AbstractPropertyResolver.java:210) ~[spring-core-5.3.7.jar:5.3.7]
at org.springframework.context.support.PropertySourcesPlaceholderConfigurer.lambda$processProperties$0(PropertySourcesPlaceholderConfigurer.java:175) ~[spring-context-5.3.7.jar:5.3.7]
at org.springframework.beans.factory.support.AbstractBeanFactory.resolveEmbeddedValue(AbstractBeanFactory.java:936) ~[spring-beans-5.3.7.jar:5.3.7]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1321) ~[spring-beans-5.3.7.jar:5.3.7]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1300) ~[spring-beans-5.3.7.jar:5.3.7]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:657) ~[spring-beans-5.3.7.jar:5.3.7]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:640) ~[spring-beans-5.3.7.jar:5.3.7]
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:119) ~[spring-beans-5.3.7.jar:5.3.7]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:399) ~[spring-beans-5.3.7.jar:5.3.7]
... 17 common frames omitted
解決辦法是在Apollo等配置文件中對@Value對應的值進行配置,或設置默認值。本文介紹@Value注解的語法糖,介紹如何設置key的默認值,介紹如何配置數組、列表和map的初始值。
@Value注解語法糖
注解@Value用於讀取配置文件中的屬性,語法糖有以下三種。
(1) 直接賦值
@Value("string value")
這種方式就是直接把要注入的值字面量寫在注解里,比較少用。如果要寫死在注解里了,那直接定義變量的時候寫死就可以了。
(2) 使用占位符$
@Value(${key : default_value})
這是最常用的姿勢,通過屬性名的key,將值注入變量。default_value為默認值。$注入的是配置文件對應的property,使用英文冒號“:”對未配置或值為空的key設置默認值。
(3)SpEL表達式
@Value(#{self.key?: default_value})
使用 Spring Expression Language (SpEL) 設置默認值。#注入的是SpEL表達式對應的內容,使用“?:”對未配置或值為空的表達式設置默認值。
另外,占位符和SpEL表達式可以雙劍合璧,解鎖方式如下:
@Value("#{'${listOfValues}'.split(',')}")
private List valueList;
溫馨提示,內外順序不能顛倒,必須是#{}外面,${}在里面!
使用場景
對於注入的場景,主要有三種:
(1)bean聲明的變量,香餑餑級別用法。
(2)setter方法注入,不常用。
(3)構造方法或其它方法的入參,這就是雞肋,不能把關鍵參數寫死。
示例如下:
//bean聲明的變量
public static class MyValues {
@Value("${self.user.name}")
private String userName;
}
//setter 方法中
public static class MyValues {
private String userName;
@Value("${self.user.name}")
public void setUserName(String userName) {
this.userName = userName;
}
}
//方法入參
public class MyValues {
private String userName;
@Autowired
public void configure(@Value("${self.user.name}") String userName) {
this.userName = userName;
}
}
基本類型
設置默認值時,在key后加上冒號及其默認值即可,方法如下:
public class ReadConfig {
// 未指定默認值
@Value("${self.user.name}")
private String userName;
// 使用英文冒號指定默認值為“defaultValue”
@Value("${self.user.address:defaultValue}")
private String userAddress;
@Value("${self.bool:true}")
private boolean booleanWithDefaultValue;
@Value("${self.user.age:21}")
private Integer userAge;
}
針對以上第一種@Value注解的使用方式,如果username對應的屬性值未在Apollo配置中心、application.properties文件中配置或未在java -jar命令中傳遞參數,那么服務啟動時將拋出 IllegalArgumentException 異常,導致服務發布失敗。而關於第二種方式,通過“:”指定默認值,則可以正常啟動服務。
數組和列表
在配置文件中配置數組或者列表時,使用的默認分隔符是英文逗號,也可以自定義。 demo如下:
self.array = xxx1,xxx2,xxx3
基於配置文件注入屬性值:
/**
* 注入數組,默認','分隔
*/
@Value("${self.array:one,two,three}")
String[] array;
/**
* 注入列表,分隔符使用英文分號
*/
@Value("#{'${self.empty.array:}'.empty ? null : '${self.empty.array}'.split(';')}")
List<String> list;
設置map
@Value("#{${self.map1:{name: 'default', age: 18, city: '河南'}}}")
private Map<String, Object> defaultMap;
map設置默認值也是使用英文冒號。
綜合實戰
配置文件配置如下:
self.param.user.name = 樓蘭胡楊
self.array = xxx1,xxx2,xxx3
self.map={name: 'Wiener', age: '18', city: '商丘'}
測試用例如下:
// 指定默認值
@Value("${self.user.name:defaultValue}")
private String userName;
@Value("${self.array}")
private List<String> myList;
@Value("${self.array:one,two,three}")
private String[] myArray;
// 未配置屬性,使用默認值空數組
@Value("${self.empty.array:}")
private String[] myEmptyArray;
// 未配置屬性,使用null
@Value("#{'${self.empty.array:}'.empty ? null : '${self.empty.array}'.split(',')}")
private List<String> myNullList;
@Value("#{${self.map}}")
private Map<String, Object> myMap;
@Value("#{${self.map}.city}")
private String cityValue;
@Value("#{${self.map1:{name: 'default', age: 18, city: '河南'}}}")
private Map<String, Object> defaultMap;
//表達式結果
@Value("#{ T(java.lang.Math).random() * 100.0 }")
private double randomNumber;
@PostMapping("/readConfig")
public Map<String, Object> readConfig(@Value("${self.param.user.name}") String myUserName) {
System.out.println("雞肋般的傳參 myUserName=" + myUserName);
System.out.println("#````````````````````````# userName=" + userName);
System.out.println("#````````````````````````# myList=" + myList);
System.out.println("#````````````````````````# cityValue=" + cityValue);
System.out.println("#``````使用默認值 defaultMap=" + defaultMap);
System.out.println("#````````````````````````# myArray=");
for (String one : myArray) {
System.out.println(one + " 數組");
}
System.out.println("空數組myEmptyArray大小:" + myEmptyArray.length);
System.out.println("空列表myNullList是否為null:" + CollectionUtils.isEmpty(myNullList));
System.out.println("random number:" + randomNumber);
return myMap;
}
執行結果如下:
雞肋般的傳參 myUserName=樓蘭胡楊
#````````````````````````# userName=defaultValue
#````````````````````````# myList=[xxx1, xxx2, xxx3]
#````````````````````````# cityValue=商丘
#``````使用默認值 defaultMap={name=default, age=18, city=河南}
#````````````````````````# myArray=
xxx1 數組
xxx2 數組
xxx3 數組
空數組myEmptyArray大小:0
空列表myNullList是否為null:true
random number:41.28241165389434
小結
本文結合案例講解了@Value注解的使用方法,包括如何設置數組、列表和map的默認值。
最后,奉上一個歸納總結,如下圖[1]所示:
對於Wiener以上的話題,大家又有什么自己的獨特見解呢?歡迎在下方評論區留言!
