Merge pull request #221 from ryanjbaxter/refresh-when-targeted

Refresh Context Only If Event Targets App.  Fixes #68
This commit is contained in:
Ryan Baxter
2020-03-18 17:13:46 -04:00
committed by GitHub
7 changed files with 166 additions and 31 deletions

View File

@@ -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)

View File

@@ -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" })

View File

@@ -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;
}
}

View File

@@ -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<RefreshRemoteApplicationEvent> {
@@ -34,14 +36,43 @@ 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) {
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 may be performed even if "
+ "the event does not target this app. Consider passing a ServiceMatcher in "
+ "the constructor");
refresh();
}
else if (serviceMatcher.isForSelf(event)) {
refresh();
}
else {
log.info("Refresh not performed, the event was targetting "
+ event.getDestinationService());
}
}
private void refresh() {
Set<String> keys = this.contextRefresher.refresh();
log.info("Received remote refresh request. Keys refreshed " + keys);
log.info("Keys refreshed " + keys);
}
}

View File

@@ -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

View File

@@ -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));

View File

@@ -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 {
}
}