前言
在日常开发中,项目的配置数据是再平常不过的操作。通常使用的配置方式有启动参数配置、配置文件配置、配置中心(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变量属于对象属性。在项目启动过程中,本次涉及的阶段顺序如下:
- 加载类信息(即是staticApolloValue静态变量)
- Servlet容器初始化
- Spring容器初始化
- 启动Servlet容器
- 启动完成
当类加载完成后,staticApollo静态变量并没有被赋值。@Value注解开始运行的时机在Spring容器初始化过程中(该过程会进行bean的创建)。@Value实现属性注入的原理是通过反射将值注入的。由于在Spring启动过程中会将在静态变量和静态方法上的@Value注解过滤,所以在Spring启动过程中并不会对该属性进行注入值。所以静态变量始终没有机会通过反射注入值。
- Apollo变更后为预期值
在翻阅Apollo源码发现如下关键点:
- Apollo客户端与服务端建立了长连接实时感知服务端的变化
- 当服务单发生变更后,客户端会通过反射将变更量同步到项目中
- Apollo通过反射进行变更时,并没有过来静态变量的注入

以上内容可以解释为什么通过Apollo变更变量值后静态变量的值符合预期
收获
在排查上述问题的过程中,对Spring源码和Apollo源码有了如下收获:
- 在使用apollo-start时,项目中存在3个bean(AutoUpdateConfigChangeListener、SpringValueProcessor、ApolloProcessor)其中SpringValueProcessor、ApolloProcessor2个类属于继承关系,所以@Value注解能够在项目启动过程中读取到Apollo对配置数据。
- 在bean初始化前后分别调用了postProcessBeforeInitialization()、postProcessAfterInitialization()方法,通过反射进行逻辑处理。
- 上述解决静态变量注入的3种方案,思路是相同的。都是在Spring启动过程创建bean时对静态变量赋值。
- 尽量不要对静态变量进行属性注入行为。
- 若无法避免使用静态变量属性注入,需使用正确的注入方式。