Rebind configuration properties on startup in case environment changed

The main driver for this was a config server use case, where the server
wants to configure its own properties in the remote repo. It was working
up to a point, but the beans in bootstrap context were not refreshed
after the Environment was built so you had to manually refresh them to
get them to point to (e.g.) config repos configured remotely.

Fixes https://github.com/spring-cloud/spring-cloud-config/issues/139
This commit is contained in:
Dave Syer
2015-08-21 12:22:31 +01:00
parent 385994f58c
commit b022a39ba8
10 changed files with 315 additions and 169 deletions

View File

@@ -17,12 +17,12 @@
package org.springframework.cloud.autoconfigure;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration;
@@ -44,10 +44,13 @@ import org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfigu
import org.springframework.cloud.bootstrap.config.RefreshEndpoint;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.cloud.context.environment.EnvironmentManager;
import org.springframework.cloud.context.properties.ConfigurationPropertiesBeans;
import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder;
import org.springframework.cloud.context.restart.RestartEndpoint;
import org.springframework.cloud.context.scope.refresh.RefreshScope;
import org.springframework.cloud.logging.LoggingRebinder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
@@ -138,33 +141,48 @@ public class RefreshAutoConfiguration {
@Configuration
@ConditionalOnBean(ConfigurationPropertiesBindingPostProcessor.class)
protected static class ConfigurationPropertiesRebinderConfiguration implements
BeanFactoryAware {
ApplicationContextAware, SmartInitializingSingleton {
private BeanFactory context;
private ApplicationContext context;
@Override
public void setBeanFactory(BeanFactory applicationContext) throws BeansException {
public void setApplicationContext(ApplicationContext applicationContext) {
this.context = applicationContext;
}
@Bean
@ConditionalOnMissingBean(search=SearchStrategy.CURRENT)
public ConfigurationPropertiesRebinder configurationPropertiesRebinder() {
// Since this is a BeanPostProcessor we have to be super careful not to cause
// a cascade of bean instantiation. Knowing the *name* of the beans we need is
// super optimal, but a little brittle (unfortunately we have no choice).
ConfigurationPropertiesBindingPostProcessor binder = this.context
.getBean(
ConfigurationPropertiesBindingPostProcessorRegistrar.BINDER_BEAN_NAME,
ConfigurationPropertiesBindingPostProcessor.class);
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
public ConfigurationPropertiesBeans configurationPropertiesBeans() {
// Since this is a BeanPostProcessor we have to be super careful not to
// cause a cascade of bean instantiation. Knowing the *name* of the beans we
// need is super optimal, but a little brittle (unfortunately we have no
// choice).
ConfigurationBeanFactoryMetaData metaData = this.context.getBean(
ConfigurationPropertiesBindingPostProcessorRegistrar.BINDER_BEAN_NAME
+ ".store", ConfigurationBeanFactoryMetaData.class);
ConfigurationPropertiesBeans beans = new ConfigurationPropertiesBeans();
beans.setBeanMetaDataStore(metaData);
return beans;
}
@Bean
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
public ConfigurationPropertiesRebinder configurationPropertiesRebinder(
ConfigurationPropertiesBeans beans,
ConfigurationPropertiesBindingPostProcessor binder) {
ConfigurationPropertiesRebinder rebinder = new ConfigurationPropertiesRebinder(
binder);
rebinder.setBeanMetaDataStore(metaData);
binder, beans);
return rebinder;
}
@Override
public void afterSingletonsInstantiated() {
// After all beans are initialized send a pre-emptive EnvironmentChangeEvent
// so that anything that needs to rebind gets a chance now (especially for
// beans in the parent context)
this.context.publishEvent(new EnvironmentChangeEvent(Collections
.<String> emptySet()));
}
}
@ConditionalOnClass(Endpoint.class)

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2015 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
*
* http://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 org.springframework.cloud.bootstrap.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.context.properties.ConfigurationBeanFactoryMetaData;
import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessorRegistrar;
import org.springframework.cloud.context.properties.ConfigurationPropertiesBeans;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Dave Syer
*
*/
@Configuration
public class ConfigurationPropertiesBootstrapConfiguration {
@Bean
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
public ConfigurationPropertiesBeans configurationPropertiesBeans(
ApplicationContext context) {
// Since this is a BeanPostProcessor we have to be super careful not to
// cause a cascade of bean instantiation. Knowing the *name* of the beans we
// need is super optimal, but a little brittle (unfortunately we have no
// choice).
ConfigurationBeanFactoryMetaData metaData = context.getBean(
ConfigurationPropertiesBindingPostProcessorRegistrar.BINDER_BEAN_NAME
+ ".store", ConfigurationBeanFactoryMetaData.class);
ConfigurationPropertiesBeans rebinder = new ConfigurationPropertiesBeans();
rebinder.setBeanMetaDataStore(metaData);
return rebinder;
}
}

View File

@@ -53,7 +53,7 @@ import org.springframework.util.StringUtils;
@Configuration
@EnableConfigurationProperties(PropertySourceBootstrapProperties.class)
public class PropertySourceBootstrapConfiguration implements
ApplicationContextInitializer<ConfigurableApplicationContext> {
ApplicationContextInitializer<ConfigurableApplicationContext> {
private static final String BOOTSTRAP_PROPERTY_SOURCE_NAME = BootstrapApplicationListener.BOOTSTRAP_PROPERTY_SOURCE_NAME;
@@ -112,12 +112,13 @@ public class PropertySourceBootstrapConfiguration implements
try {
ResourceUtils.getURL(logConfig).openStream().close();
LogFile logFile = LogFile.get(environment);
system.initialize(new LoggingInitializationContext(environment), logConfig, logFile);
system.initialize(new LoggingInitializationContext(environment),
logConfig, logFile);
}
catch (Exception ex) {
PropertySourceBootstrapConfiguration.logger
.warn("Logging config file location '" + logConfig
+ "' cannot be opened and will be ignored");
.warn("Logging config file location '" + logConfig
+ "' cannot be opened and will be ignored");
}
}
}
@@ -137,7 +138,7 @@ public class PropertySourceBootstrapConfiguration implements
incoming.addFirst(composite);
PropertySourceBootstrapProperties remoteProperties = new PropertySourceBootstrapProperties();
new RelaxedDataBinder(remoteProperties, "spring.cloud.config")
.bind(new PropertySourcesPropertyValues(incoming));
.bind(new PropertySourcesPropertyValues(incoming));
if (!remoteProperties.isAllowOverride()
|| (!remoteProperties.isOverrideNone() && remoteProperties
.isOverrideSystemProperties())) {

View File

@@ -21,14 +21,9 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.context.properties.ConfigurationBeanFactoryMetaData;
import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor;
import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessorRegistrar;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.bootstrap.encrypt.KeyProperties.KeyStore;
import org.springframework.cloud.context.encrypt.EncryptorFactory;
import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
@@ -96,18 +91,6 @@ public class EncryptionBootstrapConfiguration {
}
@Bean
public ConfigurationPropertiesRebinder configurationPropertiesRebinder(
ApplicationContext context, ConfigurationPropertiesBindingPostProcessor binder) {
ConfigurationBeanFactoryMetaData metaData = context.getBean(
ConfigurationPropertiesBindingPostProcessorRegistrar.BINDER_BEAN_NAME
+ ".store", ConfigurationBeanFactoryMetaData.class);
ConfigurationPropertiesRebinder rebinder = new ConfigurationPropertiesRebinder(
binder);
rebinder.setBeanMetaDataStore(metaData);
return rebinder;
}
@Bean
public EnvironmentDecryptApplicationInitializer environmentDecryptApplicationListener() {
if (this.encryptor == null) {

View File

@@ -0,0 +1,132 @@
/*
* Copyright 2013-2014 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
*
* http://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 org.springframework.cloud.context.properties;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.context.properties.ConfigurationBeanFactoryMetaData;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
/**
* Collects references to <code>@ConfigurationProperties</code> beans in the context and
* its parent.
*
* @author Dave Syer
*
*/
@Component
public class ConfigurationPropertiesBeans implements BeanPostProcessor,
ApplicationContextAware {
private ConfigurationBeanFactoryMetaData metaData;
private Map<String, Object> beans = new HashMap<String, Object>();
private ConfigurableListableBeanFactory beanFactory;
private String refreshScope;
private boolean refreshScopeInitialized;
private ConfigurationPropertiesBeans parent;
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
if (applicationContext.getAutowireCapableBeanFactory() instanceof ConfigurableListableBeanFactory) {
this.beanFactory = (ConfigurableListableBeanFactory) applicationContext
.getAutowireCapableBeanFactory();
}
if (applicationContext.getParent() != null
&& applicationContext.getParent().getAutowireCapableBeanFactory() instanceof ConfigurableListableBeanFactory) {
ConfigurableListableBeanFactory listable = (ConfigurableListableBeanFactory) applicationContext
.getParent().getAutowireCapableBeanFactory();
String[] names = listable
.getBeanNamesForType(ConfigurationPropertiesBeans.class);
if (names.length == 1) {
this.parent = (ConfigurationPropertiesBeans) listable.getBean(names[0]);
this.beans.putAll(this.parent.beans);
}
}
}
/**
* @param beans the bean meta data to set
*/
public void setBeanMetaDataStore(ConfigurationBeanFactoryMetaData beans) {
this.metaData = beans;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (isRefreshScoped(beanName)) {
return bean;
}
ConfigurationProperties annotation = AnnotationUtils.findAnnotation(
bean.getClass(), ConfigurationProperties.class);
if (annotation != null) {
this.beans.put(beanName, bean);
}
else if (this.metaData != null) {
annotation = this.metaData.findFactoryAnnotation(beanName,
ConfigurationProperties.class);
if (annotation != null) {
this.beans.put(beanName, bean);
}
}
return bean;
}
private boolean isRefreshScoped(String beanName) {
if (this.refreshScope == null && !this.refreshScopeInitialized) {
this.refreshScopeInitialized = true;
for (String scope : this.beanFactory.getRegisteredScopeNames()) {
if (this.beanFactory.getRegisteredScope(scope) instanceof org.springframework.cloud.context.scope.refresh.RefreshScope) {
this.refreshScope = scope;
break;
}
}
}
if (beanName == null || this.refreshScope == null) {
return false;
}
return this.beanFactory.containsBeanDefinition(beanName)
&& this.refreshScope.equals(this.beanFactory.getBeanDefinition(beanName)
.getScope());
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
public Set<String> getBeanNames() {
return new HashSet<String>(this.beans.keySet());
}
}

View File

@@ -15,15 +15,10 @@
*/
package org.springframework.cloud.context.properties;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.context.properties.ConfigurationBeanFactoryMetaData;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor;
import org.springframework.cloud.context.config.annotation.RefreshScope;
@@ -31,7 +26,6 @@ import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
@@ -52,116 +46,53 @@ import org.springframework.stereotype.Component;
*/
@Component
@ManagedResource
public class ConfigurationPropertiesRebinder implements BeanPostProcessor,
ApplicationListener<EnvironmentChangeEvent>, ApplicationContextAware {
public class ConfigurationPropertiesRebinder implements ApplicationContextAware,
ApplicationListener<EnvironmentChangeEvent> {
private ConfigurationBeanFactoryMetaData metaData;
private ConfigurationPropertiesBeans beans;
private ConfigurationPropertiesBindingPostProcessor binder;
public ConfigurationPropertiesRebinder(
ConfigurationPropertiesBindingPostProcessor binder) {
this.binder = binder;
}
private Map<String, Object> beans = new HashMap<String, Object>();
private ApplicationContext applicationContext;
private ConfigurableListableBeanFactory beanFactory;
private String refreshScope;
private boolean refreshScopeInitialized;
public ConfigurationPropertiesRebinder(
ConfigurationPropertiesBindingPostProcessor binder,
ConfigurationPropertiesBeans beans) {
this.binder = binder;
this.beans = beans;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
if (applicationContext.getAutowireCapableBeanFactory() instanceof ConfigurableListableBeanFactory) {
this.beanFactory = (ConfigurableListableBeanFactory) applicationContext
.getAutowireCapableBeanFactory();
}
}
/**
* @param beans the bean meta data to set
*/
public void setBeanMetaDataStore(ConfigurationBeanFactoryMetaData beans) {
this.metaData = beans;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (isRefreshScoped(beanName)) {
return bean;
}
ConfigurationProperties annotation = AnnotationUtils.findAnnotation(
bean.getClass(), ConfigurationProperties.class);
if (annotation != null) {
this.beans.put(beanName, bean);
}
else if (this.metaData != null) {
annotation = this.metaData.findFactoryAnnotation(beanName,
ConfigurationProperties.class);
if (annotation != null) {
this.beans.put(beanName, bean);
}
}
return bean;
}
private boolean isRefreshScoped(String beanName) {
if (this.refreshScope == null && !this.refreshScopeInitialized) {
this.refreshScopeInitialized = true;
for (String scope : this.beanFactory.getRegisteredScopeNames()) {
if (this.beanFactory.getRegisteredScope(scope) instanceof org.springframework.cloud.context.scope.refresh.RefreshScope) {
this.refreshScope = scope;
break;
}
}
}
if (beanName == null || this.refreshScope == null) {
return false;
}
return this.beanFactory.containsBeanDefinition(beanName)
&& this.refreshScope
.equals(this.beanFactory.getBeanDefinition(beanName).getScope());
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
@ManagedOperation
public void rebind() {
for (String name : this.beans.keySet()) {
for (String name : this.beans.getBeanNames()) {
rebind(name);
}
}
@ManagedOperation
public void rebind(String name) {
if (!this.applicationContext.containsBean(name)) {
return;
public boolean rebind(String name) {
if (!this.beans.getBeanNames().contains(name)) {
return false;
}
if (isRefreshScoped(name)) {
return;
}
Object bean = this.applicationContext.getBean(name);
this.binder.postProcessBeforeInitialization(bean, name);
if (this.applicationContext != null) {
this.applicationContext.getAutowireCapableBeanFactory().initializeBean(
bean, name);
Object bean = this.applicationContext.getBean(name);
this.binder.postProcessBeforeInitialization(bean, name);
this.applicationContext.getAutowireCapableBeanFactory().initializeBean(bean,
name);
return true;
}
return false;
}
@ManagedAttribute
public Set<String> getBeanNames() {
return new HashSet<String>(this.beans.keySet());
return new HashSet<String>(this.beans.getBeanNames());
}
@Override

View File

@@ -134,11 +134,19 @@ public class GenericScope implements Scope, BeanFactoryPostProcessor, Disposable
}
}
protected void destroy(String name) {
/**
* Destroy the named bean (i.e. flush it from the cache by default).
*
* @param name the bean name to flush
* @return true if the bean was already cached, false otherwise
*/
protected boolean destroy(String name) {
BeanLifecycleWrapper wrapper = this.cache.remove(name);
if (wrapper != null) {
wrapper.destroy();
return true;
}
return false;
}
@Override

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2002-2009 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
*
*
* http://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.
@@ -24,37 +24,44 @@ import org.springframework.jmx.export.annotation.ManagedResource;
/**
* <p>
* A Scope implementation that allows for beans to be refreshed dynamically at runtime (see {@link #refresh(String)} and
* {@link #refreshAll()}). If a bean is refreshed then the next time the bean is accessed (i.e. a method is executed) a
* new instance is created. All lifecycle methods are applied to the bean instances, so any destruction callbacks that
* were registered in the bean factory are called when it is refreshed, and then the initialization callbacks are
* invoked as normal when the new instance is created. A new bean instance is created from the original bean definition,
* so any externalized content (property placeholders or expressions in string literals) is re-evaluated when it is
* created.
* A Scope implementation that allows for beans to be refreshed dynamically at runtime
* (see {@link #refresh(String)} and {@link #refreshAll()}). If a bean is refreshed then
* the next time the bean is accessed (i.e. a method is executed) a new instance is
* created. All lifecycle methods are applied to the bean instances, so any destruction
* callbacks that were registered in the bean factory are called when it is refreshed, and
* then the initialization callbacks are invoked as normal when the new instance is
* created. A new bean instance is created from the original bean definition, so any
* externalized content (property placeholders or expressions in string literals) is
* re-evaluated when it is created.
* </p>
*
*
* <p>
* Note that all beans in this scope are <em>only</em> initialized when first accessed, so the scope forces lazy
* initialization semantics. The implementation involves creating a proxy for every bean in the scope, so there is a
* flag {@link #setProxyTargetClass(boolean) proxyTargetClass} which controls the proxy creation, defaulting to JDK
* dynamic proxies and therefore only exposing the interfaces implemented by a bean. If callers need access to other
* methods then the flag needs to be set (and CGLib present on the classpath). Because this scope automatically proxies
* all its beans, there is no need to add <code>&lt;aop:auto-proxy/&gt;</code> to any bean definitions.
* Note that all beans in this scope are <em>only</em> initialized when first accessed, so
* the scope forces lazy initialization semantics. The implementation involves creating a
* proxy for every bean in the scope, so there is a flag
* {@link #setProxyTargetClass(boolean) proxyTargetClass} which controls the proxy
* creation, defaulting to JDK dynamic proxies and therefore only exposing the interfaces
* implemented by a bean. If callers need access to other methods then the flag needs to
* be set (and CGLib present on the classpath). Because this scope automatically proxies
* all its beans, there is no need to add <code>&lt;aop:auto-proxy/&gt;</code> to any bean
* definitions.
* </p>
*
*
* <p>
* The scoped proxy approach adopted here has a side benefit that bean instances are automatically {@link Serializable},
* and can be sent across the wire as long as the receiver has an identical application context on the other side. To
* ensure that the two contexts agree that they are identical they have to have the same serialization id. One will be
* generated automatically by default from the bean names, so two contexts with the same bean names are by default able
* to exchange beans by name. If you need to override the default id then provide an explicit {@link #setId(String) id}
* when the Scope is declared.
* The scoped proxy approach adopted here has a side benefit that bean instances are
* automatically {@link Serializable}, and can be sent across the wire as long as the
* receiver has an identical application context on the other side. To ensure that the two
* contexts agree that they are identical they have to have the same serialization id. One
* will be generated automatically by default from the bean names, so two contexts with
* the same bean names are by default able to exchange beans by name. If you need to
* override the default id then provide an explicit {@link #setId(String) id} when the
* Scope is declared.
* </p>
*
*
* @author Dave Syer
*
*
* @since 3.1
*
*
*/
@ManagedResource
public class RefreshScope extends GenericScope implements ApplicationContextAware {
@@ -70,20 +77,24 @@ public class RefreshScope extends GenericScope implements ApplicationContextAwar
}
@ManagedOperation(description = "Dispose of the current instance of bean name provided and force a refresh on next method execution.")
public void refresh(String name) {
public boolean refresh(String name) {
if (!name.startsWith(SCOPED_TARGET_PREFIX)) {
// User wants to refresh the bean with this name but that isn't the one in the cache...
// User wants to refresh the bean with this name but that isn't the one in the
// cache...
name = SCOPED_TARGET_PREFIX + name;
}
// Ensure lifecycle is finished if bean was disposable
super.destroy(name);
context.publishEvent(new RefreshScopeRefreshedEvent(name));
if (super.destroy(name)) {
this.context.publishEvent(new RefreshScopeRefreshedEvent(name));
return true;
}
return false;
}
@ManagedOperation(description = "Dispose of the current instance of all beans in this scope and force a refresh on next method execution.")
public void refreshAll() {
super.destroy();
context.publishEvent(new RefreshScopeRefreshedEvent());
this.context.publishEvent(new RefreshScopeRefreshedEvent());
}
@Override

View File

@@ -10,6 +10,7 @@ org.springframework.cloud.context.restart.RestartListener
# Bootstrap components
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.bootstrap.config.ConfigurationPropertiesBootstrapConfiguration,\
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration,\
org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration,\
org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration

View File

@@ -36,7 +36,7 @@ import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@SpringApplicationConfiguration(classes=TestConfiguration.class)
@SpringApplicationConfiguration(classes = TestConfiguration.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class ConfigurationPropertiesRebinderIntegrationTests {
@@ -53,42 +53,44 @@ public class ConfigurationPropertiesRebinderIntegrationTests {
@DirtiesContext
public void testSimpleProperties() throws Exception {
assertEquals("Hello scope!", this.properties.getMessage());
assertEquals(2, this.properties.getCount());
// Change the dynamic property source...
EnvironmentTestUtils.addEnvironment(this.environment, "message:Foo");
// ...but don't refresh, so the bean stays the same:
assertEquals("Hello scope!", this.properties.getMessage());
assertEquals(1, this.properties.getCount());
assertEquals(2, this.properties.getCount());
}
@Test
@DirtiesContext
public void testRefresh() throws Exception {
assertEquals(1, this.properties.getCount());
assertEquals(2, this.properties.getCount());
assertEquals("Hello scope!", this.properties.getMessage());
// Change the dynamic property source...
EnvironmentTestUtils.addEnvironment(this.environment, "message:Foo");
// ...and then refresh, so the bean is re-initialized:
this.rebinder.rebind();
assertEquals("Foo", this.properties.getMessage());
assertEquals(2, this.properties.getCount());
assertEquals(3, this.properties.getCount());
}
@Test
@DirtiesContext
public void testRefreshByName() throws Exception {
assertEquals(1, this.properties.getCount());
assertEquals(2, this.properties.getCount());
assertEquals("Hello scope!", this.properties.getMessage());
// Change the dynamic property source...
EnvironmentTestUtils.addEnvironment(this.environment, "message:Foo");
// ...and then refresh, so the bean is re-initialized:
this.rebinder.rebind("properties");
assertEquals("Foo", this.properties.getMessage());
assertEquals(2, this.properties.getCount());
assertEquals(3, this.properties.getCount());
}
@Configuration
@EnableConfigurationProperties
@Import({RefreshConfiguration.RebinderConfiguration.class, PropertyPlaceholderAutoConfiguration.class})
@Import({ RefreshConfiguration.RebinderConfiguration.class,
PropertyPlaceholderAutoConfiguration.class })
protected static class TestConfiguration {
@Bean
@@ -101,7 +103,8 @@ public class ConfigurationPropertiesRebinderIntegrationTests {
// Hack out a protected inner class for testing
protected static class RefreshConfiguration extends RefreshAutoConfiguration {
@Configuration
protected static class RebinderConfiguration extends ConfigurationPropertiesRebinderConfiguration {
protected static class RebinderConfiguration extends
ConfigurationPropertiesRebinderConfiguration {
}
}
@@ -111,24 +114,30 @@ public class ConfigurationPropertiesRebinderIntegrationTests {
private String message;
private int delay;
private int count = 0;
public int getCount() {
return this.count;
}
public String getMessage() {
return this.message;
}
public void setMessage(String message) {
this.message = message;
}
public int getDelay() {
return this.delay;
}
public void setDelay(int delay) {
this.delay = delay;
}
@PostConstruct
public void init() {
this.count ++;
this.count++;
}
}