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. 若无法避免使用静态变量属性注入,需使用正确的注入方式。
相关推荐
小蒜学长3 小时前
基于SpingBoot+Vue的定向越野赛事管理系统的设计与开发(代码+数据库+LW)
java·前端·spring boot·后端·前后端分离·定向越野赛事
RuoyiOffice3 小时前
项目预算和交付日期变了,为什么不能直接改台账?
spring boot·项目管理·vue3·flowable·业务建模·ruoyi office·项目变更
小蒜学长3 小时前
基于过程性评价的ai助学平台(代码+数据库+LW)
java·spring boot·后端·微信小程序·过程性评价
博风4 小时前
使用Spring官网下载Springboot骨架
java·spring boot·spring
木井巳5 小时前
【JavaEE】SpringBoot 统一功能处理: 拦截器、统一返回与全局异常
java·spring boot·后端·spring·java-ee
Thomas.Sir5 小时前
第6课:SpringBoot4.x 核心注解全解:Component、Bean、Scope、Lazy等IOC相关注解
java·spring boot
paopaokaka_luck5 小时前
美食推荐系统(协同过滤算法推荐、AI助手、HanLP中文分词、 ECharts可视化分析、菜谱学习与话题交流)
java·spring boot·mysql·tomcat·echarts
相隔一个图书馆的距离6 小时前
单测(三)Spring Boot 3.5.6 && @MockitoSpyBean / AOP / 循环依赖:静默吞错与运行期 NPE
java·spring boot·后端
凤山老林6 小时前
Spring Boot 集成 Apache CXF 发布与消费 SOAP WebService:WSDL、WS-Security
spring boot·后端·apache·webservice·soap·apache cxf
软件技术NINI6 小时前
基于JavaWeb 汽车租聘
java·vue.js·spring boot·后端·python·毕业设计·毕设