zoukankan      html  css  js  c++  java
  • SpringCloud 详解配置刷新的原理 使用jasypt自动加解密后 无法使用 springcloud 中的自动刷新/refresh功能

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

    当我使用了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

  • 相关阅读:
    斜率dp+cdq分治
    踢罐子 [几何+乱搞]
    [HDU3710] Battle Over Cities [树链剖分+线段树+并查集+kruskal+思维]
    [xsy1129] flow [树链剖分和线段树一起优化网络流][我也不知道这是什么鬼标签]
    [CF666E] Forensic Examination [广义后缀自动机+线段树合并]
    省选算法学习-BSGS与exBSGS与二次剩余
    省选算法学习-回文自动机 && 回文树
    省选算法学习-后缀数组+后缀自动机+后缀树
    Password [分块]
    随机过程——泊松过程
  • 原文地址:https://www.cnblogs.com/hfultrastrong/p/8556606.html
Copyright © 2011-2022 走看看