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. 若无法避免使用静态变量属性注入,需使用正确的注入方式。
相关推荐
用户908324602732 天前
Spring AI 1.1.2 + Neo4j:用知识图谱增强 RAG 检索(上篇:图谱构建)
java·spring boot
用户8307196840823 天前
Spring Boot 集成 RabbitMQ :8 个最佳实践,杜绝消息丢失与队列阻塞
spring boot·后端·rabbitmq
Java水解3 天前
Spring Boot 视图层与模板引擎
spring boot·后端
Java水解3 天前
一文搞懂 Spring Boot 默认数据库连接池 HikariCP
spring boot·后端
洋洋技术笔记3 天前
Spring Boot Web MVC配置详解
spring boot·后端
初次攀爬者4 天前
Kafka 基础介绍
spring boot·kafka·消息队列
用户8307196840824 天前
spring ai alibaba + nacos +mcp 实现mcp服务负载均衡调用实战
spring boot·spring·mcp
Java水解4 天前
SpringBoot3全栈开发实战:从入门到精通的完整指南
spring boot·后端
初次攀爬者5 天前
RocketMQ在Spring Boot上的基础使用
java·spring boot·rocketmq
花花无缺5 天前
搞懂@Autowired 与@Resuorce
java·spring boot·后端