简介
本文介绍Spring Cloud的@RefreshScope动态刷新的注意事项。
不用@RefreshScope也能动态刷新
Spring Cloud的默认实现了动态刷新,不加@RefreshScope就能实现动态更新。方法如下:
法1:ApplicationContextHolder.getContext().getEnvironment().getRequiredProperty(key);
ApplicationContextHolder见:SpringBoot-静态获得Bean的工具类 – 自学精灵
法2:使用@ConfigurationProperties将配置放到类里边去
@ConfigurationProperties的用法见:SpringBoot-用类表示yml配置文件的值 – 自学精灵
原理
配置属性有ConfigurationPropertiesRebinder这个监听器,监听EnvironmentChangeEvent事件。当发生EnvironmentChange事件后,会刷新Environment,然后重新构造配置类对象。
@RefreshScope会失效
我们经常用@Value来使用配置,此时类上要加@RefreshScope。但是,@RefreshScope结合@Value这种用法,在一些场景下,自动刷新会失效!
会失效的场景
场景1:使用@RefreshScope的类必须是注入到容器中的bean,否则无效。
场景2:假如A组件注入B组件,B组件上使用了@RefreshScope并使用@Value获取配置,那么A组件上必须也加上@RefreshScope,否则无法实现动态刷新。
场景3:@RefreshScope 不能用在 @Scheduled、Listener、Timmer等类上,会有问题。以Scheduled为例:如果使用了@Scheduled的类里用@Value引入了配置,当配置中心修改了这个配置时,这个定时调度会失效(不再执行)。
解决方案
上边场景1是无法解决的,但场景2,3是可以解决的。本处只讨论场景2,3的解决方案。
法1:使用Apollo(阿波罗)
Nacos有失效问题,但Apollo没问题!Apollo内部实现了高级的更新逻辑:com.ctrip.framework.apollo.spring.property.AutoUpdateConfigChangeListener#onChange。
法2:使用动态更新组件
如果是Nacos,可以用此组件来实现动态更新
上边的2,3都可以解决,见:手写组件动态更新@Value的值 – 自学精灵
拓展:经对比,上边两种方法的原理几乎是一样的!
注意事项
- @RefreshScope作用的类,不能是final类,否则启动时会报错。
- 如果类里的方法有@XxlJob,不要使用@RefreshScope,否则定时任务报错:job handler [xxx] not found.(这个是XXL-JOB的问题,一直没解决)
静态变量用@RefreshScope的坑
代码
Controller
package com.knife.example.business.order.core.test;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
public class TestController {
@Autowired
private TestUtil testUtil;
@GetMapping("test")
public String test() {
System.out.println(TestUtil.findConfig());
testUtil.nothing();
System.out.println(TestUtil.findConfig());
return "success";
}
}
工具类
package com.knife.example.business.order.core.test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
@Component
@RefreshScope
public class TestUtil {
private static String name;
@Value("${custom.name}")
public void setName(String name) {
TestUtil.name = name;
}
public static String findConfig() {
return name;
}
public void nothing() {
}
}
配置中心

测试
结果:
hello hello
修改配置中心的值,改为:

再次测试,访问:http://localhost:9012/test
结果
hello hi
原因分析
动态刷新成功时,仅仅是再创建类一个代理对象,并清除了实际对象的缓存;再次通过代理对象来使用时,才会触发创建一个新的实例对象,此时才会更新配置的值。

请先 !