SpringCloud 详解配置刷新的原理 使用jasypt自动加解密后 无法使用 springcloud 中的自动刷新/refresh功能
之所以会查找这篇文章,是因为要解决这样一个问题:
当我使用了jasypt进行配置文件加解密后,如果再使用refresh 去刷新配置,则自动加解密会失效。
原因分析:刷新不是我之前想象的直接调用config获取最新配置的,而是通过重新创建一个SpringBoot环境(非WEB),等到SpringBoot环境启动时就相当于重新启动了一个非web版的服务器。此时config会自动加载到最新的配置。这个过程类似于启动服务器。相当于是重新启动了一个springboot环境,而这个环境中没有加解密功能。拿回的配置,都是原始字符串。
首先先介绍下实现后的效果:
1、在需要动态配置属性的类上添加注解@RefreshScope表示此类Scope为refresh类型的
2、启动工程,修改config-server对应的配置文件,这里修改的是system.order.serverName
3、以post的方式调用refresh接口,返回修改后的key值
4、访问infoTest接口,可以看到修改后的值
详细流程:
依次启动config-server,eureka-server后,再启动订单服务order-service,首先访问http://localhost:8100/infoTest 查看serverName的值:
然后修改config-server工程下的order-service-dev.properties中的system.order.serverName为Order Service modified,通过postman使用POST的方式调用refresh接口,可以看到返回了修改的属性的key,继续访问http://localhost:8100/infoTest 显示新的数据如下:
可以看到对应的属性值已经变化了。
基本使用演示完了,下面该对属性刷新原理进行详细探究:
1、先从基本入口refresh接口入手,在项目启动时可以看到log
Mapped "{[/refresh || /refresh.json],methods=[POST]}" onto public java.lang.Object org.springframework.cloud.endpoint.GenericPostableMvcEndpoint.invoke()
可以知道refresh对应的handler是GenericPostableMvcEndpoint的invoke方法
2、继续进入GenericPostableMvcEndpoint看invoke源码:
@RequestMapping(method = RequestMethod.POST)
@ResponseBody
@Override
public Object invoke() {
if (!getDelegate().isEnabled()) {
return new ResponseEntity<>(Collections.singletonMap(
"message", "This endpoint is disabled"), HttpStatus.NOT_FOUND);
}
return super.invoke();
}
非常简单,直接调用父类的invoke方法,继续跟进到AbstractEndpointMvcAdapter类,发现最后调用的是delegate的invoke方法,而且delegate是从构造方法传入的。
protected Object invoke() {
if (!this.delegate.isEnabled()) {
// Shouldn't happen - shouldn't be registered when delegate's disabled
return getDisabledResponse();
}
return this.delegate.invoke();
}
3、步骤2可以看出最终调用的是对应泛型的invoke方法,那么找到注入refresh接口的地方,通过查询哪里使用到此类,查找到LifecycleMvcEndpointAutoConfiguration,通过refreshMvcEndpoint方法注入了refresh接口
@Bean
@ConditionalOnBean(RefreshEndpoint.class)
public MvcEndpoint refreshMvcEndpoint(RefreshEndpoint endpoint) {
return new GenericPostableMvcEndpoint(endpoint);
}
4、可以那么GenericPostableMvcEndpoint中的delegate就是RefreshEndpoint,转至研究RefreshEndpoint
@ManagedOperation
public String[] refresh() {
Set<String> keys = contextRefresher.refresh();
return keys.toArray(new String[keys.size()]);
}
@Override
public Collection<String> invoke() {
return Arrays.asList(refresh());
}
发现invoke方法很简单,只是返回一个修改过的属性key的集合对象。核心方法contextRefresher.refresh()
5、跟进到contextRefresher.refresh()方法,这里就是核心了
public synchronized Set<String> refresh() {
//获取目前系统的配置
Map<String, Object> before = extract(
this.context.getEnvironment().getPropertySources());
//获取最新配置
addConfigFilesToEnvironment();
//对比目前系统配置和最新配置,返回修改后的属性
Set<String> keys = changes(before,
extract(this.context.getEnvironment().getPropertySources())).keySet();
//通知系统配置变更
this.context.publishEvent(new EnvironmentChangeEvent(keys));
//对应的bean刷新
this.scope.refreshAll();
return keys;
}
6、核心就是获取最新的配置,那么是如何获取的呢?之前
还以为是通过直接调用config配置加载呢,那么继续看addConfigFilesToEnvironment源码:
private void addConfigFilesToEnvironment() {
ConfigurableApplicationContext capture = null;
try {
StandardEnvironment environment = copyEnvironment(
this.context.getEnvironment());
//这里就是核心了,启动SpringBoot环境
SpringApplicationBuilder builder = new SpringApplicationBuilder(Empty.class)
.bannerMode(Mode.OFF).web(false).environment(environment);
// Just the listeners that affect the environment (e.g. excluding logging
// listener because it has side effects)
builder.application()
.setListeners(Arrays.asList(new BootstrapApplicationListener(),
new ConfigFileApplicationListener()));
capture = builder.run();
if (environment.getPropertySources().contains(REFRESH_ARGS_PROPERTY_SOURCE)) {
environment.getPropertySources().remove(REFRESH_ARGS_PROPERTY_SOURCE);
}
MutablePropertySources target = this.context.getEnvironment()
.getPropertySources();
String targetName = null;
for (PropertySource<?> source : environment.getPropertySources()) {
String name = source.getName();
if (target.contains(name)) {
targetName = name;
}
if (!this.standardSources.contains(name)) {
if (target.contains(name)) {
target.replace(name, source);
}
else {
if (targetName != null) {
target.addAfter(targetName, source);
}
else {
if (target.contains("defaultProperties")) {
target.addBefore("defaultProperties", source);
}
else {
target.addLast(source);
}
}
}
}
}
}
finally {
ConfigurableApplicationContext closeable = capture;
closeable.close();
}
}
通过以上代码可知,刷新不是我之前想象的直接调用config获取最新配置的,而是通过重新创建一个SpringBoot环境(非WEB),等到SpringBoot环境启动时就相当于重新启动了一个非web版的服务器。此时config会自动加载到最新的配置。这个过程类似于启动服务器。
等到服务器启动成功后,获取到最新的配置,然后跟原来的配置进行对比,返回修改过的key值。
7、获取到修改后的配置后,发出EnvironmentChangeEvent事件,ConfigurationPropertiesRebinder监听了此事件,调用rebind方法进行配置重新加载
8、this.scope.refreshAll();首先销毁scope为refresh的bean。然后发出RefreshScopeRefreshedEvent事件,通知bean生命周期已经变更,已知两个类EurekaDiscoveryClientConfiguration.EurekaClientConfigurationRefresher接收了此事件,EurekaClientConfigurationRefresher接收到此事件后,进行对eureka服务器重连的操作。
总结:通过以上步骤,配置刷新基本流程就是再起一个SpringBoot环境,加载最新配置,与目前环境配置对应,筛选出变化后的属性,将scope类型为refresh的bean销毁。等到下一次获取时bean时重新装配bean,这样最新配置就注入ok了。具体其他细节自己Debug就行了。
本文中的代码已提交至: https://gitee.com/cmlbeliever/springcloud 欢迎Star
SpringCloud 详解配置刷新的原理 使用jasypt自动加解密后 无法使用 springcloud 中的自动刷新/refresh功能的更多相关文章
- Spring Cloud Config 配置中心 自动加解密功能 jasypt方式
使用此种方式会存在一种问题:如果我配置了自动配置刷新,则刷新过后,加密过后的密文无法被解密.具体原因分析,看 SpringCloud 详解配置刷新的原理 使用 jasypt-spring-boot- ...
- 淘宝JAVA中间件Diamond详解(2)-原理介绍
淘宝JAVA中间件Diamond详解(二)---原理介绍 大家好,通过第一篇的快速使用,大家已经对diamond有了一个基本的了解.本次为大家带来的是diamond核心原理的介绍,主要包括server ...
- RocketMQ详解(一)原理概览
专题目录 RocketMQ详解(一)原理概览 RocketMQ详解(二)安装使用详解 RocketMQ详解(三)启动运行原理 RocketMQ详解(四)核心设计原理 RocketMQ详解(五)总结提高 ...
- [转帖]万字详解Oracle架构、原理、进程,学会世间再无复杂架构
万字详解Oracle架构.原理.进程,学会世间再无复杂架构 http://www.itpub.net/2019/04/24/1694/ 里面的图特别好 数据和云 2019-04-24 09:11:59 ...
- mongo 3.4分片集群系列之六:详解配置数据库
这个系列大致想跟大家分享以下篇章: 1.mongo 3.4分片集群系列之一:浅谈分片集群 2.mongo 3.4分片集群系列之二:搭建分片集群--哈希分片 3.mongo 3.4分片集群系列之三:搭建 ...
- [转帖]详解Linux系统inode原理--硬链接、软链接、innodb大小和划分等
详解Linux系统inode原理--硬链接.软链接.innodb大小和划分等 原创 波波说运维 2019-07-17 00:03:00 https://www.toutiao.com/i6713116 ...
- 【详解】Dubbo的原理以及详细原理、配置
Dubbo的背景 随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进. Dubbo的应用 用于大规模 ...
- apt-get 详解&&配置阿里源
配置apt-get的下载源 1.复制原文件备份 sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak 2.编辑源列表文件 sudo vim / ...
- 详解PHP的执行原理和流程
简介 先看看下面这个过程: • 我们从未手动开启过PHP的相关进程,它是随着Apache的启动而运行的: • PHP通过mod_php5.so模块和Apache相连(具体说来是SAPI,即服务器应用程 ...
随机推荐
- java实现windows下amr转换为mp3(可实现微信语音和qq语音转换)
最近做一个项目需要将微信的语音文件放在页面进行播放,查了好多资料发现,web页面直接播放并没有一个好的解决方案,于是就想到了先将amr文件转换成易于在页面播放的mp3文件,然后在进行播放,现在将amr ...
- RabbitMQ 消费端 Client CPU 100%的解决办法
Func<bool> run = () => { try { using (IConnection conn = cf.CreateConnection()) { using (IM ...
- 处理 ASP.NET 中的异常:无法在发送 HTTP 标头之后进行重定向。
因为在 Global.asax 中的 Application_Error 事件中添加了统一的错误处理,其中会有 Redirect 重定向到错误页面. 但是有可能有些情况下已经进行过其它重定向操作,所以 ...
- android开发前奏曲之开发工具ADT
原文:http://android.eoe.cn/topic/android_sdk Android开发工具(ADT)插件为Eclipse提供了一个专业级的开发环境,用于构建Android应用程序.这 ...
- [hihoCoder] 第五十周: 欧拉路·二
题目1 : 欧拉路·二 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 在上一回中小Hi和小Ho控制着主角收集了分散在各个木桥上的道具,这些道具其实是一块一块骨牌. 主角 ...
- 如何在JS数组特定索引处指定位置插入元素?
如何在JS数组特定索引处指定位置插入元素? 需求: 将一个元素插入到现有数组的特定索引处.听起来很容易和常见,但需要一点时间来研究它. // 原来的数组var array = ["one&q ...
- ios中解析json对象基类
这个是对上面一篇写的一个解析json对象的基类 @interface BaseObjectFromJson : NSObject + (id) objectWithDict:(NSDictionary ...
- (原创)c++11改进我们的模式之改进表驱动模式
所谓表驱动法(Table-Driven Approach),简单讲是指用查表的方法获取值.表驱动是将一些通过较为复杂逻辑语句来得到数据信息的方式,通过查询表的方式来实现,将数据信息存放在表里.对于消除 ...
- 【Spring】Spring,我的零散使用杂记
通过Java类设置配置信息,JavaConfig Spring常用的通过XML或者@Controller.@Servoce.@Repository.@Component等注解注册Bean,最近看Spr ...
- LOCAL_EXPORT_C_INCLUDES和LOCALC_INCLUDES 的差别
http://stackoverflow.com/questions/6595208/what-does-this-line-mean-local-export-c-includes LOCAL_EX ...