解決Spring中@Value注解取值為null問(wèn)題
一、背景
近期應(yīng)用中因業(yè)務(wù)迭代需要接入 user 客戶(hù)端,接入后總是啟動(dòng)失敗,報(bào)注冊(cè) user bean 依賴(lài)的配置屬性為 null(如 appName,group 等都為空),示例代碼如下:
@Configuration public class ConsumerBeanConfig { @Value("${project.name}") private String appName; @Value("${spring.hsf.group}") private String group; @Value("${spring.hsf.version}") private String version; @Bean public UserCommonContext userCommonContext() { UserCommonContext commonContext = new UserCommonContext(); commonContext.setAppName(appName); return commonContext; } @Bean public HSFSpringConsumerBean userReadService() { HSFSpringConsumerBean consumer = new HSFSpringConsumerBean(); consumer.setInterfaceClass(UserReadService.class); consumer.setVersion(version); consumer.setGroup(group); return consumer; } //...... }
二、@Value 取值為 null 原因分析
2.1. @Value 取值為 null 常見(jiàn)原因分析
常見(jiàn)現(xiàn)象一:類(lèi)沒(méi)有交給 Spring 管理,比如類(lèi)沒(méi)有加上 @Component 等注解
- 錯(cuò)誤案例
// 配置類(lèi) public class PeopleConfigValue { @Value("${people.name}") private String name; @Value("${people.age}") private String age; public PeopleConfigValue() { } // getter and setter... } // 測(cè)試類(lèi)(以下取值為空) PeopleConfigValue peopleConfigValue = new PeopleConfigValue(); System.out.println("get peopleConfigValue-name value " + peopleConfigValue.getName()); System.out.println("get peopleConfigValue-age value " + peopleConfigValue.getAge());
- 正常案例
@Component public class PeopleConfigValue { @Value("${people.name}") private String name; @Value("${people.age}") private String age; public PeopleConfigValue() { } // getter and setter... } // 測(cè)試類(lèi)(以下取值正常) PeopleConfigValue peopleConfigValue = SpringContextUtil.getBean(PeopleConfigValue.class); System.out.println("get peopleConfigValue-name value " + peopleConfigValue.getName()); System.out.println("get peopleConfigValue-age value " + peopleConfigValue.getAge());
常見(jiàn)現(xiàn)象二:手動(dòng) new 對(duì)象實(shí)例,沒(méi)有從 Spring 容器中獲取
- 錯(cuò)誤案例
@Component public class PeopleConfigValue { @Value("${people.name}") private String name; @Value("${people.age}") private String age; public PeopleConfigValue() { } // getter and setter... } // 測(cè)試類(lèi)(以下取值為空) PeopleConfigValue peopleConfigValue = new PeopleConfigValue(); System.out.println("get peopleConfigValue-name value " + peopleConfigValue.getName()); System.out.println("get peopleConfigValue-age value " + peopleConfigValue.getAge());
- 正確案例,參考第一個(gè)現(xiàn)象。
常見(jiàn)現(xiàn)象三:使用 static 或 final 修飾成員變量
使用 static 或 final 修飾成員變量值不可改變,注解無(wú)法注入配置值。
- 錯(cuò)誤案例
@Component public class PeopleConfigValue { @Value("${people.name}") private static String name; @Value("${people.age}") private static String age; public PeopleConfigValue() { } // getter } // 測(cè)試類(lèi)(以下取值為空) PeopleConfigValue peopleConfigValue = SpringContextUtil.getBean(PeopleConfigValue.class); System.out.println("get peopleConfigValue-name value " + peopleConfigValue.getName()); System.out.println("get peopleConfigValue-age value " + peopleConfigValue.getAge());
正確案例
以下方式不推薦,作為 static 或 final 修飾成員變量值應(yīng)該是不可變的,以下可通過(guò) setter 方式修改值:
@Component public class PeopleConfigValue { private static String name; private static String age; public PeopleConfigValue() { } public static String getName() { return name; } @Value("${people.name}") public void setName(String nameValue) { name = nameValue; } public static String getAge() { return age; } @Value("${people.age}") public void setAge(String ageValue) { age = ageValue; } } // 測(cè)試類(lèi),取值正常 PeopleConfigValue peopleConfigValue = SpringContextUtil.getBean(PeopleConfigValue.class); System.out.println("get peopleConfigValue-name value " + peopleConfigValue.getName()); System.out.println("get peopleConfigValue-age value " + peopleConfigValue.getAge());
2.2 案例原因分析
上述案例中 @Value 的使用方式是常規(guī)使用方式,不應(yīng)該出現(xiàn)問(wèn)題,開(kāi)始懷疑是與 Spring 應(yīng)用上下文 Bean 的初始化順序有關(guān),排查這個(gè)問(wèn)題還是先摸清一下 Spring Boot 的啟動(dòng)原理及 @Value 解析機(jī)制,直接上圖:
圖片箭頭指向即 SpringApplication 啟動(dòng)階段,在這個(gè)過(guò)程中進(jìn)行 Bean 的實(shí)例化,進(jìn)一步細(xì)化 SpringApplication 啟動(dòng)流程如下:
眾所周知,應(yīng)用中配置的 bean 在 Spring 啟動(dòng)時(shí)會(huì)全部解析為 BeanDefinition(可視為 bean 的元信息,圖中第 2 步),同時(shí) Spring 提供了 BeanFactoryPostProcessor 接口用于用戶(hù)擴(kuò)展(圖中第 5 步,比如在這里可以修改BeanDefinition 的元數(shù)據(jù)) ,最后在實(shí)例化 bean 過(guò)程時(shí)(SpringApplication 啟動(dòng)流程圖中第 11.3 步)會(huì)讀取相應(yīng)的 BeanDefinition 進(jìn)行初始化。
回到 @Value 注解占位符的解析機(jī)制,@Value 注解占位符靠 PropertyResourceConfigurer 來(lái)解析(PropertySourcesPlaceholderConfigurer 會(huì)調(diào)用 PropertyResourceConfigurer 解析能力來(lái)解析占位符,并存儲(chǔ)到 propertySources 屬性集合中),而 PropertyResourceConfigurer 正是實(shí)現(xiàn)了 BeanFactoryPostProcessor 接口,在 BeanFactory 后處理階段進(jìn)行了占位符替換,且 PropertyResourceConfigurer 的優(yōu)化級(jí)最低(這里有個(gè)風(fēng)險(xiǎn)點(diǎn):任何應(yīng)用依賴(lài)的實(shí)現(xiàn) BeanFactoryPostProcessor 接口的 bean 都會(huì)比 PropertyResourceConfigurer 先執(zhí)行)。
理解了 Spring 的啟動(dòng)機(jī)制和 @Value 注解占位符的解析機(jī)制,再排查應(yīng)用代碼發(fā)現(xiàn) UserCommonContext 也實(shí)現(xiàn)了 BeanFactoryPostProcessor 接口,也就是說(shuō),出現(xiàn)了下述情況:
由于 UserCommonContext 依賴(lài)了 UserBeanConfig,導(dǎo)致 UserBeanConfig 提前初始化,但此時(shí) @Value 中的占位符還未替換,那么 UserBeanConfig 中所有標(biāo)記 @Value 注解屬性都為 null,導(dǎo)致啟動(dòng)失敗。
三、解決方案
上述情況雖然會(huì)導(dǎo)致 UserBeanConfig 中所有標(biāo)記 @Value 注解屬性都為 null,其他 bean 的配置就不要依賴(lài) UserBeanConfig 中標(biāo)記 @Value 注解的屬性即可(不依賴(lài)干擾 bean 生命周期):
@Bean public HSFSpringConsumerBean userReadService(@Value("${spring.hsf.version}") String version, @Value("${spring.hsf.group}") String group) { HSFSpringConsumerBean consumer = new HSFSpringConsumerBean(); consumer.setInterfaceClass(UserReadService.class); consumer.setVersion(version); consumer.setGroup(group); return consumer; }
以上就是解決Spring中@Value注解取值為null問(wèn)題的詳細(xì)內(nèi)容,更多關(guān)于Spring @Value注解取值為null的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Spring boot Rabbitmq消息防丟失實(shí)踐
這篇文章主要介紹了Spring boot Rabbitmq消息防丟失實(shí)踐,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-09-09java 反射getClass .class 的使用方法示例
這篇文章主要介紹了java 反射getClass .class 的使用方法,結(jié)合實(shí)例形式分析了java類(lèi)反射機(jī)制的相關(guān)操作技巧,需要的朋友可以參考下2019-11-11Java實(shí)現(xiàn)把文件壓縮成zip文件的示例代碼
這篇文章主要為大家介紹了如何通過(guò)Java語(yǔ)言實(shí)現(xiàn)將文件壓縮成zip文件,本文中示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-02-02一篇文章帶你了解jdk1.8新特性--為什么使用lambda表達(dá)式
Lambda是一個(gè)匿名函數(shù),我們可以把Lambda表達(dá)式理解為是一段可以傳遞的代碼,本篇文章就帶你了解,希望能給你帶來(lái)幫助2021-08-08詳談Java幾種線(xiàn)程池類(lèi)型介紹及使用方法
下面小編就為大家?guī)?lái)一篇詳談Java幾種線(xiàn)程池類(lèi)型介紹及使用方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-03-03java 動(dòng)態(tài)代理的方法總結(jié)
這篇文章主要介紹了java 動(dòng)態(tài)代理的方法總結(jié)的相關(guān)資料,需要的朋友可以參考下2017-04-04