Spring—@Value在static中引用方式
@Value注解
spring在讀取yml、properties等文件中的配置時(shí),可直接使用@Value注解。
而且@Value除了支持String,int等類型的數(shù)據(jù),還支持?jǐn)?shù)組、Map、bean多種類型數(shù)據(jù)注入,應(yīng)用起來非常方便。
不過在使用這個(gè)注解的過程中也有需要注意的點(diǎn)。
其中一點(diǎn)就是靜態(tài)屬性的注入時(shí)機(jī),如果使用方法不當(dāng),靜態(tài)屬性無法完成屬性注入;第二點(diǎn)是需要用什么方式,才可以在在靜態(tài)方法、靜態(tài)代碼塊中獲取配置的屬性信息。
本文主要是對(duì)@Value在這兩種情況下的使用進(jìn)行說明,同時(shí)簡(jiǎn)單講解一下@Value注入的原理。
代碼加載順序
在進(jìn)入@Value使用介紹之前,先說下對(duì)于包含靜態(tài)方法、靜態(tài)代碼塊、@PostConstruct方法、默認(rèn)構(gòu)造方法的代碼執(zhí)行順序是什么樣的。
static修飾的靜態(tài)方法、代碼塊、靜態(tài)屬性,都是在類加載的時(shí)候就進(jìn)行加載,并且靜態(tài)代碼塊是會(huì)主動(dòng)執(zhí)行,靜態(tài)方法可以直接通過類名引用。
類實(shí)例是在類加載之后才進(jìn)行的。
下邊的例子針對(duì)靜態(tài)代碼塊和默認(rèn)構(gòu)造參數(shù)、PostConstruct方法進(jìn)行對(duì)比,根據(jù)輸出結(jié)果可知靜態(tài)代碼塊是先加載,然后是默認(rèn)構(gòu)造函數(shù),最后才是PostConstruct修飾的方法。所以如果想要在靜態(tài)代碼塊中使用spring注入的屬性需要做些特殊處理(后邊會(huì)講到)。
@Component
public class FuncLoadOrderService {
//記錄每個(gè)步驟執(zhí)行的順序
private static AtomicLong step = new AtomicLong(0);
//默認(rèn)構(gòu)造參數(shù)
public FuncLoadOrderService() {
System.out.println("construct run step " + step.getAndIncrement());
}
//靜態(tài)代碼塊
static {
System.out.println("static block run step " + step.getAndIncrement());
}
//
@PostConstruct
public void constructFunc() {
System.out.println("PostConstruct run step " + step.getAndIncrement());
}
}結(jié)果輸出
static block run step 0
construct run step 1
PostConstruct run step 2
@Value屬性注入
普通屬性注入
此處說的普通屬性為非static變量,類似如下聲明
//這就是一個(gè)普通屬性 private String testConfigId;
- 結(jié)論先行。
- 對(duì)于這種注入方式,由于實(shí)例是在默認(rèn)構(gòu)造參數(shù)執(zhí)行之后才會(huì)創(chuàng)建,且方法加載順序?yàn)?靜態(tài)代碼塊 --> 靜態(tài)方法 --> 默認(rèn)構(gòu)造參數(shù) --> PostConstruct修飾的方法。
- 所以普通屬性使用@Value注入的變量,只有在PostConstruct修飾的方法可以取到值,即只有對(duì)象bean完成了初始化才可以獲取到配置值。
- 想在PostConstruct前的幾步中取到值需要直接讀取配置文件,加載內(nèi)容。
屬性的普通注入方式如下,直接使用@Value注解就可以注入配置文件中的配置。
@Value("${test.configId}")
private String testConfigId;這種方式是工作中比較常用的注入方式了,但是因?yàn)関alue是在類實(shí)例創(chuàng)建之后才注入的,
所以這里有兩個(gè)注意點(diǎn)
- 1.默認(rèn)構(gòu)造參數(shù)中無法獲取注入的value
- 2.static修飾的方法無法獲取注入的value
如果一定想要在默認(rèn)構(gòu)造參數(shù)里獲取@Value注入的值怎么辦呢?想一想為什么默認(rèn)構(gòu)造參數(shù)無法使用@Value注入的值~~
是不是因?yàn)榇藭r(shí)bean還沒有創(chuàng)建,類對(duì)象還沒有實(shí)例化,所以所有依賴Bean創(chuàng)建方式來注入值的方式都不可以使用,因此可以考慮直接讀取配置文件來獲取值。
這里提供兩種方式。第一種是使用 YamlMapFactoryBean 將配置文件的內(nèi)容讀到map中
YamlMapFactoryBean yaml = new YamlMapFactoryBean();
yaml.setResources(new ClassPathResource("application.yml"));
Map<String, Object> configMap = yaml.getObject();這個(gè)方式可以達(dá)到目的,但是數(shù)據(jù)是嵌套的map,沒有按照key展開,使用起來不是很方便。
比如配置文件中內(nèi)容如下,如果用YamlMapFactoryBean方式讀配置,只能先 map.get(“test”) 獲取返回值,進(jìn)行類型轉(zhuǎn)換,再做其他處理
test:
configId: 1oiieuu
configMap: "{\"id\":123,\"key\":12333}"第二種方式和第一個(gè)類似,只不過讀出來的數(shù)據(jù)是按照key做了展開。
這種方式使用的是 YamlPropertiesFactoryBean 。
YamlPropertiesFactoryBean yamlProperties = new YamlPropertiesFactoryBean();
yamlProperties.setResources(new ClassPathResource("application.yml"));
Properties properties = yamlProperties.getObject();對(duì)于上邊提到的同樣的配置,如果想讀configId,可以直接使用 properties.get(“test.configId”) / properties.getProperty(“test.configId”) 等方法。
使用 YamlPropertiesFactoryBean 的方式最接近直接用@Value,強(qiáng)烈推薦??!
static塊、默認(rèn)構(gòu)造方法獲取@Value值
對(duì)于static代碼塊和默認(rèn)構(gòu)造方法,想要獲取配置文件中的值,只能通過將配置文件讀到內(nèi)存,轉(zhuǎn)成map、properties等方法(也可以轉(zhuǎn)json、yaml等),來獲取值。
靜態(tài)方法中獲取@Value值
上邊講到普通的變量使用@Value修飾,在靜態(tài)方法中獲取不到值,是因?yàn)殪o態(tài)方法中想要獲取普通變量,需要用new來創(chuàng)建對(duì)象,new出來的對(duì)象沒有注入@Value,所以想要在靜態(tài)方法中使用@Value修飾的對(duì)象,需要把對(duì)象定義為static類型,
代碼如下:
private static String configId;
@Value("${test.configId}")
public void setConfigId(String configId) {
FuncLoadOrderService.configId = configId;
}
public static void staticFuncTest() {
System.out.println("static func " + FuncLoadOrderService.configId);
}原理
上邊我們先給出了在不同情況下使用@Value會(huì)出現(xiàn)的情況,并給出了解決方案,下邊我們來簡(jiǎn)單看下@Value為什么有些情況不能完成value注入。
- 結(jié)論先行。
- 對(duì)于@Value修飾的屬性、方法,在底層處理的時(shí)候和@Autowired處理邏輯是一樣的。
- 處理邏輯是由AutowiredAnnotationBeanPostProcessor類的內(nèi)部類,AutowiredFieldElement 和 AutowiredMethodElement進(jìn)行具體邏輯處理。
- 其中AutowiredFieldElement處理使用了相關(guān)注解的屬性
- AutowiredMethodElement處理使用了相關(guān)注解的方法
- 以屬性處理鏈路為例,給出處理邏輯的調(diào)用鏈
- AutowiredFieldElement#inject#resolveDependency#doResolveDependency#convertIfNecessary
//AutowiredAnnotationBeanPostProcessor默認(rèn)構(gòu)造方法,把此類可以處理的類型加入到列表中,從代碼中可以看到這個(gè)類可以處理 @Value、@Autowired、@Inject三種注解
public AutowiredAnnotationBeanPostProcessor() {
this.autowiredAnnotationTypes.add(Autowired.class);
this.autowiredAnnotationTypes.add(Value.class);
try {
this.autowiredAnnotationTypes.add((Class<? extends Annotation>)
ClassUtils.forName("javax.inject.Inject", AutowiredAnnotationBeanPostProcessor.class.getClassLoader()));
logger.trace("JSR-330 'javax.inject.Inject' annotation found and supported for autowiring");
}
catch (ClassNotFoundException ex) {
// JSR-330 API not available - simply skip.
}
}內(nèi)部類AutowiredFieldElement的關(guān)鍵源碼如下。
從源碼中可看到處理邏輯都是針對(duì)bean來進(jìn)行處理,而static修飾的方法、屬性,是在bean中獲取不到,所以static屬性使用@Value無法注入對(duì)應(yīng)的值。
@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
Field field = (Field) this.member;
Object value;
if (this.cached) {
//在緩存中,直接進(jìn)行屬性處理
value = resolvedCachedArgument(beanName, this.cachedFieldValue);
}
else {
//設(shè)置屬性信息
DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
desc.setContainingClass(bean.getClass());
Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
Assert.state(beanFactory != null, "No BeanFactory available");
TypeConverter typeConverter = beanFactory.getTypeConverter();
try {
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
}
synchronized (this) {
if (!this.cached) {
//加入緩存處理
}
}
}
if (value != null) {
ReflectionUtils.makeAccessible(field);
field.set(bean, value);
}
}AutowiredMethodElement和AutowiredFieldElement一樣,都是繼承自 InjectionMetadata.InjectedElement,所以對(duì)于使用@Value的方法能夠完成注入。
下邊這段代碼中configId能夠注入,就是因?yàn)锧Value使用在方法上,在bean加載時(shí)會(huì)將configId裝配到bean中。
private static String configId;
@Value("${test.configId}")
public void setConfigId(String configId) {
FuncLoadOrderService.configId = configId;
}
public static void staticFuncTest() {
System.out.println("static func " + FuncLoadOrderService.configId);
}結(jié)論
@Value是平時(shí)用的比較多的注解,使用時(shí)也會(huì)遇到某些情況注入失敗,所以進(jìn)行了一番了解并將了解的結(jié)論分享給大家。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
解決Spring Boot項(xiàng)目端口8080被占用的問題
這篇文章主要介紹了解決Spring Boot項(xiàng)目端口8080被占用的問題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09
詳解SpringBoot是如何整合SpringDataRedis的?
今天給大家?guī)淼氖顷P(guān)于Java的相關(guān)知識(shí),文章圍繞著SpringBoot是如何整合SpringDataRedis展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-06-06
java實(shí)現(xiàn)cassandra高級(jí)操作之分頁(yè)實(shí)例(有項(xiàng)目具體需求)
這篇文章主要介紹了java實(shí)現(xiàn)cassandra高級(jí)操作之分頁(yè)實(shí)例(有項(xiàng)目具體需求),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-04-04
SpringBoot中@RestControllerAdvice 全局異常處理的實(shí)現(xiàn)
本文主要介紹了SpringBoot中@RestControllerAdvice 全局異常處理的實(shí)現(xiàn),通過定義統(tǒng)一響應(yīng)格式、自定義異常類及測(cè)試驗(yàn)證,確保接口異常時(shí)返回指定格式的提示信息,提升錯(cuò)誤處理一致性2025-06-06
SpringBoot實(shí)現(xiàn)接口數(shù)據(jù)加解密的三種解決方案
這篇文章主要介紹了SpringBoot實(shí)現(xiàn)接口數(shù)據(jù)加解密的三種解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-06-06
Springboot整合MybatisPlus的實(shí)現(xiàn)過程解析
這篇文章主要介紹了Springboot整合MybatisPlus的實(shí)現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10

