配置文件的动态更新
通常获取配置文件的方式
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);
}
}