diff --git a/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/BusAutoConfiguration.java b/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/BusAutoConfiguration.java index 14d3030..3a65868 100644 --- a/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/BusAutoConfiguration.java +++ b/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/BusAutoConfiguration.java @@ -52,11 +52,8 @@ import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.event.EventListener; -import org.springframework.core.env.Environment; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.support.MessageBuilder; -import org.springframework.util.AntPathMatcher; -import org.springframework.util.PathMatcher; /** * @author Spencer Gibb @@ -68,7 +65,8 @@ import org.springframework.util.PathMatcher; @EnableConfigurationProperties(BusProperties.class) @AutoConfigureBefore(BindingServiceConfiguration.class) // so stream bindings work properly -@AutoConfigureAfter(LifecycleMvcEndpointAutoConfiguration.class) +@AutoConfigureAfter({ LifecycleMvcEndpointAutoConfiguration.class, + ServiceMatcherAutoConfiguration.class }) // so actuator endpoints have needed dependencies public class BusAutoConfiguration implements ApplicationEventPublisherAware { @@ -191,29 +189,6 @@ public class BusAutoConfiguration implements ApplicationEventPublisherAware { } } - @Configuration(proxyBeanMethods = false) - protected static class MatcherConfiguration { - - @BusPathMatcher - // There is a @Bean of type PathMatcher coming from Spring MVC - @ConditionalOnMissingBean(name = BusAutoConfiguration.BUS_PATH_MATCHER_NAME) - @Bean(name = BusAutoConfiguration.BUS_PATH_MATCHER_NAME) - public PathMatcher busPathMatcher() { - return new DefaultBusPathMatcher(new AntPathMatcher(":")); - } - - @Bean - public ServiceMatcher serviceMatcher(@BusPathMatcher PathMatcher pathMatcher, - BusProperties properties, Environment environment) { - String[] configNames = environment.getProperty(CLOUD_CONFIG_NAME_PROPERTY, - String[].class, new String[] {}); - ServiceMatcher serviceMatcher = new ServiceMatcher(pathMatcher, - properties.getId(), configNames); - return serviceMatcher; - } - - } - @Configuration(proxyBeanMethods = false) @ConditionalOnClass({ Endpoint.class }) @ConditionalOnBean(HttpTraceRepository.class) diff --git a/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/BusRefreshAutoConfiguration.java b/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/BusRefreshAutoConfiguration.java index 2f62759..3b76447 100644 --- a/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/BusRefreshAutoConfiguration.java +++ b/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/BusRefreshAutoConfiguration.java @@ -41,12 +41,12 @@ public class BusRefreshAutoConfiguration { @ConditionalOnProperty(value = "spring.cloud.bus.refresh.enabled", matchIfMissing = true) @ConditionalOnBean(ContextRefresher.class) - public RefreshListener refreshListener(ContextRefresher contextRefresher) { - return new RefreshListener(contextRefresher); + public RefreshListener refreshListener(ContextRefresher contextRefresher, + ServiceMatcher serviceMatcher) { + return new RefreshListener(contextRefresher, serviceMatcher); } @Configuration(proxyBeanMethods = false) - @ConditionalOnBean(ContextRefresher.class) @ConditionalOnClass( name = { "org.springframework.boot.actuate.endpoint.annotation.Endpoint", "org.springframework.cloud.context.scope.refresh.RefreshScope" }) diff --git a/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/ServiceMatcherAutoConfiguration.java b/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/ServiceMatcherAutoConfiguration.java new file mode 100644 index 0000000..f547f5e --- /dev/null +++ b/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/ServiceMatcherAutoConfiguration.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.bus; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.util.AntPathMatcher; +import org.springframework.util.PathMatcher; + +import static org.springframework.cloud.bus.BusAutoConfiguration.CLOUD_CONFIG_NAME_PROPERTY; + +/** + * @author Ryan Baxter + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnBusEnabled +@EnableConfigurationProperties(BusProperties.class) +public class ServiceMatcherAutoConfiguration { + + @BusPathMatcher + // There is a @Bean of type PathMatcher coming from Spring MVC + @ConditionalOnMissingBean(name = BusAutoConfiguration.BUS_PATH_MATCHER_NAME) + @Bean(name = BusAutoConfiguration.BUS_PATH_MATCHER_NAME) + public PathMatcher busPathMatcher() { + return new DefaultBusPathMatcher(new AntPathMatcher(":")); + } + + @Bean + public ServiceMatcher serviceMatcher(@BusPathMatcher PathMatcher pathMatcher, + BusProperties properties, Environment environment) { + String[] configNames = environment.getProperty(CLOUD_CONFIG_NAME_PROPERTY, + String[].class, new String[] {}); + ServiceMatcher serviceMatcher = new ServiceMatcher(pathMatcher, + properties.getId(), configNames); + return serviceMatcher; + } + +} diff --git a/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/event/RefreshListener.java b/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/event/RefreshListener.java index f561715..8b57292 100644 --- a/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/event/RefreshListener.java +++ b/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/event/RefreshListener.java @@ -21,11 +21,13 @@ import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.cloud.bus.ServiceMatcher; import org.springframework.cloud.context.refresh.ContextRefresher; import org.springframework.context.ApplicationListener; /** * @author Spencer Gibb + * @author Ryan Baxter */ public class RefreshListener implements ApplicationListener { @@ -34,14 +36,38 @@ public class RefreshListener private ContextRefresher contextRefresher; + private ServiceMatcher serviceMatcher; + + @Deprecated + // TODO Remove in 3.0.x public RefreshListener(ContextRefresher contextRefresher) { + this(contextRefresher, null); + } + + public RefreshListener(ContextRefresher contextRefresher, + ServiceMatcher serviceMatcher) { this.contextRefresher = contextRefresher; + this.serviceMatcher = serviceMatcher; } @Override public void onApplicationEvent(RefreshRemoteApplicationEvent event) { - Set keys = this.contextRefresher.refresh(); - log.info("Received remote refresh request. Keys refreshed " + keys); + log.info("Received remote refresh request."); + // TODO Remove this in 3.0.x + if (serviceMatcher == null) { + log.warn( + "RefreshListener does not have a ServiceMatcher, refresh will not be performed. Consider" + + "passing a ServiceMatcher in the constructor"); + return; + } + if (serviceMatcher.isForSelf(event)) { + Set keys = this.contextRefresher.refresh(); + log.info("Keys refreshed " + keys); + } + else { + log.info("Refresh not performed, the event was targetting " + + event.getDestinationService()); + } } } diff --git a/spring-cloud-bus/src/main/resources/META-INF/spring.factories b/spring-cloud-bus/src/main/resources/META-INF/spring.factories index 2f2d935..874dec2 100644 --- a/spring-cloud-bus/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-bus/src/main/resources/META-INF/spring.factories @@ -1,5 +1,6 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.cloud.bus.BusPropertiesAutoConfiguration,\ +org.springframework.cloud.bus.ServiceMatcherAutoConfiguration,\ org.springframework.cloud.bus.BusAutoConfiguration,\ org.springframework.cloud.bus.BusRefreshAutoConfiguration,\ org.springframework.cloud.bus.jackson.BusJacksonAutoConfiguration diff --git a/spring-cloud-bus/src/test/java/org/springframework/cloud/bus/BusAutoConfigurationClassPathTests.java b/spring-cloud-bus/src/test/java/org/springframework/cloud/bus/BusAutoConfigurationClassPathTests.java index 4536897..ea1fe6f 100644 --- a/spring-cloud-bus/src/test/java/org/springframework/cloud/bus/BusAutoConfigurationClassPathTests.java +++ b/spring-cloud-bus/src/test/java/org/springframework/cloud/bus/BusAutoConfigurationClassPathTests.java @@ -38,6 +38,7 @@ public class BusAutoConfigurationClassPathTests { public void refreshListenerCreatedWithoutActuator() { new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(RefreshAutoConfiguration.class, + ServiceMatcherAutoConfiguration.class, BusRefreshAutoConfiguration.class)) .run(context -> assertThat(context).hasSingleBean(RefreshListener.class) .doesNotHaveBean(RefreshBusEndpoint.class)); diff --git a/spring-cloud-bus/src/test/java/org/springframework/cloud/bus/RefreshListenerIntegrationTests.java b/spring-cloud-bus/src/test/java/org/springframework/cloud/bus/RefreshListenerIntegrationTests.java new file mode 100644 index 0000000..b75806d --- /dev/null +++ b/spring-cloud-bus/src/test/java/org/springframework/cloud/bus/RefreshListenerIntegrationTests.java @@ -0,0 +1,72 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.bus; + +import java.util.HashMap; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.cloud.context.refresh.ContextRefresher; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +/** + * @author Ryan Baxter + */ +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = RefreshListenerIntegrationTests.MyApp.class, + properties = { "management.endpoints.web.exposure.include=*", + "spring.application.name=foobar" }) +public class RefreshListenerIntegrationTests { + + @Autowired + private TestRestTemplate rest; + + @Autowired + private ApplicationEventPublisher context; + + @MockBean + ContextRefresher contextRefresher; + + @Test + public void testEndpoint() { + System.out.println(rest.getForObject("/actuator", String.class)); + assertThat(rest.postForEntity("/actuator/bus-refresh/demoapp", new HashMap<>(), + String.class).getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT); + assertThat(rest.postForEntity("/actuator/bus-refresh/foobar", new HashMap<>(), + String.class).getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT); + verify(contextRefresher, times(1)).refresh(); + } + + @SpringBootApplication + static class MyApp { + + } + +}