Spring—@Value在static中引用方式
@Value注解
spring在讀取yml、properties等文件中的配置時(shí),可直接使用@Value注解。
而且@Value除了支持String,int等類(lèi)型的數(shù)據(jù),還支持?jǐn)?shù)組、Map、bean多種類(lèi)型數(shù)據(jù)注入,應(yīng)用起來(lái)非常方便。
不過(guò)在使用這個(gè)注解的過(guò)程中也有需要注意的點(diǎn)。
其中一點(diǎn)就是靜態(tài)屬性的注入時(shí)機(jī),如果使用方法不當(dāng),靜態(tài)屬性無(wú)法完成屬性注入;第二點(diǎn)是需要用什么方式,才可以在在靜態(tài)方法、靜態(tài)代碼塊中獲取配置的屬性信息。
本文主要是對(duì)@Value在這兩種情況下的使用進(jìn)行說(shuō)明,同時(shí)簡(jiǎn)單講解一下@Value注入的原理。
代碼加載順序
在進(jìn)入@Value使用介紹之前,先說(shuō)下對(duì)于包含靜態(tài)方法、靜態(tài)代碼塊、@PostConstruct方法、默認(rèn)構(gòu)造方法的代碼執(zhí)行順序是什么樣的。
static修飾的靜態(tài)方法、代碼塊、靜態(tài)屬性,都是在類(lèi)加載的時(shí)候就進(jìn)行加載,并且靜態(tài)代碼塊是會(huì)主動(dòng)執(zhí)行,靜態(tài)方法可以直接通過(guò)類(lèi)名引用。
類(lèi)實(shí)例是在類(lèi)加載之后才進(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屬性注入
普通屬性注入
此處說(shuō)的普通屬性為非static變量,類(lèi)似如下聲明
//這就是一個(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是在類(lèi)實(shí)例創(chuàng)建之后才注入的,
所以這里有兩個(gè)注意點(diǎn)
- 1.默認(rèn)構(gòu)造參數(shù)中無(wú)法獲取注入的value
- 2.static修飾的方法無(wú)法獲取注入的value
如果一定想要在默認(rèn)構(gòu)造參數(shù)里獲取@Value注入的值怎么辦呢?想一想為什么默認(rèn)構(gòu)造參數(shù)無(wú)法使用@Value注入的值~~
是不是因?yàn)榇藭r(shí)bean還沒(méi)有創(chuàng)建,類(lèi)對(duì)象還沒(méi)有實(shí)例化,所以所有依賴(lài)Bean創(chuàng)建方式來(lái)注入值的方式都不可以使用,因此可以考慮直接讀取配置文件來(lái)獲取值。
這里提供兩種方式。第一種是使用 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,沒(méi)有按照key展開(kāi),使用起來(lái)不是很方便。
比如配置文件中內(nèi)容如下,如果用YamlMapFactoryBean方式讀配置,只能先 map.get(“test”) 獲取返回值,進(jìn)行類(lèi)型轉(zhuǎn)換,再做其他處理
test:
configId: 1oiieuu
configMap: "{\"id\":123,\"key\":12333}"第二種方式和第一個(gè)類(lèi)似,只不過(guò)讀出來(lái)的數(shù)據(jù)是按照key做了展開(kāi)。
這種方式使用的是 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)造方法,想要獲取配置文件中的值,只能通過(guò)將配置文件讀到內(nèi)存,轉(zhuǎn)成map、properties等方法(也可以轉(zhuǎn)json、yaml等),來(lái)獲取值。
靜態(tài)方法中獲取@Value值
上邊講到普通的變量使用@Value修飾,在靜態(tài)方法中獲取不到值,是因?yàn)殪o態(tài)方法中想要獲取普通變量,需要用new來(lái)創(chuàng)建對(duì)象,new出來(lái)的對(duì)象沒(méi)有注入@Value,所以想要在靜態(tài)方法中使用@Value修飾的對(duì)象,需要把對(duì)象定義為static類(lèi)型,
代碼如下:
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)的情況,并給出了解決方案,下邊我們來(lái)簡(jiǎn)單看下@Value為什么有些情況不能完成value注入。
- 結(jié)論先行。
- 對(duì)于@Value修飾的屬性、方法,在底層處理的時(shí)候和@Autowired處理邏輯是一樣的。
- 處理邏輯是由AutowiredAnnotationBeanPostProcessor類(lèi)的內(nèi)部類(lèi),AutowiredFieldElement 和 AutowiredMethodElement進(jìn)行具體邏輯處理。
- 其中AutowiredFieldElement處理使用了相關(guān)注解的屬性
- AutowiredMethodElement處理使用了相關(guān)注解的方法
- 以屬性處理鏈路為例,給出處理邏輯的調(diào)用鏈
- AutowiredFieldElement#inject#resolveDependency#doResolveDependency#convertIfNecessary
//AutowiredAnnotationBeanPostProcessor默認(rèn)構(gòu)造方法,把此類(lèi)可以處理的類(lèi)型加入到列表中,從代碼中可以看到這個(gè)類(lèi)可以處理 @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)部類(lèi)AutowiredFieldElement的關(guān)鍵源碼如下。
從源碼中可看到處理邏輯都是針對(duì)bean來(lái)進(jìn)行處理,而static修飾的方法、屬性,是在bean中獲取不到,所以static屬性使用@Value無(wú)法注入對(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)文章
Java中transient關(guān)鍵字的詳細(xì)總結(jié)
本文要介紹的是Java中的transient關(guān)鍵字,transient是短暫的意思。對(duì)于transient 修飾的成員變量,在類(lèi)的實(shí)例對(duì)象的序列化處理過(guò)程中會(huì)被忽略,感興趣的朋友可以參考閱讀2023-04-04
SpringCloud使用feign調(diào)用錯(cuò)誤的問(wèn)題
這篇文章主要介紹了SpringCloud使用feign調(diào)用錯(cuò)誤的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06
java接收文件流+response.body()調(diào)用兩次問(wèn)題(分別接收文件和對(duì)象)
這篇文章主要介紹了java接收文件流+response.body()調(diào)用兩次問(wèn)題(分別接收文件和對(duì)象),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06
完美解決Get和Post請(qǐng)求中文亂碼的問(wèn)題
下面小編就為大家?guī)?lái)一篇完美解決Get和Post請(qǐng)求中文亂碼的問(wèn)題。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-05-05
java并發(fā)編程_線(xiàn)程池的使用方法(詳解)
下面小編就為大家?guī)?lái)一篇java并發(fā)編程_線(xiàn)程池的使用方法(詳解)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-05-05

