java如何实现配置文件动态刷新_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, finalString dataId) {

Listener listener= listenerMap.computeIfAbsent(dataId, i -> newListener() {

@Overridepublic voidreceiveConfigInfo(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);

}

}

@OverridepublicExecutor 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 implementsSmartApplicationListener {private static Log log = LogFactory.getLog(RefreshEventListener.class);privateContextRefresher refresh;private AtomicBoolean ready = new AtomicBoolean(false);publicRefreshEventListener(ContextRefresher refresh) {this.refresh =refresh;

}

@Overridepublic boolean supportsEventType(Class extends ApplicationEvent>eventType) {return ApplicationReadyEvent.class.isAssignableFrom(eventType)|| RefreshEvent.class.isAssignableFrom(eventType);

}

@Overridepublic voidonApplicationEvent(ApplicationEvent event) {if (event instanceofApplicationReadyEvent) {

handle((ApplicationReadyEvent) event);

}else if (event instanceofRefreshEvent) {

handle((RefreshEvent) event);

}

}public voidhandle(ApplicationReadyEvent event) {this.ready.compareAndSet(false, true);

}public voidhandle(RefreshEvent event) {if (this.ready.get()) { //don't handle events before app is ready

log.debug("Event received " +event.getEventDesc());

Set keys = this.refresh.refresh();

log.info("Refresh keys changed: " +keys);

}

}

}

org.springframework.cloud.context.refresh.ContextRefresher

public synchronized Setrefresh() {

Set keys =refreshEnvironment();this.scope.refreshAll();returnkeys;

}public synchronized SetrefreshEnvironment() {

Map before =extract(this.context.getEnvironment().getPropertySources());

addConfigFilesToEnvironment();

Set keys =changes(before,

extract(this.context.getEnvironment().getPropertySources())).keySet();this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));returnkeys;

}

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() instanceofConfigurableApplicationContext) {

closeable=(ConfigurableApplicationContext) closeable.getParent();

}else{break;

}

}

}returncapture;

}

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

/*** Run the Spring application,creating and refreshing a new

* {@linkApplicationContext}.

*@paramargs the application arguments (usually passed from a Java main method)

*@returna running {@linkApplicationContext}*/

publicConfigurableApplicationContext run(String... args) {

StopWatch stopWatch= newStopWatch();

stopWatch.start();

ConfigurableApplicationContext context= null;

Collection exceptionReporters = new ArrayList<>();

configureHeadlessProperty();

SpringApplicationRunListeners listeners=getRunListeners(args);

listeners.starting();try{

ApplicationArguments applicationArguments= newDefaultApplicationArguments(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 newIllegalStateException(ex);

}try{

listeners.running(context);

}catch(Throwable ex) {

handleRunFailure(context, ex, exceptionReporters,null);throw newIllegalStateException(ex);

}returncontext;

}

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

public voidonApplicationEvent(EnvironmentChangeEvent event) {if (this.applicationContext.equals(event.getSource())//Backwards compatible

||event.getKeys().equals(event.getSource())) {

rebind();

}

}

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

找到所有的ConfigurationPropertiesBeans, 遍历它们

@ManagedOperationpublic voidrebind() {this.errors.clear();for (String name : this.beans.getBeanNames()) {

rebind(name);

}

}

@ManagedOperationpublic booleanrebind(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);throwe;

}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

@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throwsBeansException {

ConfigurationProperties annotation= getAnnotation(bean, beanName, ConfigurationProperties.class);if (annotation != null) {

bind(bean, beanName, annotation);

}returnbean;

}

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

private voidbind(Object bean, String beanName, ConfigurationProperties annotation) {

ResolvableType type=getBeanType(bean, beanName);

Validated validated= getAnnotation(bean, beanName, Validated.class);

Annotation[] annotations= (validated != null) ? newAnnotation[] { annotation, validated }

:newAnnotation[] { annotation };

Bindable> target =Bindable.of(type).withExistingValue(bean).withAnnotations(annotations);try{this.configurationPropertiesBinder.bind(target);

}catch(Exception ex) {throw newConfigurationPropertiesBindException(beanName, bean, annotation, ex);

}

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot 2.x中整合Nacos配置服务,可以通过Nacos提供的Spring Cloud Config支持类来实现配置获取和刷新。 首先,在pom.xml文件中添加以下依赖: ```xml <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> ``` 然后,在application.properties(或application.yml)文件中配置Nacos的服务器地址、命名空间、应用名、配置信息等: ```properties spring.cloud.nacos.config.server-addr=127.0.0.1:8848 spring.cloud.nacos.config.namespace=xxx spring.cloud.nacos.config.group=xxx spring.cloud.nacos.config.prefix=xxx spring.cloud.nacos.config.file-extension=properties ``` 其中,server-addr是Nacos服务器的地址,namespace是命名空间,group是配置分组,prefix是配置前缀,file-extension是配置文件的扩展名。 然后,可以通过@Value注解来获取配置信息: ```java @Value("${xxx}") private String xxx; ``` 如果需要动态刷新配置,可以添加@RefreshScope注解,并在配置类中添加@NacosConfigListener注解监听配置变化: ```java @Configuration @RefreshScope public class Config { @Value("${xxx}") private String xxx; @NacosConfigListener(dataId = "xxx", groupId = "xxx", timeout = 5000) public void onMessage(String config) { // 重新加载配置 xxx = config; } } ``` 其中,dataId是配置信息的ID,groupId是配置分组的ID,timeout是超时时间。 这样,当Nacos配置信息发生变化时,就会触发onMessage方法重新加载配置信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值