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. 若无法避免使用静态变量属性注入,需使用正确的注入方式。
相关推荐
一 乐29 分钟前
商城推荐系统|基于SprinBoot+vue的商城推荐系统(源码+数据库+文档)
前端·数据库·vue.js·spring boot·后端·商城推荐系统
后端小张1 小时前
【JAVA 进阶】Mybatis-Plus 实战使用与最佳实践
java·spring boot·spring·spring cloud·tomcat·mybatis·mybatis plus
摇滚侠3 小时前
Spring Boot3零基础教程,SpringApplication 自定义 banner,笔记54
java·spring boot·笔记
摇滚侠3 小时前
Spring Boot3零基础教程,Spring Boot 完成了哪些Spring MVC 自动配置,笔记49
spring boot·spring·mvc
摇滚侠6 小时前
Spring Boot3零基础教程,KafkaTemplate 发送消息,笔记77
java·spring boot·笔记·后端·kafka
计算机学长felix9 小时前
基于SpringBoot的“面向校园的助力跑腿系统”的设计与实现(源码+数据库+文档+PPT)
数据库·spring boot·后端
java水泥工10 小时前
课程答疑系统|基于SpringBoot和Vue的课程答疑系统(源码+数据库+文档)
spring boot·vue·计算机毕业设计·java毕业设计·大学生毕业设计·课程答疑系统
Rocket MAN12 小时前
Spring Boot 缓存:工具选型、两级缓存策略、注解实现与进阶优化
spring boot·后端·缓存
程序定小飞15 小时前
基于springboot的民宿在线预定平台开发与设计
java·开发语言·spring boot·后端·spring
FREE技术15 小时前
山区农产品售卖系统
java·spring boot