SpringBoot 动态改配置?我有九招,谁用谁管用!
你说现在这开发日子,真是一天比一天难。做功能吧,刚写完,产品一句话:“这个限流数你别写死啊,要能动态改~”,我当时差点直接摔键盘。
你写代码的时候,他跟你说“这个参数可控就行”,上线后就变成了“我们运营那边每天要调几次,要实时生效,要后台改,不准重启~”
???你咋不早说?
我以前也怨,后来想明白了:怨没用,还是得想招儿解决,哥这 20 年开发下来,没吃过猪肉也看过猪跑,配置这种事——要稳,要灵活,还得抗操!
所以咱不废话,直接上货,整了 12 种我亲测能用的SpringBoot 动态修改配置方法,从最轻量的 @Value
到最重型的 Apollo/Nacos,从“土轮询”到“监听器黑科技”,哪个项目能用我都标明白咧!
不是堆概念,不是拷贝文档,是哥在线上服务器前一边抽烟一边改配置总结出来的真招儿。
会了这 12 套,谁敢跟你说“SpringBoot 配置写死了不能改”,你就直接甩给他:来,我改给你看!
1、@Value
不是废物,只是你用得太死
这玩意儿本来吧,用起来忒顺手,写个
@Value("${timeout:30}")
配置一改,值就跟着走……理想是挺丰满,现实呢?你改完配置,值一点不带变的,得重启;你重启吧,线上可不能断人业务,那就等着背锅吧。
我干脆直接写个环境监听器,谁改配置我就现场拿新值硬灌回去。
@Component
public class TimeoutConfigListener implements ApplicationListener<EnvironmentChangeEvent> {
@Value("${timeout:30}")
private String timeout;
@Override
public void onApplicationEvent(EnvironmentChangeEvent event) {
if (event.getKeys().contains("timeout")) {
// 拿最新值灌进来
String newVal = ApplicationContextProvider.getContext()
.getEnvironment()
.getProperty("timeout");
timeout = newVal;
System.out.println("超时时间改咧:" + timeout);
}
}
}
再整一个 ApplicationContextProvider 搭着用,不然你拿不到环境。
@Component
public class ApplicationContextProvider implements ApplicationContextAware {
private static ApplicationContext ctx;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
ctx = applicationContext;
}
public static ApplicationContext getContext() {
return ctx;
}
}
这招儿不优雅不高级,但特他娘的实用,你 yml 配上个 config server,都不如这玩意儿来得直接,线下调试也不费劲,真香!
2、整点 Nacos,热更新你就靠它吃饭
Nacos 这个东西吧,用的人多了,也不稀奇了,但我还是得唠叨一嘴。你要是配得好,配合 Spring Cloud,值一改,Bean 一刷新,整个服务就跟打了鸡血一样,自动变,毫不费劲。
@RefreshScope
@RestController
public class BizController {
@Value("${biz.feature.switch:false}")
private boolean featureSwitch;
@GetMapping("/feature/status")
public String checkFeature() {
return "当前功能开关状态:" + featureSwitch;
}
}
咋样?改配置文件直接生效,但条件是你得让 Spring Cloud 顶着干,bootstrap.yml
要整明白:
spring:
application:
name: config-demo
cloud:
nacos:
config:
server-addr: localhost:8848
file-extension: yaml
项目一跑起来,后台你在 Nacos 改改值,前台直接拿到,跟魔术似的。
唯一麻烦点儿是,依赖一堆,配置得小心点,不然动不动就连不上,急得脑壳冒汗。
3、Apollo,大厂那味儿就是稳
当年我做政务项目的时候,Apollo 是项目组点名要用的。为啥?稳定!可视化!还能灰度配置!你说一个配置能玩出花儿来,也就 Apollo 了。
@RestController
public class ApolloController {
@ApolloConfig
private Config config;
@GetMapping("/current/env")
public String getEnv() {
String env = config.getProperty("app.env", "dev");
return "当前环境是:" + env;
}
}
注意啊,这里面不是啥 @Value
,而是config.getProperty()
去取实时值,Apollo 不走 Spring 原生那一套缓存机制,你改完后台立即生效,干就完了。
而且这玩意儿还能搞发布审核、通知、灰度开关,那感觉就像玩配置的淘宝后台一样,点点点就全改完。
4、动态切换数据源,听我一句劝,别手抖写死了!
这个事儿我是真有体会,当初我们项目得接三个库,配置写三个 yml
是能整,可问题是一个用户查的是A库,另一个要连B库,你咋办?重启切库?你项目等得起,客户可等不起!
于是我给它整了个自定义 DataSource
,再配个ThreadLocal
动态换。
核心是这仨玩意:
- 多数据源注册
AbstractRoutingDataSource
重写- 接口前加拦截,自己决定用哪个
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setDataSource(String key) {
contextHolder.set(key);
}
public static String getDataSource() {
return contextHolder.get();
}
public static void clearDataSource() {
contextHolder.remove();
}
}
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSource();
}
}
用法也简单,你 controller 里加一行:
DynamicDataSourceContextHolder.setDataSource("tenantA");
// 然后你该咋查咋查
查完别忘清:
DynamicDataSourceContextHolder.clearDataSource();
这一套搞下来,你前台想调哪个库,后端就给你切上哪个,调度员附体,稳得一批。
当然你配置那块得提前准备好每个库,别到时候报个死库错,吓尿。
5、运行时动态修改 Bean 属性?别当 Spring 容器摆设了
你是不是也有这种场景,某个 service 里的参数,平时都写死了,结果临上线前项目经理一句话:“给我调成20,不要重启~”
那你还用注入?直接暴力反射或者从上下文里拿对象,改属性,生效秒见效!
@Service
public class BizService {
private int retryCount = 3;
public void doSomething() {
System.out.println("当前重试次数:" + retryCount);
// 执行逻辑
}
public void setRetryCount(int retryCount) {
this.retryCount = retryCount;
}
}
然后你页面调用接口改这个值:
@Autowired
private ApplicationContext ctx;
@PostMapping("/set-retry")
public String setRetry(@RequestParam int count) {
BizService bean = ctx.getBean(BizService.class);
bean.setRetryCount(count);
return "已设置重试次数为:" + count;
}
看着就 low ?low 但好使,谁用谁知道,特别是那种跑批逻辑的 service,要改时间、改间隔,全靠它!
6、用缓存模拟配置?Map 里一写,说改就改!
有时候你项目没搞 Nacos、Apollo,又不想用那么重的监听器咋办?那你整一份缓存 Map,配合数据库或者 Redis,读取配置数据,页面改完后直接刷内存,简直不要太轻便!
@Component
public class InMemoryConfig {
private final Map<String, String> configMap = new ConcurrentHashMap<>();
public String get(String key) {
return configMap.getOrDefault(key, "");
}
public void set(String key, String val) {
configMap.put(key, val);
}
}
然后你 controller 随便整俩接口:
@Autowired
private InMemoryConfig config;
@PostMapping("/update-config")
public String update(@RequestParam String key, @RequestParam String value) {
config.set(key, value);
return "更新成功";
}
@GetMapping("/get-config")
public String get(@RequestParam String key) {
return config.get(key);
}
你随便丢个管理后台出来,搞个表单,一点就改,后端 Map 直接改值,全项目调用的地方同步拿的就是更新后的值。
适合啥项目?适合那种小公司内部项目+启动慢但改动频繁的系统。啥 Nacos、啥 Apollo?用不起或者懒得接,Map 直接干!
7、Spring Cloud Config:远程配置 + 实时刷新,配置一多就靠它
你要是做分布式、微服务那种,配置动不动几十个服务一套,那用本地application.yml
基本等于找死。
Spring Cloud Config 就是干这活儿的!一套 Git 上托管 yml,所有服务拉最新,还支持@RefreshScope
自动刷新,谁用谁真香。
先整服务端:
一个专门跑 Config 的 Spring Boot 服务,配置 Git 地址就行:
server:
port: 8888
spring:
cloud:
config:
server:
git:
uri: https://gitee.com/your-team/config-repo.git
客户端加这两口子:
spring:
application:
name: your-app
cloud:
config:
uri: http://localhost:8888
再加个神技:
@RefreshScope
@RestController
public class ConfigClientController {
@Value("${biz.name:default}")
private String bizName;
@GetMapping("/get-biz")
public String biz() {
return "当前业务名:" + bizName;
}
}
一改 Git 配置,调用/actuator/refresh
就能刷新配置。
你说这香不香?不用重启,不用手改配置文件,不用进服务器找 yml,运维都给你点赞!
8、定时拉配置:不依赖框架,就靠你自己瞎鸡儿轮询
这方法我给它起名叫:“笨法里的王者”。
有时候你不能接中心、不能用动态注解,那你咋办?
写个定时任务,每 5 秒、10 秒去 Redis、数据库查一遍配置,拉下来扔内存 Map 里,效果一样能动态生效。
@Component
public class ConfigPuller {
private final Map<String, String> localCache = new ConcurrentHashMap<>();
@Scheduled(fixedRate = 10000)
public void refreshConfig() {
// 模拟从 Redis 查
String val = redisTemplate.opsForValue().get("sys.timeout");
if (val != null) {
localCache.put("sys.timeout", val);
System.out.println("刷新配置,当前timeout:" + val);
}
}
public String get(String key) {
return localCache.getOrDefault(key, "");
}
}
不用接中心,不用注解,就是纯靠轮询,越土越稳。
特别适合那种传统系统,不用改结构,也能搞动态配置,运维不支持搞 Apollo?自己就地开个 Redis 做中心,照样能跑!
9、基于事件的配置刷新机制,配置改了自动通知各模块
这招是骚气中带着灵气,我项目里配置更新之后要通知多个模块重新初始化,那咋办?
用事件!
public class ConfigUpdateEvent extends ApplicationEvent {
private final String key;
private final String value;
public ConfigUpdateEvent(Object source, String key, String value) {
super(source);
this.key = key;
this.value = value;
}
// get方法略
}
然后谁感兴趣谁就监听:
@Component
public class BizModule implements ApplicationListener<ConfigUpdateEvent> {
@Override
public void onApplicationEvent(ConfigUpdateEvent event) {
if ("biz.switch".equals(event.getKey())) {
System.out.println("收到配置更新事件,biz.switch 改成了:" + event.getValue());
// 你想干啥干啥,比如切换开关
}
}
}
你 controller 一改配置,发布事件:
@Autowired
private ApplicationEventPublisher publisher;
@PostMapping("/update")
public String update(@RequestParam String key, @RequestParam String val) {
// 更新你本地缓存后
publisher.publishEvent(new ConfigUpdateEvent(this, key, val));
return "配置发布成功";
}
这叫啥?这叫“模块级协作”,自己通知自己模块更新,优雅得不行
比起你在代码里 if else 改值,不知道高到哪里去咧!
10、利用 Spring 的 Environment
动态修改配置(Hack版)
这招说实话有点“擦边球”操作,官方没推荐,但实战真用得上。我做活动平台时就干过——一个 Redis 配置临时改掉,结果重启项目又还原,最后我直接怼进 Spring 的环境变量里。
@Autowired
private ConfigurableEnvironment environment;
public void updateProperty(String key, String value) {
MutablePropertySources propSources = environment.getPropertySources();
// 找到系统环境变量那层,把值给 override 了
Map<String, Object> map = new HashMap<>();
map.put(key, value);
propSources.addFirst(new MapPropertySource("customOverride", map));
}
直接强塞配置进去,强插队的思路。
注意:你这层如果没加在前面,可能还被别的源给覆盖了,一定要 .addFirst()
。
这招唯一问题就是重启就没了,适合那种“运行中救火”的临时操作,真香!
11、写个 @ConfigurationProperties
+ 外部改 Bean 值
这事儿很多人都搞不明白:为啥 @Value
热不起来?为啥 @ConfigurationProperties
更好?
我告诉你,你看下面这段配置类,只要你搞了 @RefreshScope
+ 外部动态 set,你就能随便控制值——
@Component
@ConfigurationProperties(prefix = "biz.config")
@RefreshScope
public class BizProperties {
private int threadCount;
private boolean enable;
// getter/setter...
}
然后呢?你 controller 里直接注入这 Bean,动态 set 值:
@Autowired
private BizProperties props;
@PostMapping("/set-thread")
public String update(@RequestParam int count) {
props.setThreadCount(count);
return "线程数改成:" + count;
}
很多人以为@ConfigurationProperties
是只能读 yml,其实只要你能控制 Bean 实例,那它就能动态改。
别被文档骗咧,这玩意儿好用得很!
12、Zookeeper + 本地监听器,改配置还送通知
有段时间我跟一家做金融交易的合作,非要用 ZK 做中心化配置,Apollo 和 Nacos都嫌弃,我们就这么干的:
Zookeeper 存 key-value,节点数据一改,本地监听器自动收通知。
zkClient.subscribeDataChanges("/config/timeout", new IZkDataListener() {
@Override
public void handleDataChange(String dataPath, Object data) {
System.out.println("配置改了:" + dataPath + " = " + data);
// 更新你本地缓存逻辑
}
@Override
public void handleDataDeleted(String dataPath) {
System.out.println("配置被删了:" + dataPath);
}
});
一改 ZK 节点,服务端立马就感知。
你要觉得 ZK 就只能做注册中心,那你是真没用过“它监听数据变化的骚操作”!
这招适合啥?适合那种内网复杂、不能开放外部 API、但配置得共享的场景。
不重启、不重拉、不定时刷,改一次全系统感知,贼刺激!
结尾:别光抄代码,记住这些思路才是关键!
行了哥们,12招全抖完了,咱们收个尾。
你看啊,这12套骚操作,其实也能分门别类整理下思路:
分类 | 技术方案 | 特点 |
---|---|---|
🧱 本地轻量玩法 | @Value +监听器、内存Map缓存、Env注入、配置类手改 |
轻便、适合小项目 |
☁️ 中心化配置 | Nacos、Apollo、Spring Cloud Config、Zookeeper监听 | 适合多服务协同、大厂味十足 |
🔄 极限骚操作 | 动态换数据源、运行时改Bean属性、事件发布更新、定时轮询拉配置 | 没有框架依赖,手动拉扯、灵活暴力 |
说到底——
配置这玩意儿,说简单也简单,说复杂也能复杂到飞起。
你项目小,选本地 Map、监听器自己玩就够了;
你项目大,要扛流量,要统一治理,那你 Apollo、Nacos 不用都对不起自己;
你需求急、客户催、不能上线,那你 runtime 直接干内存那一套,谁拦你谁背锅。
我写这些不是装、也不是教你啥“最佳实践”那套官方鬼话,
我就是想让你真遇到问题的时候,有得用,有得选,不用一脸懵逼盯着yml
改完重启再挨骂。