From 14d54b196412d8046abdaa0c8695befaf82adaab Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Thu, 31 Dec 2015 15:29:02 +0000 Subject: [PATCH] Eagerly instantiate @RefreshScope beans on startup This ensures that any fetaures that require a bean to be instantiated (for instance @Scheduled) behave the same for @RefreshScope and normal singletons. Fixes gh-74 --- .../cloud/context/scope/GenericScope.java | 4 + .../context/scope/refresh/RefreshScope.java | 41 ++- .../ImportRefreshScopeIntegrationTests.java | 15 +- .../MoreRefreshScopeIntegrationTests.java | 93 ++++--- .../refresh/RefreshScopeIntegrationTests.java | 78 +++--- .../RefreshScopeLazyIntegrationTests.java | 251 ++++++++++++++++++ .../scope/refresh/RefreshScopeScaleTests.java | 4 + 7 files changed, 403 insertions(+), 83 deletions(-) create mode 100644 spring-cloud-context/src/test/java/org/springframework/cloud/context/scope/refresh/RefreshScopeLazyIntegrationTests.java 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 0e6617a1..3f757c73 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 @@ -258,6 +258,10 @@ public class GenericScope implements Scope, BeanFactoryPostProcessor, Disposable return new IllegalStateException(throwable); } + protected String getName() { + return this.name; + } + private static class BeanLifecycleWrapperCache { private final ScopeCache cache; 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 30c155ab..8ddf4d10 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 @@ -16,9 +16,14 @@ package org.springframework.cloud.context.scope.refresh; import java.io.Serializable; import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.cloud.context.scope.GenericScope; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.event.EventListener; import org.springframework.jmx.export.annotation.ManagedOperation; import org.springframework.jmx.export.annotation.ManagedResource; @@ -64,18 +69,50 @@ import org.springframework.jmx.export.annotation.ManagedResource; * */ @ManagedResource -public class RefreshScope extends GenericScope implements ApplicationContextAware { +public class RefreshScope extends GenericScope + implements ApplicationContextAware, BeanDefinitionRegistryPostProcessor { private ApplicationContext context; + private BeanDefinitionRegistry registry; + private boolean eager = true; /** * Create a scope instance and give it the default name: "refresh". */ public RefreshScope() { - super(); super.setName("refresh"); } + /** + * Flag to determine whether all beans in refresh scope should be instantiated eagerly + * on startup. Default true. + * + * @param eager the flag to set + */ + public void setEager(boolean eager) { + this.eager = eager; + } + + @Override + public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) + throws BeansException { + this.registry = registry; + } + + @EventListener + public void start(ContextRefreshedEvent event) { + if (event.getApplicationContext() == this.context) { + if (this.eager && this.registry != null) { + for (String name : this.context.getBeanDefinitionNames()) { + BeanDefinition definition = this.registry.getBeanDefinition(name); + if (this.getName().equals(definition.getScope())) { + this.context.getBean(name); + } + } + } + } + } + @ManagedOperation(description = "Dispose of the current instance of bean name provided and force a refresh on next method execution.") public boolean refresh(String name) { if (!name.startsWith(SCOPED_TARGET_PREFIX)) { diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/context/scope/refresh/ImportRefreshScopeIntegrationTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/context/scope/refresh/ImportRefreshScopeIntegrationTests.java index 76cb9a74..cfbfad8f 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/context/scope/refresh/ImportRefreshScopeIntegrationTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/context/scope/refresh/ImportRefreshScopeIntegrationTests.java @@ -15,8 +15,6 @@ */ package org.springframework.cloud.context.scope.refresh; -import static org.junit.Assert.assertEquals; - import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -29,10 +27,12 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import static org.junit.Assert.assertEquals; + @SpringApplicationConfiguration(classes = TestConfiguration.class) @RunWith(SpringJUnit4ClassRunner.class) public class ImportRefreshScopeIntegrationTests { - + @Autowired private ConfigurableListableBeanFactory beanFactory; @@ -40,13 +40,14 @@ public class ImportRefreshScopeIntegrationTests { private ExampleService service; @Autowired - private org.springframework.cloud.context.scope.refresh.RefreshScope scope; + org.springframework.cloud.context.scope.refresh.RefreshScope scope; @Test public void testSimpleProperties() throws Exception { - assertEquals("Hello scope!", service.getMessage()); - assertEquals("refresh", beanFactory.getBeanDefinition("scopedTarget.service").getScope()); - assertEquals("Hello scope!", service.getMessage()); + assertEquals("Hello scope!", this.service.getMessage()); + assertEquals("refresh", + this.beanFactory.getBeanDefinition("scopedTarget.service").getScope()); + assertEquals("Hello scope!", this.service.getMessage()); } @Configuration("service") diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/context/scope/refresh/MoreRefreshScopeIntegrationTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/context/scope/refresh/MoreRefreshScopeIntegrationTests.java index b4d54305..cd020704 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/context/scope/refresh/MoreRefreshScopeIntegrationTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/context/scope/refresh/MoreRefreshScopeIntegrationTests.java @@ -16,13 +16,9 @@ package org.springframework.cloud.context.scope.refresh; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -36,18 +32,23 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.cloud.context.scope.refresh.MoreRefreshScopeIntegrationTests.TestConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.jmx.export.annotation.ManagedAttribute; import org.springframework.jmx.export.annotation.ManagedResource; -import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration; -import org.springframework.cloud.context.config.annotation.RefreshScope; -import org.springframework.cloud.context.scope.refresh.MoreRefreshScopeIntegrationTests.TestConfiguration; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + @SpringApplicationConfiguration(classes = TestConfiguration.class) @RunWith(SpringJUnit4ClassRunner.class) public class MoreRefreshScopeIntegrationTests { @@ -55,9 +56,6 @@ public class MoreRefreshScopeIntegrationTests { @Autowired private TestService service; - @Autowired - private TestProperties properties; - @Autowired private org.springframework.cloud.context.scope.refresh.RefreshScope scope; @@ -66,34 +64,40 @@ public class MoreRefreshScopeIntegrationTests { @Before public void init() { + assertEquals(1, TestService.getInitCount()); + TestService.reset(); + } + + @After + public void close() { TestService.reset(); } @Test @DirtiesContext public void testSimpleProperties() throws Exception { - assertEquals("Hello scope!", service.getMessage()); - assertTrue(service instanceof Advised); + assertEquals("Hello scope!", this.service.getMessage()); + assertTrue(this.service instanceof Advised); // Change the dynamic property source... - EnvironmentTestUtils.addEnvironment(environment, "message:Foo"); + EnvironmentTestUtils.addEnvironment(this.environment, "message:Foo"); // ...but don't refresh, so the bean stays the same: - assertEquals("Hello scope!", service.getMessage()); - assertEquals(1, TestService.getInitCount()); + assertEquals("Hello scope!", this.service.getMessage()); + assertEquals(0, TestService.getInitCount()); assertEquals(0, TestService.getDestroyCount()); } @Test @DirtiesContext public void testRefresh() throws Exception { - assertEquals("Hello scope!", service.getMessage()); - String id1 = service.toString(); + assertEquals("Hello scope!", this.service.getMessage()); + String id1 = this.service.toString(); // Change the dynamic property source... - EnvironmentTestUtils.addEnvironment(environment, "message:Foo"); + EnvironmentTestUtils.addEnvironment(this.environment, "message:Foo"); // ...and then refresh, so the bean is re-initialized: - scope.refreshAll(); - String id2 = service.toString(); - assertEquals("Foo", service.getMessage()); - assertEquals(2, TestService.getInitCount()); + this.scope.refreshAll(); + String id2 = this.service.toString(); + assertEquals("Foo", this.service.getMessage()); + assertEquals(1, TestService.getInitCount()); assertEquals(1, TestService.getDestroyCount()); assertNotSame(id1, id2); } @@ -101,23 +105,24 @@ public class MoreRefreshScopeIntegrationTests { @Test @DirtiesContext public void testRefreshFails() throws Exception { - assertEquals("Hello scope!", service.getMessage()); + assertEquals("Hello scope!", this.service.getMessage()); // Change the dynamic property source... - EnvironmentTestUtils.addEnvironment(environment, "message:Foo", "delay:foo"); + EnvironmentTestUtils.addEnvironment(this.environment, "message:Foo", "delay:foo"); // ...and then refresh, so the bean is re-initialized: - scope.refreshAll(); + this.scope.refreshAll(); try { // If a refresh fails (e.g. a binding error in this case) the application is // basically hosed. - assertEquals("Hello scope!", service.getMessage()); + assertEquals("Hello scope!", this.service.getMessage()); fail("expected BeanCreationException"); - } catch (BeanCreationException e) { + } + catch (BeanCreationException e) { } // But we can fix it by fixing the binding error: - EnvironmentTestUtils.addEnvironment(environment, "delay:0"); + EnvironmentTestUtils.addEnvironment(this.environment, "delay:0"); // ...and then refresh, so the bean is re-initialized: - scope.refreshAll(); - assertEquals("Foo", service.getMessage()); + this.scope.refreshAll(); + assertEquals("Foo", this.service.getMessage()); } public static class TestService implements InitializingBean, DisposableBean { @@ -136,15 +141,17 @@ public class MoreRefreshScopeIntegrationTests { this.delay = delay; } + @Override public void afterPropertiesSet() throws Exception { - logger.debug("Initializing message: " + message); + logger.debug("Initializing message: " + this.message); initCount++; } + @Override public void destroy() throws Exception { - logger.debug("Destroying message: " + message); + logger.debug("Destroying message: " + this.message); destroyCount++; - message = null; + this.message = null; } public static void reset() { @@ -166,21 +173,23 @@ public class MoreRefreshScopeIntegrationTests { } public String getMessage() { - logger.debug("Getting message: " + message); + logger.debug("Getting message: " + this.message); try { - Thread.sleep(delay); - } catch (InterruptedException e) { + Thread.sleep(this.delay); + } + catch (InterruptedException e) { Thread.currentThread().interrupt(); } - logger.info("Returning message: " + message); - return message; + logger.info("Returning message: " + this.message); + return this.message; } } @Configuration @EnableConfigurationProperties - @Import({ RefreshAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class }) + @Import({ RefreshAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class }) protected static class TestConfiguration { @Bean @@ -210,7 +219,7 @@ public class MoreRefreshScopeIntegrationTests { @ManagedAttribute public String getMessage() { - return message; + return this.message; } public void setMessage(String message) { @@ -219,7 +228,7 @@ public class MoreRefreshScopeIntegrationTests { @ManagedAttribute public int getDelay() { - return delay; + return this.delay; } public void setDelay(int delay) { diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/context/scope/refresh/RefreshScopeIntegrationTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/context/scope/refresh/RefreshScopeIntegrationTests.java index e540a779..fd966fe1 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/context/scope/refresh/RefreshScopeIntegrationTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/context/scope/refresh/RefreshScopeIntegrationTests.java @@ -17,6 +17,7 @@ package org.springframework.cloud.context.scope.refresh; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -41,7 +42,10 @@ import org.springframework.jmx.export.annotation.ManagedResource; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertTrue; @SpringApplicationConfiguration(classes = TestConfiguration.class) @RunWith(SpringJUnit4ClassRunner.class) @@ -58,34 +62,40 @@ public class RefreshScopeIntegrationTests { @Before public void init() { + assertEquals(1, ExampleService.getInitCount()); + ExampleService.reset(); + } + + @After + public void close() { ExampleService.reset(); } @Test @DirtiesContext public void testSimpleProperties() throws Exception { - assertEquals("Hello scope!", service.getMessage()); - assertTrue(service instanceof Advised); + assertEquals("Hello scope!", this.service.getMessage()); + assertTrue(this.service instanceof Advised); // Change the dynamic property source... - properties.setMessage("Foo"); + this.properties.setMessage("Foo"); // ...but don't refresh, so the bean stays the same: - assertEquals("Hello scope!", service.getMessage()); - assertEquals(1, ExampleService.getInitCount()); + assertEquals("Hello scope!", this.service.getMessage()); + assertEquals(0, ExampleService.getInitCount()); assertEquals(0, ExampleService.getDestroyCount()); } @Test @DirtiesContext public void testRefresh() throws Exception { - assertEquals("Hello scope!", service.getMessage()); - String id1 = service.toString(); + assertEquals("Hello scope!", this.service.getMessage()); + String id1 = this.service.toString(); // Change the dynamic property source... - properties.setMessage("Foo"); + this.properties.setMessage("Foo"); // ...and then refresh, so the bean is re-initialized: - scope.refreshAll(); - String id2 = service.toString(); - assertEquals("Foo", service.getMessage()); - assertEquals(2, ExampleService.getInitCount()); + this.scope.refreshAll(); + String id2 = this.service.toString(); + assertEquals("Foo", this.service.getMessage()); + assertEquals(1, ExampleService.getInitCount()); assertEquals(1, ExampleService.getDestroyCount()); assertNotSame(id1, id2); assertNotNull(ExampleService.event); @@ -96,15 +106,15 @@ public class RefreshScopeIntegrationTests { @Test @DirtiesContext public void testRefreshBean() throws Exception { - assertEquals("Hello scope!", service.getMessage()); - String id1 = service.toString(); + assertEquals("Hello scope!", this.service.getMessage()); + String id1 = this.service.toString(); // Change the dynamic property source... - properties.setMessage("Foo"); + this.properties.setMessage("Foo"); // ...and then refresh, so the bean is re-initialized: - scope.refresh("service"); - String id2 = service.toString(); - assertEquals("Foo", service.getMessage()); - assertEquals(2, ExampleService.getInitCount()); + this.scope.refresh("service"); + String id2 = this.service.toString(); + assertEquals("Foo", this.service.getMessage()); + assertEquals(1, ExampleService.getInitCount()); assertEquals(1, ExampleService.getDestroyCount()); assertNotSame(id1, id2); assertNotNull(ExampleService.event); @@ -134,15 +144,17 @@ public class RefreshScopeIntegrationTests { this.delay = delay; } + @Override public void afterPropertiesSet() throws Exception { - logger.debug("Initializing message: " + message); + logger.debug("Initializing message: " + this.message); initCount++; } + @Override public void destroy() throws Exception { - logger.debug("Destroying message: " + message); + logger.debug("Destroying message: " + this.message); destroyCount++; - message = null; + this.message = null; } public static void reset() { @@ -164,16 +176,17 @@ public class RefreshScopeIntegrationTests { this.message = message; } + @Override public String getMessage() { - logger.debug("Getting message: " + message); + logger.debug("Getting message: " + this.message); try { - Thread.sleep(delay); + Thread.sleep(this.delay); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } - logger.info("Returning message: " + message); - return message; + logger.info("Returning message: " + this.message); + return this.message; } @Override @@ -184,7 +197,8 @@ public class RefreshScopeIntegrationTests { @Configuration @EnableConfigurationProperties(TestProperties.class) - @Import({ RefreshAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class }) + @Import({ RefreshAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class }) protected static class TestConfiguration { @Autowired @@ -194,8 +208,8 @@ public class RefreshScopeIntegrationTests { @RefreshScope public ExampleService service() { ExampleService service = new ExampleService(); - service.setMessage(properties.getMessage()); - service.setDelay(properties.getDelay()); + service.setMessage(this.properties.getMessage()); + service.setDelay(this.properties.getDelay()); return service; } @@ -209,7 +223,7 @@ public class RefreshScopeIntegrationTests { @ManagedAttribute public String getMessage() { - return message; + return this.message; } public void setMessage(String message) { @@ -218,7 +232,7 @@ public class RefreshScopeIntegrationTests { @ManagedAttribute public int getDelay() { - return delay; + return this.delay; } public void setDelay(int delay) { diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/context/scope/refresh/RefreshScopeLazyIntegrationTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/context/scope/refresh/RefreshScopeLazyIntegrationTests.java new file mode 100644 index 00000000..27747bcf --- /dev/null +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/context/scope/refresh/RefreshScopeLazyIntegrationTests.java @@ -0,0 +1,251 @@ +/* + * Copyright 2006-2007 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.scope.refresh; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.aop.framework.Advised; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.cloud.context.scope.GenericScope; +import org.springframework.cloud.context.scope.refresh.RefreshScopeLazyIntegrationTests.TestConfiguration; +import org.springframework.context.ApplicationListener; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.jmx.export.annotation.ManagedAttribute; +import org.springframework.jmx.export.annotation.ManagedResource; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertTrue; + +@SpringApplicationConfiguration(classes = TestConfiguration.class) +@RunWith(SpringJUnit4ClassRunner.class) +public class RefreshScopeLazyIntegrationTests { + + @Autowired + private Service service; + + @Autowired + private TestProperties properties; + + @Autowired + private org.springframework.cloud.context.scope.refresh.RefreshScope scope; + + @Before + public void init() { + // The RefreshScope is lazy (eager=false) so it won't have been instantiated yet + assertEquals(0, ExampleService.getInitCount()); + ExampleService.reset(); + } + + @After + public void close() { + ExampleService.reset(); + } + + @Test + @DirtiesContext + public void testSimpleProperties() throws Exception { + assertEquals("Hello scope!", this.service.getMessage()); + assertTrue(this.service instanceof Advised); + // Change the dynamic property source... + this.properties.setMessage("Foo"); + // ...but don't refresh, so the bean stays the same: + assertEquals("Hello scope!", this.service.getMessage()); + assertEquals(1, ExampleService.getInitCount()); + assertEquals(0, ExampleService.getDestroyCount()); + } + + @Test + @DirtiesContext + public void testRefresh() throws Exception { + assertEquals("Hello scope!", this.service.getMessage()); + String id1 = this.service.toString(); + // Change the dynamic property source... + this.properties.setMessage("Foo"); + // ...and then refresh, so the bean is re-initialized: + this.scope.refreshAll(); + String id2 = this.service.toString(); + assertEquals("Foo", this.service.getMessage()); + assertEquals(2, ExampleService.getInitCount()); + assertEquals(1, ExampleService.getDestroyCount()); + assertNotSame(id1, id2); + assertNotNull(ExampleService.event); + assertEquals(RefreshScopeRefreshedEvent.DEFAULT_NAME, + ExampleService.event.getName()); + } + + @Test + @DirtiesContext + public void testRefreshBean() throws Exception { + assertEquals("Hello scope!", this.service.getMessage()); + String id1 = this.service.toString(); + // Change the dynamic property source... + this.properties.setMessage("Foo"); + // ...and then refresh, so the bean is re-initialized: + this.scope.refresh("service"); + String id2 = this.service.toString(); + assertEquals("Foo", this.service.getMessage()); + assertEquals(2, ExampleService.getInitCount()); + assertEquals(1, ExampleService.getDestroyCount()); + assertNotSame(id1, id2); + assertNotNull(ExampleService.event); + assertEquals(GenericScope.SCOPED_TARGET_PREFIX + "service", + ExampleService.event.getName()); + } + + public static interface Service { + + String getMessage(); + + } + + public static class ExampleService implements Service, InitializingBean, + DisposableBean, ApplicationListener { + + private static Log logger = LogFactory.getLog(ExampleService.class); + + private volatile static int initCount = 0; + private volatile static int destroyCount = 0; + private volatile static RefreshScopeRefreshedEvent event; + + private String message = null; + private volatile long delay = 0; + + public void setDelay(long delay) { + this.delay = delay; + } + + @Override + public void afterPropertiesSet() throws Exception { + logger.debug("Initializing message: " + this.message); + initCount++; + } + + @Override + public void destroy() throws Exception { + logger.debug("Destroying message: " + this.message); + destroyCount++; + this.message = null; + } + + public static void reset() { + initCount = 0; + destroyCount = 0; + event = null; + } + + public static int getInitCount() { + return initCount; + } + + public static int getDestroyCount() { + return destroyCount; + } + + public void setMessage(String message) { + logger.debug("Setting message: " + message); + this.message = message; + } + + @Override + public String getMessage() { + logger.debug("Getting message: " + this.message); + try { + Thread.sleep(this.delay); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + logger.info("Returning message: " + this.message); + return this.message; + } + + @Override + public void onApplicationEvent(RefreshScopeRefreshedEvent e) { + event = e; + } + } + + @Configuration + @EnableConfigurationProperties(TestProperties.class) + @Import({ RefreshAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class }) + protected static class TestConfiguration { + + @Autowired + private TestProperties properties; + + @Bean + public static org.springframework.cloud.context.scope.refresh.RefreshScope refreshScope() { + org.springframework.cloud.context.scope.refresh.RefreshScope scope = new org.springframework.cloud.context.scope.refresh.RefreshScope(); + scope.setEager(false); + return scope; + } + + @Bean + @RefreshScope + public ExampleService service() { + ExampleService service = new ExampleService(); + service.setMessage(this.properties.getMessage()); + service.setDelay(this.properties.getDelay()); + return service; + } + + } + + @ConfigurationProperties + @ManagedResource + protected static class TestProperties { + private String message; + private int delay; + + @ManagedAttribute + public String getMessage() { + return this.message; + } + + public void setMessage(String message) { + this.message = message; + } + + @ManagedAttribute + public int getDelay() { + return this.delay; + } + + public void setDelay(int delay) { + this.delay = delay; + } + } + +} diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/context/scope/refresh/RefreshScopeScaleTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/context/scope/refresh/RefreshScopeScaleTests.java index 0663eacd..532f5689 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/context/scope/refresh/RefreshScopeScaleTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/context/scope/refresh/RefreshScopeScaleTests.java @@ -57,6 +57,9 @@ public class RefreshScopeScaleTests { private ExecutorService executor = Executors.newFixedThreadPool(8); + @Autowired + org.springframework.cloud.context.scope.refresh.RefreshScope scope; + @Autowired private ExampleService service; @@ -73,6 +76,7 @@ public class RefreshScopeScaleTests { ExampleService.count = 0; this.properties.setMessage("Foo"); this.properties.setDelay(500); + this.scope.refreshAll(); final CountDownLatch latch = new CountDownLatch(n); Future result = null; for (int i = 0; i < n; i++) {