SpringBoot配置加载原理

前言

在日常开发中,项目的配置数据是再平常不过的操作。通常使用的配置方式有启动参数配置、配置文件配置、配置中心(Apollo、Nacos等)配置。然而,如果配置使用不合理就会有惨痛的教训(不要问我为什么知道的)。

场景描述

在SpringBoot项目中配置数据通常使用@Value注解进行数据的注入。大致场景是这样的:使用@Value注解为一个静态变量配置数据后,项目启动时该值为空,通过Apollo配置中心变更该配置数据后,该变量实际数据符合预期为变更值。 代码大致如下:

java 复制代码
public class ApolloValueService {
    @Value("${apollo.value:10}")
    public static Long staticApolloValue;
    @Value("${apollo.value:10}")
    private Long apolloValue;
    
    public String getApolloValue() {
        return "static value:" + staticApolloValue + " private value:" + apolloValue;
    }
}

通过GET请求调用该方法后,运行结果如下:

  • 项目启动时:

调用结果:"static value:null private value:10"

  • 通过Apollo变更apollo.value的值为11

调用结果:"static value:11 private value:11"

问题解决

上述现象的问题是对静态变量的数据注入不正确(虽然是在测试阶段发现并没有产生生产影响)这一问题解决起来并不麻烦,解决的方式有3种。

  • set方法
java 复制代码
@Value("${apollo.value:10}")
public void setStaticApolloValue(Long val) {
    staticApolloValue = val;
}
  • set变种
java 复制代码
@Autowired
public void setStaticApolloValue(@Value("${apollo.value:10}") Long val){
    staticApolloValue = val;
}
  • @PostConstruct注解赋值
java 复制代码
@Value("${apollo.value:10}")
private Long apolloValue;

@PostConstruct
public void init() {
    staticApolloValue = apolloValue;
}

原理分析

上述问题解决起来还是比较简单的,但是产生这一现象的原因是需要分析的。上述现象可以拆解为2部分分别为:项目启动时静态变量为什么为空、通过Apollo变更后静态变量的值为什么为变更值。

  • 项目启动时为空

为验证该现象,在ApolloValueService类中添加如下代码,观察静态变量数值情况,变更代码如下:

java 复制代码
public class ApolloValueService { 
    @Value("${apollo.value:10}") 
    public static Long staticApolloValue; 
    @Value("${apollo.value:10}") 
    private Long apolloValue; 
    
    static {
        System.out.println(String.format("--------静态变量加载,staticApolloValue:%s--------", staticApolloValue));
    }
    
    public String getApolloValue() { 
        return "static value:" + staticApolloValue + " private value:" + apolloValue; 
    } 
}

再次启动项目后,控制台输出结果:

--------静态变量加载,staticApolloValue:null--------

根据结果看项目在加载类过程中静态变量没有被成功赋值,在启动完成后该静态变量也没有被赋值。对代码进行调试后发现如下结论:

  • SpringBoot是通过反射进行变量注入
  • SpringBoot启动时会过滤静态变量的注入行为

如此,可以大致解释在项目启动后,静态变量为什么为null的现象了。staticApolloValue变量属于类的属性;apolloValue变量属于对象属性。在项目启动过程中,本次涉及的阶段顺序如下:

  1. 加载类信息(即是staticApolloValue静态变量)
  2. Servlet容器初始化
  3. Spring容器初始化
  4. 启动Servlet容器
  5. 启动完成

当类加载完成后,staticApollo静态变量并没有被赋值。@Value注解开始运行的时机在Spring容器初始化过程中(该过程会进行bean的创建)。@Value实现属性注入的原理是通过反射将值注入的。由于在Spring启动过程中会将在静态变量和静态方法上的@Value注解过滤,所以在Spring启动过程中并不会对该属性进行注入值。所以静态变量始终没有机会通过反射注入值。

  • Apollo变更后为预期值

在翻阅Apollo源码发现如下关键点:

  • Apollo客户端与服务端建立了长连接实时感知服务端的变化
  • 当服务单发生变更后,客户端会通过反射将变更量同步到项目中
  • Apollo通过反射进行变更时,并没有过来静态变量的注入

以上内容可以解释为什么通过Apollo变更变量值后静态变量的值符合预期

收获

在排查上述问题的过程中,对Spring源码和Apollo源码有了如下收获:

  1. 在使用apollo-start时,项目中存在3个bean(AutoUpdateConfigChangeListener、SpringValueProcessor、ApolloProcessor)其中SpringValueProcessor、ApolloProcessor2个类属于继承关系,所以@Value注解能够在项目启动过程中读取到Apollo对配置数据。
  2. 在bean初始化前后分别调用了postProcessBeforeInitialization()、postProcessAfterInitialization()方法,通过反射进行逻辑处理。
  3. 上述解决静态变量注入的3种方案,思路是相同的。都是在Spring启动过程创建bean时对静态变量赋值。
  4. 尽量不要对静态变量进行属性注入行为。
  5. 若无法避免使用静态变量属性注入,需使用正确的注入方式。
相关推荐
兰亭序咖啡1 分钟前
学透Spring Boot — 018. 优雅支持多种响应格式
java·spring boot·后端
小杨4042 小时前
springboot框架项目实践应用十五(扩展sentinel区分来源)
spring boot·后端·spring cloud
cg50174 小时前
Spring Boot 中的 Bean
java·前端·spring boot
橘猫云计算机设计5 小时前
基于springboot科研论文检索系统的设计(源码+lw+部署文档+讲解),源码可白嫖!
java·spring boot·后端·毕业设计
西岭千秋雪_5 小时前
Sentinel核心源码分析(上)
spring boot·分布式·后端·spring cloud·微服务·sentinel
肖恩想要年薪百万6 小时前
如何在idea中快速搭建一个Spring Boot项目?
java·数据库·spring boot·后端·学习·mysql·intellij-idea
在努力的韩小豪6 小时前
SpringMVC和SpringBoot是否线程安全?
spring boot·后端·springmvc·线程安全·bean的作用域
来自星星的坤7 小时前
使用 MyBatis-Plus 实现高效的 Spring Boot 数据访问层
spring boot·后端·mybatis
LUCIAZZZ7 小时前
Redisson中BitMap位图的基本操作
java·spring boot·redis·spring