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

这篇具有很好参考价值的文章主要介绍了Spring中@Value注解取值为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 解析机制,直接上图:

Spring中@Value注解取值为null问题排查,# Spring,spring boot,Value注解,null,bean生命周期

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

Spring中@Value注解取值为null问题排查,# Spring,spring boot,Value注解,null,bean生命周期

众所周知,应用中配置的 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注解取值为null问题排查,# Spring,spring boot,Value注解,null,bean生命周期

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

Spring中@Value注解取值为null问题排查,# Spring,spring boot,Value注解,null,bean生命周期

由于 UserCommonContext 依赖了 UserBeanConfig,导致 UserBeanConfig 提前初始化,但此时 @Value 中的占位符还未替换,那么 UserBeanConfig 中所有标记 @Value 注解属性都为 null,导致启动失败。

三、解决方案

上述情况虽然会导致 UserBeanConfig 中所有标记 @Value 注解属性都为 null,其他 bean 的配置就不要依赖 UserBeanConfig 中标记 @Value 注解的属性即可(不依赖干扰 bean 生命周期):文章来源地址https://www.toymoban.com/news/detail-683670.html

@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问题排查的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • spring-mvc系列:详解@RequestMapping注解(value、method、params、header等)

    目录 一、@RequestMapping注解的功能 二、@RequestMapping注解的位置 三、@RequestMapping注解的value属性 四、@RequestMapping注解的method属性 五、@RequestMapping注解的params属性 六、@RequestMapping注解的header属性 七、SpringMVC支持ant分格的路径 八、SpringMVC支持路径中的占位符 从注解名称上我们可

    2024年02月14日
    浏览(49)
  • Spring Boot常用注解

    在 Spring Boot 中,有许多注解用于简化和标识应用程序的不同方面。以下是一些常用的 Spring Boot 注解: @SpringBootApplication : 用于标识主应用程序类。通常与 @EnableAutoConfiguration 、 @ComponentScan 和 @Configuration 一起使用,它是一个复合注解,用于简化配置。 @Controller : 用于标识控

    2024年01月19日
    浏览(44)
  • Spring boot注解讲解

    人不走空                                                                            目录         🌈个人主页:人不走空       💖系列专栏:算法专题 ⏰诗词歌赋:斯是陋室,惟吾德馨   注解 注解列表如下 JPA注解 作者其他作品:   @SpringBootApplication :申

    2024年02月19日
    浏览(51)
  • Spring boot 常见注解

    Spring Boot是一个基于Spring框架的快速开发框架,它通过自动化配置和约定优于配置的原则,简化了Spring应用程序的开发过程。Spring Boot可以帮助开发者快速构建独立的、生产级别的应用程序,并且可以与其他Spring框架和第三方库无缝集成。 Spring Boot提供了很多便利的特性,比如

    2024年01月18日
    浏览(42)
  • Spring Boot 注解解读详解

    Spring Boot提供了大量的注解来简化Spring应用的开发。下面我们将详细介绍一些最常用的Spring Boot注解。 1. @SpringBootApplication 这是一个复合注解,用于标记应用的主类。它包含了以下三个注解: @SpringBootConfiguration :等同于Spring的@Configuration,标明该类是配置类,并会把该类作为

    2024年02月06日
    浏览(47)
  • Spring Boot 启动注解分析

    虽然我们在日常开发中,Spring Boot 使用非常多,算是目前 Java 开发领域一个标配了,但是小伙伴们仔细想想自己的面试经历,和 Spring Boot 相关的面试题都有哪些?个人感觉应该是比较少的,Spring Boot 本质上还是曾经 SSM 那一套,只是通过各种 starter 简化了配置而已,其他都是

    2024年02月13日
    浏览(42)
  • Spring boot自定义注解

    定义一个注解类 使用AOP对注解进行解析,需要定义一个切面类,包括自定义的切点方法normalPointCut(),以及连接点的处理方法normalPointAround()。连接点中的ProceedingJoinPoint可以获取被代理类的方法属性等。 2.1 定义注解 2.2 实现参数解释器 记得实现WebMvcConfigurer 接口配置LimitReque

    2023年04月27日
    浏览(41)
  • Spring Boot 集成 WebSocket(原生注解与Spring封装)

    本章节将介绍 Spring Boot 集成 WebSocket 的两种主要方式:原生注解与Spring封装。 在线WebSocket测试工具 🤖 Spring Boot 2.x 实践案例(代码仓库) 引入依赖 配置文件 处理消息 @ServerEndpoint:将目前的类定义成一个websocket服务器端,注解的值将被用于监听用户连接的终端访问URL地址,

    2024年02月02日
    浏览(44)
  • Spring Boot 学习之——@SpringBootApplication注解(自动注解原理)

    springboot是基于spring的新型的轻量级框架,最厉害的地方当属**自动配置。**那我们就可以根据启动流程和相关原理来看看,如何实现传奇的自动配置 从上面代码可以看出,Annotation定义(@SpringBootApplication)和类定义(SpringApplication.run)最为耀眼,所以要揭开SpringBoot的神秘面纱

    2024年01月25日
    浏览(49)
  • Spring Boot 中的 @EnableDiscoveryClient 注解

    Spring Boot 是一个快速开发 Spring 应用程序的框架,它提供了一些基础设施,使得我们可以快速地开发出高效、可靠的应用程序。其中,@EnableDiscoveryClient 注解是 Spring Boot 中一个非常重要的注解,它提供了一种便捷的方式来将 Spring Boot 应用程序注册到服务注册中心中。本文将介

    2024年02月12日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包