Break Mvc endpoint configuration out from non Mvc

Fixes gh-44 because it allows an extra layer to insert
an @AutoConfigureAfter.

Also adds some more integration tests for refresh scope

Per gh-43, note that @Configuration does not play well
with @RefreshScope (see @Ignored test case).
This commit is contained in:
Dave Syer
2014-11-25 12:23:47 +00:00
parent 56434c79eb
commit 689d12e9c1
10 changed files with 347 additions and 272 deletions

View File

@@ -0,0 +1,78 @@
/*
* 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.autoconfigure;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration;
import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
import org.springframework.cloud.config.client.RefreshEndpoint;
import org.springframework.cloud.context.environment.EnvironmentManager;
import org.springframework.cloud.context.environment.EnvironmentManagerMvcEndpoint;
import org.springframework.cloud.context.restart.RestartEndpoint;
import org.springframework.cloud.context.restart.RestartMvcEndpoint;
import org.springframework.cloud.endpoint.GenericPostableMvcEndpoint;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Dave Syer
*
*/
@Configuration
@ConditionalOnClass(EnvironmentEndpoint.class)
@ConditionalOnExpression("${endpoints.env.enabled:true}")
@ConditionalOnWebApplication
@ConditionalOnBean({ EnvironmentEndpoint.class, RefreshEndpoint.class })
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, EndpointAutoConfiguration.class })
public class EnvironmentEndpointAutoConfiguration {
@Autowired
private RestartEndpoint restartEndpoint;
@Bean
public EnvironmentManagerMvcEndpoint environmentManagerEndpoint(
EnvironmentEndpoint delegate, EnvironmentManager environment) {
return new EnvironmentManagerMvcEndpoint(delegate, environment);
}
@Bean
public MvcEndpoint refreshMvcEndpoint(RefreshEndpoint endpoint) {
return new GenericPostableMvcEndpoint(endpoint);
}
@Bean
public RestartMvcEndpoint restartMvcEndpoint() {
return new RestartMvcEndpoint(restartEndpoint);
}
@Bean
public MvcEndpoint pauseMvcEndpoint(RestartMvcEndpoint restartEndpoint) {
return restartEndpoint.getPauseEndpoint();
}
@Bean
public MvcEndpoint resumeMvcEndpoint(RestartMvcEndpoint restartEndpoint) {
return restartEndpoint.getResumeEndpoint();
}
}

View File

@@ -23,16 +23,13 @@ import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint;
import org.springframework.boot.actuate.endpoint.InfoEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationBeanFactoryMetaData;
import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -41,12 +38,9 @@ import org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfigu
import org.springframework.cloud.config.client.RefreshEndpoint;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.cloud.context.environment.EnvironmentManager;
import org.springframework.cloud.context.environment.EnvironmentManagerMvcEndpoint;
import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder;
import org.springframework.cloud.context.restart.RestartEndpoint;
import org.springframework.cloud.context.restart.RestartMvcEndpoint;
import org.springframework.cloud.context.scope.refresh.RefreshScope;
import org.springframework.cloud.endpoint.GenericPostableMvcEndpoint;
import org.springframework.cloud.logging.LoggingRebinder;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
@@ -189,44 +183,6 @@ public class RefreshAutoConfiguration {
return endpoint;
}
@Bean
public MvcEndpoint refreshMvcEndpoint(RefreshEndpoint endpoint) {
return new GenericPostableMvcEndpoint(endpoint);
}
}
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass(EnvironmentEndpoint.class)
@ConditionalOnExpression("${endpoints.env.enabled:true}")
@ConditionalOnBean(EnvironmentEndpoint.class)
protected static class EnvironmentEndpointConfiguration {
@Autowired
private RestartEndpoint restartEndpoint;
@Bean
public EnvironmentManagerMvcEndpoint environmentManagerEndpoint(
EnvironmentEndpoint delegate, EnvironmentManager environment) {
return new EnvironmentManagerMvcEndpoint(delegate, environment);
}
@Bean
public RestartMvcEndpoint restartMvcEndpoint() {
return new RestartMvcEndpoint(restartEndpoint);
}
@Bean
public MvcEndpoint pauseMvcEndpoint(RestartMvcEndpoint restartEndpoint) {
return restartEndpoint.getPauseEndpoint();
}
@Bean
public MvcEndpoint resumeMvcEndpoint(RestartMvcEndpoint restartEndpoint) {
return restartEndpoint.getResumeEndpoint();
}
}
}

View File

