之所以会查找这篇文章,是因为要解决这样一个问题:

当我使用了jasypt进行配置文件加解密后,如果再使用refresh 去刷新配置,则自动加解密会失效。

原因分析:刷新不是我之前想象的直接调用config获取最新配置的,而是通过重新创建一个SpringBoot环境(非WEB),等到SpringBoot环境启动时就相当于重新启动了一个非web版的服务器。此时config会自动加载到最新的配置。这个过程类似于启动服务器。相当于是重新启动了一个springboot环境,而这个环境中没有加解密功能。拿回的配置,都是原始字符串。

SpringCloud 详解配置刷新的原理

首先先介绍下实现后的效果: 
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功能的更多相关文章

  1. Spring Cloud Config 配置中心 自动加解密功能 jasypt方式

    使用此种方式会存在一种问题:如果我配置了自动配置刷新,则刷新过后,加密过后的密文无法被解密.具体原因分析,看 SpringCloud 详解配置刷新的原理 使用  jasypt-spring-boot- ...

  2. 淘宝JAVA中间件Diamond详解(2)-原理介绍

    淘宝JAVA中间件Diamond详解(二)---原理介绍 大家好,通过第一篇的快速使用,大家已经对diamond有了一个基本的了解.本次为大家带来的是diamond核心原理的介绍,主要包括server ...

  3. RocketMQ详解(一)原理概览

    专题目录 RocketMQ详解(一)原理概览 RocketMQ详解(二)安装使用详解 RocketMQ详解(三)启动运行原理 RocketMQ详解(四)核心设计原理 RocketMQ详解(五)总结提高 ...

  4. [转帖]万字详解Oracle架构、原理、进程,学会世间再无复杂架构

    万字详解Oracle架构.原理.进程,学会世间再无复杂架构 http://www.itpub.net/2019/04/24/1694/ 里面的图特别好 数据和云 2019-04-24 09:11:59 ...

  5. mongo 3.4分片集群系列之六:详解配置数据库

    这个系列大致想跟大家分享以下篇章: 1.mongo 3.4分片集群系列之一:浅谈分片集群 2.mongo 3.4分片集群系列之二:搭建分片集群--哈希分片 3.mongo 3.4分片集群系列之三:搭建 ...

  6. [转帖]详解Linux系统inode原理--硬链接、软链接、innodb大小和划分等

    详解Linux系统inode原理--硬链接.软链接.innodb大小和划分等 原创 波波说运维 2019-07-17 00:03:00 https://www.toutiao.com/i6713116 ...

  7. 【详解】Dubbo的原理以及详细原理、配置

    Dubbo的背景 随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进. Dubbo的应用 用于大规模 ...

  8. apt-get 详解&&配置阿里源

    配置apt-get的下载源 1.复制原文件备份 sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak 2.编辑源列表文件 sudo vim / ...

  9. 详解PHP的执行原理和流程

    简介 先看看下面这个过程: • 我们从未手动开启过PHP的相关进程,它是随着Apache的启动而运行的: • PHP通过mod_php5.so模块和Apache相连(具体说来是SAPI,即服务器应用程 ...

随机推荐

  1. java 泛型中class<T> 和T的区别是什么?

    public <T> boolean edit(T entity) 和public <T> T get(Class<T> c, Serializable id)中这 ...

  2. 使用vs的查找功能,简单大概的统计vs中的代码行数

    VS强大的查找功能,可以使用正则表达式来进行查找,这里统计代码行数的原理就是: 在所有指定文件中进行搜索,统计匹配的文本行数. 但是匹配的行需要满足:非注释.非空等特殊非代码行. 使用Ctrl+Shi ...

  3. Ubuntu java install & config

    im:http://jingyan.baidu.com/article/08b6a591cb06f114a8092209.html http://www.cnblogs.com/zknublx/p/5 ...

  4. Atitti cto的日常流程与职责attilax总结

    Atitti cto的日常流程与职责attilax总结 1. (最重要)技术战略制定 规划,预测趋势1 1.1. 关键词 Throught技术雷达 趋势 没落  已死  辉煌 未来1 1.2. (比较 ...

  5. Atitti dbutil获取多个返回结果集的解决

    Atitti dbutil获取多个返回结果集的解决 1.1. 多个select默认只返回第一个resultset1 1.2. 调用存储过程,也是返回第一个select的1 1.3. 如果insert前 ...

  6. 使用Karma、Mocha实现vue单元测试

    Karma Karma是一个基于Node.js的JavaScript测试执行过程管理工具(Test Runner).该工具在Vue中的主要作用是将项目运行在各种主流Web浏览器进行测试.换句话说,它是 ...

  7. 编译libevent源代码(Windows)

    学习笔记,只是记录本次成功用libevent源代码进行编译.环境为MinGW+VS2008+Msys. 0.下载libevent库 http://libevent.org/ 下载stable稳定版的库 ...

  8. yum 卸载 error: %preun(tengine-2.1.0-1.el6.x86_64) scriptlet failed, exit status 6

    error: %preun(tengine-2.1.0-1.el6.x86_64) scriptlet failed, exit status 6 Error in PREUN scriptlet i ...

  9. [Windows Azure] Getting Started with Windows Azure SQL Data Sync

    Getting Started with Windows Azure SQL Data Sync In this tutorial, you learn the fundamentals of Win ...

  10. 每日英语:Three Shows That Changed The Way Networks Think About Viewership

    As we continue examining this season’s DVR success stories in The Blacklist and Sleepy Hollow it mak ...