Spring Cloud Config动态刷新原理这篇文章讲过Spring Boot项目通过集成Spring Cloud Config可以实现应用配置的动态刷新,初次尝试的朋友也许感觉真香,然而人无完人,框架也没与完美的,无论是Spring Cloud Config原生的动态刷新机制还是我介绍的阉割版的动态刷新都逃不过一个现实,那就是Bean会被重新构建,也就是说Spring Bean会先销毁然后再走一遍构建。复杂的项目会初始化很多bean,bean之间也有错综复杂的依赖,当应用配置动态刷新时,有可能一个bean拿到的还是老的配置值去处理业务,什么情况下出出现这个问题呢,看下下面这个例子
@ComponentpublicclassMyPrinter{@Autowiredpriviate APrinter printer;publicvoiddoPrint(){
printer.print();
}
}@Component("APrinter")@RefreshScopepublicclassAPrinter(){@value("${test1}")privateString testPro;publicvoidprint(){
System.out.print(testPro);
}
}
假如初始配置的test1值为1,然后我动态刷新了test1为2,然后我去通过一个接口调用MyPrinter的doPrint方法你会发现它打印的还是1. 意不意外,惊不惊喜。
尴尬委屈
原来是@RefreshScope注解的类会在context启动时创建一个动态代理类。并缓存到一个map中。代理类每次从容器中获取被代理的bean,获取时首先经过GenericScope.get()获取,当动态刷新配置时会清空map重新创建一个bean放到map中,重新创建的bean变量就是动态获取到的最新值。而重建的bean是动态代理前缀为scopedTarget.的bean,被包装的原始bean不会发生变化。
上面的例子中MyPrinter引用的时bean name为APrinter的bean,那么当动态刷新后,持有新的配置变量的代理类的bean name时scopedTarget.APrinter, MyPrinter何时执行print方法打印的都是初始的配置值1啊。
捂脸
解决方法有好多种,有说是把配置统一通过@ConfigurationProperties的bean来管理,或者在使用配置Bean的类上也加上@RerehfreshScope注解。我觉得都可以,如果是项目初始化阶段的话建议使用前者,如果项目已经成型,现在是二次开发,那么我觉得还是后者实现的工作复杂度小一些。
再来看一种情况,假如我们有个bean是定义了定时任务的,而且是通过@Schedule加在bean的方法上。然后你希望这个定时任务的周期是可以动态变化的,于是你就在bean的类上加了@RefreshScope注解,然后定时任务上cron表达式引用配置来实现可动态更改。 结果你会发现,当动态刷新完配置后,你这个bean的定时任务再也不运行了。是不是也很惊喜,也很意外
@RefreshScope@Component@EnableSchedulepublic class MySchedule {@Schedule(cron="${my.schedule.interval}")
pubic void nBScheduleCanChangeIntervalDynamic() {System.out.print("兴冲冲的定时打印!")
}
}
咱们来分析下,通过添加@RefreshScope的bean在动态刷新的时候都会被重新构建代理类。那么原先的代理类就会先执行destory方法。你去翻翻Spring的源码就会知道Spring实现通过注解的方式启动定制任务都是靠ScheduledAnnotationBeanPostProcessor这个类去扫描添加了@Schedule注解的方法然后注册定时任务
...
tasks.add(this.registrar.scheduleFixedRateTask(newFixedRateTask(runnable, fixedRate, initialDelay)));
...// Finally register the scheduled taskssynchronized (this.scheduledTasks) {
Set regTasks =this.scheduledTasks
.computeIfAbsent(bean, key ->newLinkedHashSet<>(4));
regTasks.addAll(tasks);
}
然而它也会监听bean的destory方法,然后在destory方法里注销所有定时任务
@Overridepublicvoid destroy() {
synchronized (this.scheduledTasks) {
Collection> allTasks =this.scheduledTasks.values();for(Set tasks : allTasks) {for(ScheduledTask task : tasks) {
task.cancel();
}
}this.scheduledTasks.clear();
}this.registrar.destroy();
而动态刷新虽然刷新了上下文,然后并没有像sping应用启动那样重新执行BeanPostProcesser。所以被注销的任务也就没有办法再重新启动了。
怎么解决呢,只有在刷新配置的时候重新获取下定时任务列表然后重启定时任务。
protectedvoidhandleSingleMessage(MessageExt messageExt){
refreshConfig(messageExt);//这里刷新配置//一定要先获取定时任务然后才能执行刷新,不然刷新后就获取不到喽,// 别忘了注入ScheduledAnnotationBeanPostProcessorSet scheduledTasks = scheduledBeanPostProcessor.getScheduledTasks();
refreshScope.refreshAll();//执行刷新后重启定时任务restartScheduleTasks(scheduledTasks);
}
这些坑都是严重影响应用功能的,框架有多好用,它就有多坑人,作为使用者咱们还得多留意下前人踩过的坑,莫要走别人脚印啊。
声明:本文部分素材转载自互联网,如有侵权立即删除 。
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!
7. 如遇到加密压缩包,请使用WINRAR解压,如遇到无法解压的请联系管理员!
8. 精力有限,不少源码未能详细测试(解密),不能分辨部分源码是病毒还是误报,所以没有进行任何修改,大家使用前请进行甄别
丞旭猿论坛
暂无评论内容