diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/autoconfigure/RefreshAutoConfiguration.java b/spring-cloud-context/src/main/java/org/springframework/cloud/autoconfigure/RefreshAutoConfiguration.java index c141f301..c9d0516a 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/autoconfigure/RefreshAutoConfiguration.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/autoconfigure/RefreshAutoConfiguration.java @@ -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 + . emptySet())); + } } @ConditionalOnClass(Endpoint.class) diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/config/ConfigurationPropertiesBootstrapConfiguration.java b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/config/ConfigurationPropertiesBootstrapConfiguration.java new file mode 100644 index 00000000..9087bc98 --- /dev/null +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/config/ConfigurationPropertiesBootstrapConfiguration.java @@ -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; + } + + +} diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/config/PropertySourceBootstrapConfiguration.java b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/config/PropertySourceBootstrapConfiguration.java index 5db3d02a..ba74f23b 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/config/PropertySourceBootstrapConfiguration.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/config/PropertySourceBootstrapConfiguration.java @@ -53,7 +53,7 @@ import org.springframework.util.StringUtils; @Configuration @EnableConfigurationProperties(PropertySourceBootstrapProperties.class) public class PropertySourceBootstrapConfiguration implements - ApplicationContextInitializer { +ApplicationContextInitializer { 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())) { diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/EncryptionBootstrapConfiguration.java b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/EncryptionBootstrapConfiguration.java index 5fbdf56b..43156933 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/EncryptionBootstrapConfiguration.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/encrypt/EncryptionBootstrapConfiguration.java @@ -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) { diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/context/properties/ConfigurationPropertiesBeans.java b/spring-cloud-context/src/main/java/org/springframework/cloud/context/properties/ConfigurationPropertiesBeans.java new file mode 100644 index 00000000..04eb0f86 --- /dev/null +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/context/properties/ConfigurationPropertiesBeans.java @@ -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 @ConfigurationProperties beans in the context and + * its parent. + * + * @author Dave Syer + * + */ +@Component +public class ConfigurationPropertiesBeans implements BeanPostProcessor, +ApplicationContextAware { + + private ConfigurationBeanFactoryMetaData metaData; + + private Map beans = new HashMap(); + + 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 getBeanNames() { + return new HashSet(this.beans.keySet()); + } + +} diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinder.java b/spring-cloud-context/src/main/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinder.java index 8d6dab67..9500675f 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinder.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinder.java @@ -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, ApplicationContextAware { +public class ConfigurationPropertiesRebinder implements ApplicationContextAware, +ApplicationListener { - private ConfigurationBeanFactoryMetaData metaData; + private ConfigurationPropertiesBeans beans; private ConfigurationPropertiesBindingPostProcessor binder; - public ConfigurationPropertiesRebinder( - ConfigurationPropertiesBindingPostProcessor binder) { - this.binder = binder; - } - - private Map beans = new HashMap(); - 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 getBeanNames() { - return new HashSet(this.beans.keySet()); + return new HashSet(this.beans.getBeanNames()); } @Override diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/context/scope/GenericScope.java b/spring-cloud-context/src/main/java/org/springframework/cloud/context/scope/GenericScope.java index ac940e4a..2b02f11e 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/context/scope/GenericScope.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/context/scope/GenericScope.java @@ -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 diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/context/scope/refresh/RefreshScope.java b/spring-cloud-context/src/main/java/org/springframework/cloud/context/scope/refresh/RefreshScope.java index 953e5103..30c155ab 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/context/scope/refresh/RefreshScope.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/context/scope/refresh/RefreshScope.java @@ -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; /** *

- * 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. *

- * + * *

- * Note that all beans in this scope are only 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 <aop:auto-proxy/> to any bean definitions. + * Note that all beans in this scope are only 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 <aop:auto-proxy/> to any bean + * definitions. *

- * + * *

- * 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. *

- * + * * @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 diff --git a/spring-cloud-context/src/main/resources/META-INF/spring.factories b/spring-cloud-context/src/main/resources/META-INF/spring.factories index f49c0d6e..5be1f276 100644 --- a/spring-cloud-context/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-context/src/main/resources/META-INF/spring.factories @@ -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 \ No newline at end of file diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderIntegrationTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderIntegrationTests.java index d76cdbff..926d4256 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderIntegrationTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderIntegrationTests.java @@ -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++; } }