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);
        }
    }

原文地址:https://www.cnblogs.com/hankuikui/p/12084193.html

时间: 2024-10-08 21:12:37

spring boot 配置文件动态更新原理 以Nacos为例的相关文章

史上最全面的Spring Boot配置文件详解

Spring Boot在工作中是用到的越来越广泛了,简单方便,有了它,效率提高不知道多少倍.Spring Boot配置文件对Spring Boot来说就是入门和基础,经常会用到,所以写下做个总结以便日后查看. 1.配置文件 当我们构建完Spring Boot项目后,会在resources目录下给我们一个默认的全局配置文件 application.properties,这是一个空文件,因为Spring Boot在底层已经把配置都给我们自动配置好了,当在配置文件进行配置时,会修改SpringBoot

Spring Boot学习——Spring Boot配置文件application

Spring Boot配置文件有两种格式: application.properties 和 application.yml.两种配置文件只需要使用一个. 这两种配置文件的语法有些区别,如下 1. application.properties server.port = 8080         -- tomcat 端口 server.context-path = /webName    -- URL路径 2. application.yml server: port: 8080        

Springboot 系列(二)Spring Boot 配置文件

注意:本 Spring Boot 系列文章基于 Spring Boot 版本 v2.1.1.RELEASE 进行学习分析,版本不同可能会有细微差别. 前言 不管是通过官方提供的方式获取 Spring Boot 项目,还是通过 IDEA 快速的创建 Spring Boot 项目,我们都会发现在 resource 有一个配置文件 application.properties,也有可能是application.yml.这个文件也就是 Spring Boot 的配置文件. 1. YAML 文件 在 Sp

Spring Boot 配置文件两种配置方式对比

Spring Boot框架解决java开发中繁琐的xml配置 使我们能够快速的搭建一个基础java项目 Spring Boot配置文件支持.yml 也支持.properties yml 配置加载是有序的 .properties 无序application.yml配置结构 spring:application:name: wxxcxmvc:view:prefix: /WEB-INF/jsp/suffix: jspapplication.yml配置结构 spring.application.name

黑马_13 Spring Boot:04.spring boot 配置文件

13 Spring Boot: 01.spring boot 介绍&&02.spring boot 入门 04.spring boot 配置文件 SpringBoot基础 四.SpringBoot的配置文件 SpringBoot配置文件类型和作用 SpringBoot是基于约定的,所以很多配置都有默认值,但如果想使用自己的配置替换默认配置的话,就可以使用application.properties 或者 application.yml (application.yaml)进行配置. app

初识Spring Boot (Spring Boot配置文件)

其实作为一个新人呢,本来应该先学习一下Spring再学习SpringBoot的,但是由于个人不懂,就先学习了一下SpringBoot.所以就先记录一下吧,以后不懂得再补充. 1.了解Spring Boot 所谓的SpringBoot就是Spring,只是Spring的使用需要大量的配置才可以,而SpringBoot则是直接提供了这些大量的默认配置,大大减少了我们java码农的工作量:只用少量的配置就可以搭建一个Spring Boot应用. 我初学用的是Spring Boot2.0.4,该版本采用

二、Spring Boot 配置文件

1.配置文件 Spring Boot使用一个全局的配置文件,配置文件名是固定的 application.properties applicatioin.yml 配置文件的作用:修改Spring Boot自动配置的默认值:Spring Boot在底层都给我们配置好: YAML(YAML Ain't Markup Language) YAML A Markup Language:是一个标记语言 YAML isn't Markup Language:不是一个标记语言: 标记语言: 以前的配置文件:大多

Spring Boot 配置文件中的花样,看这一篇足矣!

在快速入门一节中,我们轻松的实现了一个简单的RESTful API应用,体验了一下Spring Boot给我们带来的诸多优点,我们用非常少的代码量就成功的实现了一个Web应用,这是传统的Spring应用无法办到的,虽然我们在实现Controller时用到的代码是一样的,但是在配置方面,相信大家也注意到了,在上面的例子中,除了Maven的配置之后,就没有引入任何的配置. 这就是之前我们所提到的,Spring Boot针对我们常用的开发场景提供了一系列自动化配置来减少原本复杂而又几乎很少改动的模板化

Spring Boot? 配置文件详解:自定义属性、随机数、多环境配置等

自定义属性与加载 我们在使用Spring Boot的时候,通常也需要定义一些自己使用的属性,我们可以如下方式直接定义: application-dev.yml com.didispace.blog: name: 程序猿DD title: Spring Boot教程 desc: ${com.didispace.blog.name}正在努力写<${com.didispace.blog.title}> # 随机字符串 value: ${random.value} # 随机int number: ${