Support refresh single ConfigurationPropertiesBean.

pull/2383/head
Freeman Lau 3 years ago
parent 2cda113014
commit 67daf1f27e

@ -19,17 +19,21 @@ package com.alibaba.cloud.nacos;
import com.alibaba.cloud.nacos.refresh.NacosContextRefresher;
import com.alibaba.cloud.nacos.refresh.NacosRefreshHistory;
import com.alibaba.cloud.nacos.refresh.NacosRefreshProperties;
import com.alibaba.cloud.nacos.refresh.SmartConfigurationPropertiesRebinder;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.cloud.context.properties.ConfigurationPropertiesBeans;
import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author juven.xuxb
* @author freeman
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.nacos.config.enabled", matchIfMissing = true)
@ -73,4 +77,11 @@ public class NacosConfigAutoConfiguration {
return new NacosContextRefresher(nacosConfigManager, nacosRefreshHistory);
}
@Bean
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
public ConfigurationPropertiesRebinder smartConfigurationPropertiesRebinder(
ConfigurationPropertiesBeans beans) {
return new SmartConfigurationPropertiesRebinder(beans);
}
}

@ -17,14 +17,19 @@
package com.alibaba.cloud.nacos;
import com.alibaba.cloud.nacos.client.NacosPropertySourceLocator;
import com.alibaba.cloud.nacos.refresh.SmartConfigurationPropertiesRebinder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.cloud.context.properties.ConfigurationPropertiesBeans;
import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author xiaojing
* @author freeman
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.nacos.config.enabled", matchIfMissing = true)
@ -49,4 +54,15 @@ public class NacosConfigBootstrapConfiguration {
return new NacosPropertySourceLocator(nacosConfigManager);
}
/**
* Compatible with spring boot < 2.4.0.
*/
@Bean
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
public ConfigurationPropertiesRebinder smartConfigurationPropertiesRebinder(
ConfigurationPropertiesBeans beans) {
return new SmartConfigurationPropertiesRebinder(beans);
}
}

@ -129,7 +129,6 @@ public class NacosContextRefresher
String configInfo) {
refreshCountIncrement();
nacosRefreshHistory.addRefreshRecord(dataId, group, configInfo);
// todo feature: support single refresh for listening
applicationContext.publishEvent(
new RefreshEvent(this, null, "Refresh Nacos config"));
if (log.isDebugEnabled()) {

@ -0,0 +1,33 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.nacos.refresh;
/**
* Refresh behavior.
*
* @author freeman
*/
public enum RefreshBehavior {
/**
* Refresh all ConfigurationPropertiesBean.
*/
ALL,
/**
* Refresh specific ConfigurationPropertiesBean base on change key.
*/
SPECIFIC
}

@ -0,0 +1,112 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.nacos.refresh;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.springframework.beans.BeansException;
import org.springframework.boot.context.properties.ConfigurationPropertiesBean;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.cloud.context.properties.ConfigurationPropertiesBeans;
import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ReflectionUtils;
/**
* Extend {@link ConfigurationPropertiesRebinder}.
* <p>
* Spring team doesn't seem to support single {@link ConfigurationPropertiesBean} refresh.
* <p>
* SmartConfigurationPropertiesRebinder can refresh specific
* {@link ConfigurationPropertiesBean} base on change keys.
* <p>
* <strong> NOTE: We still use Spring's default behavior (full refresh) as default
* behavior, This feature can be considered an advanced feature, it may not be as stable
* as the default behavior. </strong>
*
* @author freeman
*/
public class SmartConfigurationPropertiesRebinder
extends ConfigurationPropertiesRebinder {
private Map<String, ConfigurationPropertiesBean> beanMap;
private ApplicationContext applicationContext;
private RefreshBehavior refreshBehavior;
public SmartConfigurationPropertiesRebinder(ConfigurationPropertiesBeans beans) {
super(beans);
fillBeanMap(beans);
}
@SuppressWarnings("unchecked")
private void fillBeanMap(ConfigurationPropertiesBeans beans) {
this.beanMap = new HashMap<>();
Field field = ReflectionUtils.findField(beans.getClass(), "beans");
if (field != null) {
field.setAccessible(true);
this.beanMap.putAll((Map<String, ConfigurationPropertiesBean>) Optional
.ofNullable(ReflectionUtils.getField(field, beans))
.orElse(Collections.emptyMap()));
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
super.setApplicationContext(applicationContext);
this.applicationContext = applicationContext;
this.refreshBehavior = this.applicationContext.getEnvironment().getProperty(
"spring.cloud.nacos.config.refresh-behavior", RefreshBehavior.class,
RefreshBehavior.ALL);
}
@Override
public void onApplicationEvent(EnvironmentChangeEvent event) {
if (this.applicationContext.equals(event.getSource())
// Backwards compatible
|| event.getKeys().equals(event.getSource())) {
switch (refreshBehavior) {
case SPECIFIC:
rebindSpecific(event);
break;
default:
rebind();
}
}
}
private void rebindSpecific(EnvironmentChangeEvent event) {
Set<String> refreshedSet = new HashSet<>();
beanMap.forEach((name, bean) -> event.getKeys().forEach(key -> {
String prefix = AnnotationUtils.getValue(bean.getAnnotation()).toString();
// prevent multiple refresh one ConfigurationPropertiesBean.
if (key.startsWith(prefix) && refreshedSet.add(name)) {
rebind(name);
}
}));
}
}

@ -88,6 +88,12 @@
"name": "spring.cloud.nacos.password",
"type": "java.lang.String",
"description": "nacos password to authenticate."
},
{
"name": "spring.cloud.nacos.config.refresh-behavior",
"type": "com.alibaba.cloud.nacos.refresh.RefreshBehavior",
"defaultValue": "ALL",
"description": "ConfigurationPropertiesBean refresh behavior."
}
]
}

@ -0,0 +1,83 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.nacos;
import java.util.HashSet;
import java.util.Set;
import com.alibaba.cloud.nacos.refresh.RefreshBehavior;
import com.alibaba.cloud.nacos.refresh.SmartConfigurationPropertiesRebinder;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.util.ReflectionTestUtils;
import static com.alibaba.cloud.nacos.SmartConfigurationPropertiesRebinderIntegrationTest.TestConfig;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
/**
*
*
* @author freeman
* @date 2022/2/6
*/
@SpringBootTest(classes = TestConfig.class, properties = {
"spring.cloud.nacos.config.refresh-behavior=specific",
"spring.cloud.nacos.server-addr=123.123.123.123:8848" }, webEnvironment = RANDOM_PORT)
public class SmartConfigurationPropertiesRebinderIntegrationTest {
@Autowired
private ConfigurationPropertiesRebinder rebinder;
@Autowired
private ApplicationContext context;
@Test
public void testUsingSmartConfigurationPropertiesRebinder() {
assertThat(rebinder.getClass())
.isEqualTo(SmartConfigurationPropertiesRebinder.class);
RefreshBehavior refreshBehavior = (RefreshBehavior) ReflectionTestUtils
.getField(rebinder, "refreshBehavior");
assertThat(refreshBehavior).isEqualTo(RefreshBehavior.SPECIFIC);
}
@Test
public void testSpecificRefreshWork() {
Set<String> keys = new HashSet<>();
keys.add("spring.cloud.nacos.config.server-addr");
keys.add("spring.cloud.nacos.config.name");
// for debug
context.publishEvent(new EnvironmentChangeEvent(context, keys));
}
@Configuration
@ImportAutoConfiguration({ NacosConfigAutoConfiguration.class })
@EnableAutoConfiguration
public static class TestConfig {
}
}
Loading…
Cancel
Save