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

  • 相关阅读:
    win7网络共享原来如此简单,WiFi共享精灵开启半天都弱爆了!
    JQUERY UI Datepicker Demo
    Official online document, install svn server in centOS
    JAVE not work in linux
    AMR 转mp3 失败
    XD, XR, DR 股票
    Linux 下MySql 重置密码
    Difinition Of Done
    Apache, Tomcat, JK Configuration Example
    Linux 安装tomcat
  • 原文地址:https://www.cnblogs.com/hfultrastrong/p/8556606.html
Copyright © 2011-2022 走看看