解决Spring中@Value注解取值为null问题

 更新时间:2023年08月29日 09:21:45   作者:bboyzqh  
近期应用中因业务迭代需要接入 user 客户端,接入后总是启动失败,报注册 user bean 依赖的配置属性为 null,所以接下来小编就和大家一起排查分析这个问题,感兴趣的小伙伴跟着小编一起来看看吧

一、背景

近期应用中因业务迭代需要接入 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 常见原因分析

常见现象一:类没有交给 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());

常见现象二:手动 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());
  • 正确案例,参考第一个现象。

常见现象三:使用 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 的使用方式是常规使用方式,不应该出现问题,开始怀疑是与 Spring 应用上下文 Bean 的初始化顺序有关,排查这个问题还是先摸清一下 Spring Boot 的启动原理及 @Value 解析机制,直接上图:

图片箭头指向即 SpringApplication 启动阶段,在这个过程中进行 Bean 的实例化,进一步细化 SpringApplication 启动流程如下:

众所周知,应用中配置的 bean 在 Spring 启动时会全部解析为 BeanDefinition(可视为 bean 的元信息,图中第 2 步),同时 Spring 提供了 BeanFactoryPostProcessor 接口用于用户扩展(图中第 5 步,比如在这里可以修改BeanDefinition 的元数据) ,最后在实例化 bean 过程时(SpringApplication 启动流程图中第 11.3 步)会读取相应的 BeanDefinition 进行初始化。

回到 @Value 注解占位符的解析机制,@Value 注解占位符靠 PropertyResourceConfigurer 来解析(PropertySourcesPlaceholderConfigurer 会调用 PropertyResourceConfigurer 解析能力来解析占位符,并存储到 propertySources 属性集合中),而 PropertyResourceConfigurer 正是实现了 BeanFactoryPostProcessor 接口,在 BeanFactory 后处理阶段进行了占位符替换,且 PropertyResourceConfigurer 的优化级最低(这里有个风险点:任何应用依赖的实现 BeanFactoryPostProcessor 接口的 bean 都会比 PropertyResourceConfigurer 先执行)。

理解了 Spring 的启动机制和 @Value 注解占位符的解析机制,再排查应用代码发现 UserCommonContext 也实现了 BeanFactoryPostProcessor 接口,也就是说,出现了下述情况:

由于 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问题的详细内容,更多关于Spring @Value注解取值为null的资料请关注脚本之家其它相关文章!

相关文章

  • Spring boot Rabbitmq消息防丢失实践

    Spring boot Rabbitmq消息防丢失实践

    这篇文章主要介绍了Spring boot Rabbitmq消息防丢失实践,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-09-09
  • 浅析Java中的 new 关键字

    浅析Java中的 new 关键字

    java中的new关键字是实例化对象,接下来本文通过一个案例给大家讲解Java中的 new 关键字,感兴趣的朋友可以参考下
    2016-08-08
  • java 反射getClass .class 的使用方法示例

    java 反射getClass .class 的使用方法示例

    这篇文章主要介绍了java 反射getClass .class 的使用方法,结合实例形式分析了java类反射机制的相关操作技巧,需要的朋友可以参考下
    2019-11-11
  • Java实现把文件压缩成zip文件的示例代码

    Java实现把文件压缩成zip文件的示例代码

    这篇文章主要为大家介绍了如何通过Java语言实现将文件压缩成zip文件,本文中示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-02-02
  • 一篇文章带你了解jdk1.8新特性--为什么使用lambda表达式

    一篇文章带你了解jdk1.8新特性--为什么使用lambda表达式

    Lambda是一个匿名函数,我们可以把Lambda表达式理解为是一段可以传递的代码,本篇文章就带你了解,希望能给你带来帮助
    2021-08-08
  • 详谈Java几种线程池类型介绍及使用方法

    详谈Java几种线程池类型介绍及使用方法

    下面小编就为大家带来一篇详谈Java几种线程池类型介绍及使用方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-03-03
  • seata springcloud整合教程与遇到的坑

    seata springcloud整合教程与遇到的坑

    seata 是alibaba 出的一款分布式事务管理器,他有侵入性小,实现简单等特点。这篇文章主要介绍了seata springcloud整合教程与遇到的坑,需要的朋友可以参考下
    2021-07-07
  • 详解java中spring里的三大拦截器

    详解java中spring里的三大拦截器

    在本篇文章里我们给大家详细讲述了java中spring里的三大拦截器相关知识点以及用法代码,需要的朋友们学习下。
    2018-10-10
  • Java数据脱敏的常用方式总结

    Java数据脱敏的常用方式总结

    大家好!今天我们要聊一聊数据脱敏,这个词听起来像特工电影里的高科技武器,其实它就是给敏感数据穿上“伪装衣”,防止“坏人”偷 窥,Java 提供了多种数据脱敏方式,今天咱们来聊几种经典实用的“伪装术”,感兴趣的小伙伴跟着小编一起来看看吧
    2024-11-11
  • java 动态代理的方法总结

    java 动态代理的方法总结

    这篇文章主要介绍了java 动态代理的方法总结的相关资料,需要的朋友可以参考下
    2017-04-04

最新评论