Adds ConfigServerInstanceProvider.Function interface.

This allows for another implementation besides DiscoveryClient.

Moves SmartApplicationListener from bootstrap configuration to HeartbeatListener class.
This commit is contained in:
spencergibb
2020-05-13 23:52:13 -04:00
parent 0492371c34
commit 235c50b4dc
4 changed files with 137 additions and 75 deletions

View File

@@ -34,16 +34,20 @@ public class ConfigServerInstanceProvider {
private static Log logger = LogFactory.getLog(ConfigServerInstanceProvider.class);
private final DiscoveryClient client;
private final Function function;
public ConfigServerInstanceProvider(DiscoveryClient client) {
this.client = client;
this.function = client::getInstances;
}
public ConfigServerInstanceProvider(Function function) {
this.function = function;
}
@Retryable(interceptor = "configServerRetryInterceptor")
public List<ServiceInstance> getConfigServerInstances(String serviceId) {
logger.debug("Locating configserver (" + serviceId + ") via discovery");
List<ServiceInstance> instances = this.client.getInstances(serviceId);
List<ServiceInstance> instances = this.function.apply(serviceId);
if (instances.isEmpty()) {
throw new IllegalStateException(
"No instances found of configserver (" + serviceId + ")");
@@ -53,4 +57,11 @@ public class ConfigServerInstanceProvider {
return instances;
}
@FunctionalInterface
public interface Function {
List<ServiceInstance> apply(String serviceId);
}
}

View File

@@ -22,6 +22,7 @@ import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.ServiceInstance;
@@ -48,100 +49,119 @@ import org.springframework.context.event.SmartApplicationListener;
@Configuration(proxyBeanMethods = false)
@Import({ UtilAutoConfiguration.class })
@EnableDiscoveryClient
public class DiscoveryClientConfigServiceBootstrapConfiguration
implements SmartApplicationListener {
public class DiscoveryClientConfigServiceBootstrapConfiguration {
private static Log logger = LogFactory
.getLog(DiscoveryClientConfigServiceBootstrapConfiguration.class);
@Autowired
private ConfigClientProperties config;
@Autowired
private ConfigServerInstanceProvider instanceProvider;
private HeartbeatMonitor monitor = new HeartbeatMonitor();
@Bean
public ConfigServerInstanceProvider configServerInstanceProvider(
DiscoveryClient discoveryClient) {
return new ConfigServerInstanceProvider(discoveryClient);
}
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return ContextRefreshedEvent.class.isAssignableFrom(eventType)
|| HeartbeatEvent.class.isAssignableFrom(eventType);
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ContextRefreshedEvent) {
startup((ContextRefreshedEvent) event);
ObjectProvider<ConfigServerInstanceProvider.Function> function,
ObjectProvider<DiscoveryClient> discoveryClient) {
ConfigServerInstanceProvider.Function fn = function.getIfAvailable();
if (fn != null) {
return new ConfigServerInstanceProvider(fn);
}
else if (event instanceof HeartbeatEvent) {
heartbeat((HeartbeatEvent) event);
DiscoveryClient client = discoveryClient.getIfAvailable();
if (client == null) {
throw new IllegalStateException(
"ConfigServerInstanceProvider reqiures a DiscoveryClient or Function");
}
return new ConfigServerInstanceProvider(client);
}
public void startup(ContextRefreshedEvent event) {
refresh();
@Bean
public SmartApplicationListener heartbeatListener(
ConfigServerInstanceProvider provider) {
return new HeartbeatListener();
}
public void heartbeat(HeartbeatEvent event) {
if (this.monitor.update(event.getValue())) {
private static class HeartbeatListener implements SmartApplicationListener {
@Autowired
private ConfigClientProperties config;
@Autowired
private ConfigServerInstanceProvider instanceProvider;
private HeartbeatMonitor monitor = new HeartbeatMonitor();
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return ContextRefreshedEvent.class.isAssignableFrom(eventType)
|| HeartbeatEvent.class.isAssignableFrom(eventType);
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ContextRefreshedEvent) {
startup((ContextRefreshedEvent) event);
}
else if (event instanceof HeartbeatEvent) {
heartbeat((HeartbeatEvent) event);
}
}
public void startup(ContextRefreshedEvent event) {
refresh();
}
}
private void refresh() {
try {
String serviceId = this.config.getDiscovery().getServiceId();
List<String> listOfUrls = new ArrayList<>();
List<ServiceInstance> serviceInstances = this.instanceProvider
.getConfigServerInstances(serviceId);
public void heartbeat(HeartbeatEvent event) {
if (this.monitor.update(event.getValue())) {
refresh();
}
}
for (int i = 0; i < serviceInstances.size(); i++) {
private void refresh() {
try {
String serviceId = this.config.getDiscovery().getServiceId();
List<String> listOfUrls = new ArrayList<>();
List<ServiceInstance> serviceInstances = this.instanceProvider
.getConfigServerInstances(serviceId);
ServiceInstance server = serviceInstances.get(i);
String url = getHomePage(server);
for (int i = 0; i < serviceInstances.size(); i++) {
if (server.getMetadata().containsKey("password")) {
String user = server.getMetadata().get("user");
user = user == null ? "user" : user;
this.config.setUsername(user);
String password = server.getMetadata().get("password");
this.config.setPassword(password);
}
ServiceInstance server = serviceInstances.get(i);
String url = getHomePage(server);
if (server.getMetadata().containsKey("configPath")) {
String path = server.getMetadata().get("configPath");
if (url.endsWith("/") && path.startsWith("/")) {
url = url.substring(0, url.length() - 1);
if (server.getMetadata().containsKey("password")) {
String user = server.getMetadata().get("user");
user = user == null ? "user" : user;
this.config.setUsername(user);
String password = server.getMetadata().get("password");
this.config.setPassword(password);
}
url = url + path;
if (server.getMetadata().containsKey("configPath")) {
String path = server.getMetadata().get("configPath");
if (url.endsWith("/") && path.startsWith("/")) {
url = url.substring(0, url.length() - 1);
}
url = url + path;
}
listOfUrls.add(url);
}
listOfUrls.add(url);
}
String[] uri = new String[listOfUrls.size()];
uri = listOfUrls.toArray(uri);
this.config.setUri(uri);
String[] uri = new String[listOfUrls.size()];
uri = listOfUrls.toArray(uri);
this.config.setUri(uri);
}
catch (Exception ex) {
if (this.config.isFailFast()) {
throw ex;
}
else {
logger.warn("Could not locate configserver via discovery", ex);
catch (Exception ex) {
if (this.config.isFailFast()) {
throw ex;
}
else {
logger.warn("Could not locate configserver via discovery", ex);
}
}
}
}
private String getHomePage(ServiceInstance server) {
return server.getUri().toString() + "/";
private String getHomePage(ServiceInstance server) {
return server.getUri().toString() + "/";
}
}
}

View File

@@ -65,7 +65,7 @@ public abstract class BaseDiscoveryClientConfigServiceBootstrapConfigurationTest
void givenDiscoveryClientReturnsInfo() {
given(this.client.getInstances(DEFAULT_CONFIG_SERVER))
.willReturn(Arrays.asList(this.info));
.willReturn(Collections.singletonList(this.info));
}
void givenDiscoveryClientReturnsInfoForMultipleInstances(ServiceInstance info1,
@@ -78,7 +78,7 @@ public abstract class BaseDiscoveryClientConfigServiceBootstrapConfigurationTest
given(this.client.getInstances(DEFAULT_CONFIG_SERVER))
.willReturn(Collections.<ServiceInstance>emptyList())
.willReturn(Collections.<ServiceInstance>emptyList())
.willReturn(Arrays.asList(this.info));
.willReturn(Collections.singletonList(this.info));
}
void expectNoInstancesOfConfigServerException() {
@@ -128,16 +128,24 @@ public abstract class BaseDiscoveryClientConfigServiceBootstrapConfigurationTest
}
void setup(String... env) {
setup(true, true, env);
}
void setup(boolean refresh, boolean registerDiscoveryClient, String... env) {
this.context = new AnnotationConfigApplicationContext();
TestPropertyValues.of(env).applyTo(this.context);
TestPropertyValues.of("eureka.client.enabled=false").applyTo(this.context);
this.context.getDefaultListableBeanFactory().registerSingleton("discoveryClient",
this.client);
if (registerDiscoveryClient) {
this.context.getDefaultListableBeanFactory()
.registerSingleton("discoveryClient", this.client);
}
this.context.register(UtilAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class,
DiscoveryClientConfigServiceBootstrapConfiguration.class,
ConfigServiceBootstrapConfiguration.class, ConfigClientProperties.class);
this.context.refresh();
if (refresh) {
this.context.refresh();
}
}
}

View File

@@ -16,6 +16,8 @@
package org.springframework.cloud.config.client;
import java.util.Collections;
import org.junit.Test;
import org.springframework.cloud.client.DefaultServiceInstance;
@@ -26,6 +28,10 @@ import org.springframework.cloud.config.client.ConfigClientProperties.Credential
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.springframework.cloud.config.client.ConfigClientProperties.Discovery.DEFAULT_CONFIG_SERVER;
/**
* @author Dave Syer
@@ -56,6 +62,23 @@ public class DiscoveryClientConfigServiceBootstrapConfigurationTests
expectConfigClientPropertiesHasConfigurationFromEureka();
}
@Test
public void configServerInstanceProviderFunction() {
ConfigServerInstanceProvider.Function function = mock(
ConfigServerInstanceProvider.Function.class);
given(function.apply(DEFAULT_CONFIG_SERVER))
.willReturn(Collections.singletonList(this.info));
setup(false, false, "spring.cloud.config.discovery.enabled=true");
this.context.getDefaultListableBeanFactory().registerSingleton("myFunction",
function);
this.context.refresh();
expectDiscoveryClientConfigServiceBootstrapConfigurationIsSetup();
verify(function).apply(DEFAULT_CONFIG_SERVER);
expectConfigClientPropertiesHasConfigurationFromEureka();
}
@Test
public void onWhenHeartbeat() throws Exception {
setup("spring.cloud.config.discovery.enabled=true");