Spring—@Value在static中引用方式
@Value注解
spring在讀取yml、properties等文件中的配置時,可直接使用@Value注解。
而且@Value除了支持String,int等類型的數(shù)據(jù),還支持?jǐn)?shù)組、Map、bean多種類型數(shù)據(jù)注入,應(yīng)用起來非常方便。
不過在使用這個注解的過程中也有需要注意的點。
其中一點就是靜態(tài)屬性的注入時機,如果使用方法不當(dāng),靜態(tài)屬性無法完成屬性注入;第二點是需要用什么方式,才可以在在靜態(tài)方法、靜態(tài)代碼塊中獲取配置的屬性信息。
本文主要是對@Value在這兩種情況下的使用進行說明,同時簡單講解一下@Value注入的原理。
代碼加載順序
在進入@Value使用介紹之前,先說下對于包含靜態(tài)方法、靜態(tài)代碼塊、@PostConstruct方法、默認構(gòu)造方法的代碼執(zhí)行順序是什么樣的。
static修飾的靜態(tài)方法、代碼塊、靜態(tài)屬性,都是在類加載的時候就進行加載,并且靜態(tài)代碼塊是會主動執(zhí)行,靜態(tài)方法可以直接通過類名引用。
類實例是在類加載之后才進行的。
下邊的例子針對靜態(tài)代碼塊和默認構(gòu)造參數(shù)、PostConstruct方法進行對比,根據(jù)輸出結(jié)果可知靜態(tài)代碼塊是先加載,然后是默認構(gòu)造函數(shù),最后才是PostConstruct修飾的方法。所以如果想要在靜態(tài)代碼塊中使用spring注入的屬性需要做些特殊處理(后邊會講到)。
@Component public class FuncLoadOrderService { //記錄每個步驟執(zhí)行的順序 private static AtomicLong step = new AtomicLong(0); //默認構(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變量,類似如下聲明
//這就是一個普通屬性 private String testConfigId;
- 結(jié)論先行。
- 對于這種注入方式,由于實例是在默認構(gòu)造參數(shù)執(zhí)行之后才會創(chuàng)建,且方法加載順序為 靜態(tài)代碼塊 --> 靜態(tài)方法 --> 默認構(gòu)造參數(shù) --> PostConstruct修飾的方法。
- 所以普通屬性使用@Value注入的變量,只有在PostConstruct修飾的方法可以取到值,即只有對象bean完成了初始化才可以獲取到配置值。
- 想在PostConstruct前的幾步中取到值需要直接讀取配置文件,加載內(nèi)容。
屬性的普通注入方式如下,直接使用@Value注解就可以注入配置文件中的配置。
@Value("${test.configId}") private String testConfigId;
這種方式是工作中比較常用的注入方式了,但是因為value是在類實例創(chuàng)建之后才注入的,
所以這里有兩個注意點
- 1.默認構(gòu)造參數(shù)中無法獲取注入的value
- 2.static修飾的方法無法獲取注入的value
如果一定想要在默認構(gòu)造參數(shù)里獲取@Value注入的值怎么辦呢?想一想為什么默認構(gòu)造參數(shù)無法使用@Value注入的值~~
是不是因為此時bean還沒有創(chuàng)建,類對象還沒有實例化,所以所有依賴Bean創(chuàng)建方式來注入值的方式都不可以使用,因此可以考慮直接讀取配置文件來獲取值。
這里提供兩種方式。第一種是使用 YamlMapFactoryBean 將配置文件的內(nèi)容讀到map中
YamlMapFactoryBean yaml = new YamlMapFactoryBean(); yaml.setResources(new ClassPathResource("application.yml")); Map<String, Object> configMap = yaml.getObject();
這個方式可以達到目的,但是數(shù)據(jù)是嵌套的map,沒有按照key展開,使用起來不是很方便。
比如配置文件中內(nèi)容如下,如果用YamlMapFactoryBean方式讀配置,只能先 map.get(“test”) 獲取返回值,進行類型轉(zhuǎn)換,再做其他處理
test: configId: 1oiieuu configMap: "{\"id\":123,\"key\":12333}"
第二種方式和第一個類似,只不過讀出來的數(shù)據(jù)是按照key做了展開。
這種方式使用的是 YamlPropertiesFactoryBean 。
YamlPropertiesFactoryBean yamlProperties = new YamlPropertiesFactoryBean(); yamlProperties.setResources(new ClassPathResource("application.yml")); Properties properties = yamlProperties.getObject();
對于上邊提到的同樣的配置,如果想讀configId,可以直接使用 properties.get(“test.configId”) / properties.getProperty(“test.configId”) 等方法。
使用 YamlPropertiesFactoryBean 的方式最接近直接用@Value,強烈推薦!!
static塊、默認構(gòu)造方法獲取@Value值
對于static代碼塊和默認構(gòu)造方法,想要獲取配置文件中的值,只能通過將配置文件讀到內(nèi)存,轉(zhuǎn)成map、properties等方法(也可以轉(zhuǎn)json、yaml等),來獲取值。
靜態(tài)方法中獲取@Value值
上邊講到普通的變量使用@Value修飾,在靜態(tài)方法中獲取不到值,是因為靜態(tài)方法中想要獲取普通變量,需要用new來創(chuàng)建對象,new出來的對象沒有注入@Value,所以想要在靜態(tài)方法中使用@Value修飾的對象,需要把對象定義為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會出現(xiàn)的情況,并給出了解決方案,下邊我們來簡單看下@Value為什么有些情況不能完成value注入。
- 結(jié)論先行。
- 對于@Value修飾的屬性、方法,在底層處理的時候和@Autowired處理邏輯是一樣的。
- 處理邏輯是由AutowiredAnnotationBeanPostProcessor類的內(nèi)部類,AutowiredFieldElement 和 AutowiredMethodElement進行具體邏輯處理。
- 其中AutowiredFieldElement處理使用了相關(guān)注解的屬性
- AutowiredMethodElement處理使用了相關(guān)注解的方法
- 以屬性處理鏈路為例,給出處理邏輯的調(diào)用鏈
- AutowiredFieldElement#inject#resolveDependency#doResolveDependency#convertIfNecessary
//AutowiredAnnotationBeanPostProcessor默認構(gòu)造方法,把此類可以處理的類型加入到列表中,從代碼中可以看到這個類可以處理 @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)鍵源碼如下。
從源碼中可看到處理邏輯都是針對bean來進行處理,而static修飾的方法、屬性,是在bean中獲取不到,所以static屬性使用@Value無法注入對應(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) { //在緩存中,直接進行屬性處理 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,所以對于使用@Value的方法能夠完成注入。
下邊這段代碼中configId能夠注入,就是因為@Value使用在方法上,在bean加載時會將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是平時用的比較多的注解,使用時也會遇到某些情況注入失敗,所以進行了一番了解并將了解的結(jié)論分享給大家。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java中transient關(guān)鍵字的詳細總結(jié)
本文要介紹的是Java中的transient關(guān)鍵字,transient是短暫的意思。對于transient 修飾的成員變量,在類的實例對象的序列化處理過程中會被忽略,感興趣的朋友可以參考閱讀2023-04-04SpringCloud使用feign調(diào)用錯誤的問題
這篇文章主要介紹了SpringCloud使用feign調(diào)用錯誤的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-06-06java接收文件流+response.body()調(diào)用兩次問題(分別接收文件和對象)
這篇文章主要介紹了java接收文件流+response.body()調(diào)用兩次問題(分別接收文件和對象),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-06-06