@@ -35,7 +35,8 @@ public class ConfigServerHealthIndicator extends AbstractHealthIndicator {
Field field = ReflectionUtils.findField(CompositePropertySource.class,
"propertySources");
field.setAccessible(true);
Set<PropertySource<?>> propertySources = (Set<PropertySource<?>>) field.get(composite);
@SuppressWarnings("unchecked")
Set<PropertySource<?>> propertySources = (Set<PropertySource<?>>) field.get(composite);
List<String> sources = new ArrayList<>();
for (PropertySource<?> ps : propertySources) {
sources.add(ps.getName());

View File

@@ -62,19 +62,8 @@ public class RestartEndpoint extends AbstractEndpoint<Boolean> implements
private IntegrationShutdown integrationShutdown;
private boolean forceShutdown;
private long timeout;
@ManagedAttribute
public boolean isForceShutdown() {
return forceShutdown;
}
public void setForceShutdown(boolean forceShutdown) {
this.forceShutdown = forceShutdown;
}
@ManagedAttribute
public long getTimeout() {
return timeout;
@@ -160,7 +149,7 @@ public class RestartEndpoint extends AbstractEndpoint<Boolean> implements
public synchronized ConfigurableApplicationContext restart() {
if (context != null) {
if (integrationShutdown != null) {
integrationShutdown.stop(forceShutdown, timeout);
integrationShutdown.stop(timeout);
}
application.setEnvironment(context.getEnvironment());
context.close();
@@ -206,8 +195,8 @@ public class RestartEndpoint extends AbstractEndpoint<Boolean> implements
this.exporter = (IntegrationMBeanExporter) exporter;
}
public void stop(boolean force, long timeout) {
exporter.stopActiveComponents(force, timeout);
public void stop(long timeout) {
exporter.stopActiveComponents(timeout);
}
}

View File

@@ -1,6 +1,7 @@
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.autoconfigure.RefreshAutoConfiguration,\
org.springframework.cloud.autoconfigure.EnvironmentEndpointAutoConfiguration,\
org.springframework.cloud.autoconfigure.ConfigClientAutoConfiguration
# Application Listeners

View File

@@ -0,0 +1,71 @@
/*
* 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 static org.junit.Assert.assertEquals;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
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.ImportRefreshScopeIntegrationTests.TestConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@SpringApplicationConfiguration(classes = TestConfiguration.class)
@RunWith(SpringJUnit4ClassRunner.class)
@Ignore("gh-43")
public class ImportRefreshScopeIntegrationTests {
@Autowired
private ConfigurableListableBeanFactory beanFactory;
@Autowired
private ExampleService service;
@Autowired
private org.springframework.cloud.context.scope.refresh.RefreshScope scope;
@Test
@DirtiesContext
public void testSimpleProperties() throws Exception {
assertEquals("Hello scope!", service.getMessage());
assertEquals("refresh", beanFactory.getBeanDefinition("service").getScope());
assertEquals("Hello scope!", service.getMessage());
}
@Configuration("service")
@RefreshScope
public static class ExampleService {
public String getMessage() {
return "Hello scope!";
}
}
@Configuration
@Import({ RefreshAutoConfiguration.class, ExampleService.class })
protected static class TestConfiguration {
}
}

View File

@@ -1,212 +0,0 @@
/*
* 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertTrue;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Before;
import org.junit.Ignore;
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.refresh.ProxyRefreshScopeIntegrationTests.TestConfiguration;
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;
@SpringApplicationConfiguration(classes = TestConfiguration.class)
@RunWith(SpringJUnit4ClassRunner.class)
@Ignore
public class ProxyRefreshScopeIntegrationTests {
@Autowired
private Service service;
@Autowired
private TestProperties properties;
@Autowired
private org.springframework.cloud.context.scope.refresh.RefreshScope scope;
@Before
public void init() {
ExampleService.reset();
}
@Test
@DirtiesContext
public void testSimpleProperties() throws Exception {
assertEquals("Hello scope!", service.getMessage());
assertTrue(service instanceof Advised);
// Change the dynamic property source...
properties.setMessage("Foo");
// ...but don't refresh, so the bean stays the same:
assertEquals("Hello scope!", service.getMessage());
assertEquals(1, ExampleService.getInitCount());
assertEquals(0, ExampleService.getDestroyCount());
}
@Test
@DirtiesContext
public void testRefresh() throws Exception {
assertEquals("Hello scope!", service.getMessage());
String id1 = service.toString();
// Change the dynamic property source...
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());
assertEquals(1, ExampleService.getDestroyCount());
assertNotSame(id1, id2);
}
@Test
@DirtiesContext
public void testRefreshBean() throws Exception {
assertEquals("Hello scope!", service.getMessage());
String id1 = service.toString();
// Change the dynamic property source...
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());
assertEquals(1, ExampleService.getDestroyCount());
assertNotSame(id1, id2);
}
public static interface Service {
String getMessage();
}
@Configuration
@RefreshScope
public static class ExampleService implements Service, InitializingBean,
DisposableBean {
@Autowired
private TestProperties properties;
private static Log logger = LogFactory.getLog(ExampleService.class);
private volatile static int initCount = 0;
private volatile static int destroyCount = 0;
private String message = null;
private volatile long delay = 0;
public void setDelay(long delay) {
this.delay = delay;
}
public void afterPropertiesSet() throws Exception {
message = properties.getMessage();
delay = properties.getDelay();
logger.debug("Initializing message: " + message);
initCount++;
}
public void destroy() throws Exception {
logger.debug("Destroying message: " + message);
destroyCount++;
message = null;
}
public static void reset() {
initCount = 0;
destroyCount = 0;
}
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;
}
public String getMessage() {
logger.debug("Getting message: " + message);
try {
Thread.sleep(delay);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
logger.info("Returning message: " + message);
return message;
}
}
@Configuration
@EnableConfigurationProperties(TestProperties.class)
@Import({ RefreshAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class, ExampleService.class })
protected static class TestConfiguration {
}
@ConfigurationProperties
@ManagedResource
protected static class TestProperties {
private String message;
private int delay;
@ManagedAttribute
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@ManagedAttribute
public int getDelay() {
return delay;
}
public void setDelay(int delay) {
this.delay = delay;
}
}
}

View File

@@ -0,0 +1,103 @@
/*
* 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.scope.refresh;
import static org.junit.Assert.assertEquals;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.TestRestTemplate;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.context.scope.refresh.RefreshEndpointIntegrationTests.ClientApp;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Dave Syer
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = ClientApp.class)
@IntegrationTest("server.port:0")
@WebAppConfiguration
public class RefreshEndpointIntegrationTests {
@Value("${local.server.port}")
private int port;
@Test
public void webAccess() throws Exception {
TestRestTemplate template = new TestRestTemplate();
template.exchange(
getUrlEncodedEntity("http://localhost:" + port + "/env", "message",
"Hello Dave!"), String.class);
template.postForObject("http://localhost:" + port + "/refresh", "", String.class);
String message = template.getForObject("http://localhost:" + port + "/",
String.class);
assertEquals("Hello Dave!", message);
}
private RequestEntity<?> getUrlEncodedEntity(String uri, String key, String value)
throws URISyntaxException {
MultiValueMap<String, String> env = new LinkedMultiValueMap<String, String>(
Collections.singletonMap("message", Arrays.asList("Hello Dave!")));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
RequestEntity<MultiValueMap<String, String>> entity = new RequestEntity<MultiValueMap<String, String>>(
env, headers, HttpMethod.POST, new URI(uri));
return entity;
}
@Configuration
@EnableAutoConfiguration
@RestController
@RefreshScope
protected static class ClientApp {
@Value("${message:Hello World!}")
String message;
@RequestMapping("/")
public String hello() {
return message;
}
public static void main(String[] args) {
SpringApplication.run(ClientApp.class, args);
}
}
}

View File

@@ -0,0 +1,87 @@
/*
* 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.scope.refresh;
import static org.junit.Assert.assertEquals;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.context.environment.EnvironmentManager;
import org.springframework.cloud.context.scope.refresh.RefreshScopeConfigurationIntegrationTests.ClientApp;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Dave Syer
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = ClientApp.class)
@Ignore
public class RefreshScopeConfigurationIntegrationTests {
@Autowired
private org.springframework.cloud.context.scope.refresh.RefreshScope scope;
@Autowired
private EnvironmentManager environmentManager;
@Autowired
private ClientApp application;
/**
* See gh-43
*/
@Test
public void beanAccess() throws Exception {
// Comment out this line and it works!
application.hello();
environmentManager.setProperty("message", "Hello Dave!");
scope.refreshAll();
String message = application.hello();
assertEquals("Hello Dave!", message);
}
@Configuration
@EnableAutoConfiguration
@RestController
@RefreshScope
protected static class ClientApp {
@Value("${message:Hello World!}")
String message;
@RequestMapping("/")
public String hello() {
return message;
}
public static void main(String[] args) {
SpringApplication.run(ClientApp.class, args);
}
}
}

View File

@@ -1,3 +1,4 @@
message: Hello scope!
delay: 0
# debug: true
# debug: true
logging.level.org.springframework.web: DEBUG