• spring boot 配置文件动态更新原理 以Nacos为例


    配置文件的动态更新 

    通常获取配置文件的方式

    1, @Value

    2. @ConfigurationProperties(Prefix)

    如果是在运行时要动态更新的话,

    第一种方式要在bean上加@RefreshScope 

    第二种方式是自动支持的。

    以Nacos为为例,我们可以看下源码是如何实现的:

    Nacos获取配置中心是通过单独一个线程的长轮询获取的:

    com.alibaba.nacos.client.config.impl.ClientWorker.LongPollingRunnable

    当获取到更新配置后,publishEvent

    org.springframework.cloud.alibaba.nacos.refresh.NacosContextRefresher#registerNacosListener

    private void registerNacosListener(final String group, final String dataId) {
    
            Listener listener = listenerMap.computeIfAbsent(dataId, i -> new Listener() {
                @Override
                public void receiveConfigInfo(String configInfo) {
                    refreshCountIncrement();
                    String md5 = "";
                    if (!StringUtils.isEmpty(configInfo)) {
                        try {
                            MessageDigest md = MessageDigest.getInstance("MD5");
                            md5 = new BigInteger(1, md.digest(configInfo.getBytes("UTF-8")))
                                    .toString(16);
                        }
                        catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
                            log.warn("[Nacos] unable to get md5 for dataId: " + dataId, e);
                        }
                    }
                    refreshHistory.add(dataId, md5);
                    applicationContext.publishEvent(
                            new RefreshEvent(this, null, "Refresh Nacos config"));
                    if (log.isDebugEnabled()) {
                        log.debug("Refresh Nacos config group " + group + ",dataId" + dataId);
                    }
                }
    
                @Override
                public Executor getExecutor() {
                    return null;
                }
            });
    
            try {
                configService.addListener(dataId, group, listener);
            }
            catch (NacosException e) {
                e.printStackTrace();
            }
        }

    当收到{@link RefreshEvent}时调用{@link RefreshEventListener#refresh}。

    只在收到{@link ApplicationReadyEvent}后响应{@link RefreshEvent},

    因为RefreshEvents可能在应用程序生命周期中来得太早。(译文)

    public class RefreshEventListener implements SmartApplicationListener {
    
        private static Log log = LogFactory.getLog(RefreshEventListener.class);
    
        private ContextRefresher refresh;
    
        private AtomicBoolean ready = new AtomicBoolean(false);
    
        public RefreshEventListener(ContextRefresher refresh) {
            this.refresh = refresh;
        }
    
        @Override
        public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
            return ApplicationReadyEvent.class.isAssignableFrom(eventType)
                    || RefreshEvent.class.isAssignableFrom(eventType);
        }
    
        @Override
        public void onApplicationEvent(ApplicationEvent event) {
            if (event instanceof ApplicationReadyEvent) {
                handle((ApplicationReadyEvent) event);
            }
            else if (event instanceof RefreshEvent) {
                handle((RefreshEvent) event);
            }
        }
    
        public void handle(ApplicationReadyEvent event) {
            this.ready.compareAndSet(false, true);
        }
    
        public void handle(RefreshEvent event) {
            if (this.ready.get()) { // don't handle events before app is ready
                log.debug("Event received " + event.getEventDesc());
                Set<String> keys = this.refresh.refresh();
                log.info("Refresh keys changed: " + keys);
            }
        }
    
    }

    org.springframework.cloud.context.refresh.ContextRefresher

    public synchronized Set<String> refresh() {
            Set<String> keys = refreshEnvironment();
            this.scope.refreshAll();
            return keys;
        }
    
        public synchronized Set<String> refreshEnvironment() {
            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(this.context, keys));
            return keys;
        }

    ConfigurableApplicationContext addConfigFilesToEnvironment() {
            ConfigurableApplicationContext capture = null;
            try {
                StandardEnvironment environment = copyEnvironment(
                        this.context.getEnvironment());
                SpringApplicationBuilder builder = new SpringApplicationBuilder(Empty.class)
                        .bannerMode(Mode.OFF).web(WebApplicationType.NONE)
                        .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 {
                                // targetName was null so we are at the start of the list
                                target.addFirst(source);
                                targetName = name;
                            }
                        }
                    }
                }
            }
            finally {
                ConfigurableApplicationContext closeable = capture;
                while (closeable != null) {
                    try {
                        closeable.close();
                    }
                    catch (Exception e) {
                        // Ignore;
                    }
                    if (closeable.getParent() instanceof ConfigurableApplicationContext) {
                        closeable = (ConfigurableApplicationContext) closeable.getParent();
                    }
                    else {
                        break;
                    }
                }
            }
            return capture;
        }

    然后就重新走了一次启动的流程

    /**
         * Run the Spring application, creating and refreshing a new
         * {@link ApplicationContext}.
         * @param args the application arguments (usually passed from a Java main method)
         * @return a running {@link ApplicationContext}
         */
        public ConfigurableApplicationContext run(String... args) {
            StopWatch stopWatch = new StopWatch();
            stopWatch.start();
            ConfigurableApplicationContext context = null;
            Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
            configureHeadlessProperty();
            SpringApplicationRunListeners listeners = getRunListeners(args);
            listeners.starting();
            try {
                ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
                ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
                configureIgnoreBeanInfo(environment);
                Banner printedBanner = printBanner(environment);
                context = createApplicationContext();
                exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                        new Class[] { ConfigurableApplicationContext.class }, context);
                prepareContext(context, environment, listeners, applicationArguments, printedBanner);
                refreshContext(context);
                afterRefresh(context, applicationArguments);
                stopWatch.stop();
                if (this.logStartupInfo) {
                    new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
                }
                listeners.started(context);
                callRunners(context, applicationArguments);
            }
            catch (Throwable ex) {
                handleRunFailure(context, ex, exceptionReporters, listeners);
                throw new IllegalStateException(ex);
            }
    
            try {
                listeners.running(context);
            }
            catch (Throwable ex) {
                handleRunFailure(context, ex, exceptionReporters, null);
                throw new IllegalStateException(ex);
            }
            return context;
        }

    org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder#onApplicationEvent

    public void onApplicationEvent(EnvironmentChangeEvent event) {
            if (this.applicationContext.equals(event.getSource())
                    // Backwards compatible
                    || event.getKeys().equals(event.getSource())) {
                rebind();
            }
        }

    ConfigurationPropertiesRebinder 看见这个名字就知道是怎么回事了。

    找到所有的ConfigurationPropertiesBeans, 遍历它们

    @ManagedOperation
        public void rebind() {
            this.errors.clear();
            for (String name : this.beans.getBeanNames()) {
                rebind(name);
            }
        }
    
        @ManagedOperation
        public boolean rebind(String name) {
            if (!this.beans.getBeanNames().contains(name)) {
                return false;
            }
            if (this.applicationContext != null) {
                try {
                    Object bean = this.applicationContext.getBean(name);
                    if (AopUtils.isAopProxy(bean)) {
                        bean = ProxyUtils.getTargetObject(bean);
                    }
                    if (bean != null) {
                        this.applicationContext.getAutowireCapableBeanFactory()
                                .destroyBean(bean);
                        this.applicationContext.getAutowireCapableBeanFactory()
                                .initializeBean(bean, name);
                        return true;
                    }
                }
                catch (RuntimeException e) {
                    this.errors.put(name, e);
                    throw e;
                }
                catch (Exception e) {
                    this.errors.put(name, e);
                    throw new IllegalStateException("Cannot rebind to " + name, e);
                }
            }
            return false;
        }
    
    

    org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor#postProcessBeforeInitialization

    @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            ConfigurationProperties annotation = getAnnotation(bean, beanName, ConfigurationProperties.class);
            if (annotation != null) {
                bind(bean, beanName, annotation);
            }
            return bean;
        }

    这个BeanPostProcessor里就对我们要更新的Bean进行更新最新的配置值了。如下列如红色部分

    private void bind(Object bean, String beanName, ConfigurationProperties annotation) {
            ResolvableType type = getBeanType(bean, beanName);
            Validated validated = getAnnotation(bean, beanName, Validated.class);
            Annotation[] annotations = (validated != null) ? new Annotation[] { annotation, validated }
                    : new Annotation[] { annotation };
            Bindable<?> target = Bindable.of(type).withExistingValue(bean).withAnnotations(annotations);
            try {
                this.configurationPropertiesBinder.bind(target);
            }
            catch (Exception ex) {
                throw new ConfigurationPropertiesBindException(beanName, bean, annotation, ex);
            }
        }
  • 相关阅读:
    无线安全课堂:手把手教会你搭建伪AP接入点
    转载——开阔自己的视野,勇敢的接触新知识
    关于系统架构的一些总结
    MessageBox.Show()如何换行
    不患寡而患不均
    由CHAR(2)引发的BUG
    DataRow.RowState 属性
    C# 使用TimeSpan计算两个时间差
    利用反射调出其他项目的界面
    DB2 中将date类型的转换成timestamp
  • 原文地址:https://www.cnblogs.com/hankuikui/p/12084193.html
Copyright © 2020-2023  润新知