解決Spring中@Value注解取值為null問題
一、背景
近期應用中因業(yè)務迭代需要接入 user 客戶端,接入后總是啟動失敗,報注冊 user bean 依賴的配置屬性為 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 常見原因分析
常見現(xiàn)象一:類沒有交給 Spring 管理,比如類沒有加上 @Component 等注解
- 錯誤案例
// 配置類
public class PeopleConfigValue {
@Value("${people.name}")
private String name;
@Value("${people.age}")
private String age;
public PeopleConfigValue() {
}
// getter and setter...
}
// 測試類(以下取值為空)
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...
}
// 測試類(以下取值正常)
PeopleConfigValue peopleConfigValue = SpringContextUtil.getBean(PeopleConfigValue.class);
System.out.println("get peopleConfigValue-name value " + peopleConfigValue.getName());
System.out.println("get peopleConfigValue-age value " + peopleConfigValue.getAge());常見現(xiàn)象二:手動 new 對象實例,沒有從 Spring 容器中獲取
- 錯誤案例
@Component
public class PeopleConfigValue {
@Value("${people.name}")
private String name;
@Value("${people.age}")
private String age;
public PeopleConfigValue() {
}
// getter and setter...
}
// 測試類(以下取值為空)
PeopleConfigValue peopleConfigValue = new PeopleConfigValue();
System.out.println("get peopleConfigValue-name value " + peopleConfigValue.getName());
System.out.println("get peopleConfigValue-age value " + peopleConfigValue.getAge());- 正確案例,參考第一個現(xiàn)象。
常見現(xiàn)象三:使用 static 或 final 修飾成員變量
使用 static 或 final 修飾成員變量值不可改變,注解無法注入配置值。
- 錯誤案例
@Component
public class PeopleConfigValue {
@Value("${people.name}")
private static String name;
@Value("${people.age}")
private static String age;
public PeopleConfigValue() {
}
// getter
}
// 測試類(以下取值為空)
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 修飾成員變量值應該是不可變的,以下可通過 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;
}
}
// 測試類,取值正常
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ī)使用方式,不應該出現(xiàn)問題,開始懷疑是與 Spring 應用上下文 Bean 的初始化順序有關,排查這個問題還是先摸清一下 Spring Boot 的啟動原理及 @Value 解析機制,直接上圖:

圖片箭頭指向即 SpringApplication 啟動階段,在這個過程中進行 Bean 的實例化,進一步細化 SpringApplication 啟動流程如下:

眾所周知,應用中配置的 bean 在 Spring 啟動時會全部解析為 BeanDefinition(可視為 bean 的元信息,圖中第 2 步),同時 Spring 提供了 BeanFactoryPostProcessor 接口用于用戶擴展(圖中第 5 步,比如在這里可以修改BeanDefinition 的元數(shù)據(jù)) ,最后在實例化 bean 過程時(SpringApplication 啟動流程圖中第 11.3 步)會讀取相應的 BeanDefinition 進行初始化。
回到 @Value 注解占位符的解析機制,@Value 注解占位符靠 PropertyResourceConfigurer 來解析(PropertySourcesPlaceholderConfigurer 會調(diào)用 PropertyResourceConfigurer 解析能力來解析占位符,并存儲到 propertySources 屬性集合中),而 PropertyResourceConfigurer 正是實現(xiàn)了 BeanFactoryPostProcessor 接口,在 BeanFactory 后處理階段進行了占位符替換,且 PropertyResourceConfigurer 的優(yōu)化級最低(這里有個風險點:任何應用依賴的實現(xiàn) BeanFactoryPostProcessor 接口的 bean 都會比 PropertyResourceConfigurer 先執(zhí)行)。

理解了 Spring 的啟動機制和 @Value 注解占位符的解析機制,再排查應用代碼發(fā)現(xiàn) UserCommonContext 也實現(xiàn)了 BeanFactoryPostProcessor 接口,也就是說,出現(xiàn)了下述情況:

由于 UserCommonContext 依賴了 UserBeanConfig,導致 UserBeanConfig 提前初始化,但此時 @Value 中的占位符還未替換,那么 UserBeanConfig 中所有標記 @Value 注解屬性都為 null,導致啟動失敗。
三、解決方案
上述情況雖然會導致 UserBeanConfig 中所有標記 @Value 注解屬性都為 null,其他 bean 的配置就不要依賴 UserBeanConfig 中標記 @Value 注解的屬性即可(不依賴干擾 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問題的詳細內(nèi)容,更多關于Spring @Value注解取值為null的資料請關注腳本之家其它相關文章!
相關文章
java 反射getClass .class 的使用方法示例
這篇文章主要介紹了java 反射getClass .class 的使用方法,結合實例形式分析了java類反射機制的相關操作技巧,需要的朋友可以參考下2019-11-11
一篇文章帶你了解jdk1.8新特性--為什么使用lambda表達式
Lambda是一個匿名函數(shù),我們可以把Lambda表達式理解為是一段可以傳遞的代碼,本篇文章就帶你了解,希望能給你帶來幫助2021-08-08

