From 5f379c0a62aeb114b604990756e8ad18eafa47c7 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Tue, 21 Apr 2015 08:20:07 +0200 Subject: [PATCH] Add support for retry in config client If Spring Retry is on the classpath (and AspectJ) the config client will create a proxy for the ConfigServerPropertySourceLocator that retries a failed environment fetch. In this way the client can be made to wait for the config server to come up, even if failFast=true. (If failFast=false, the default, then it has no effect.) Fixes gh-90, fixes gh-129 --- pom.xml | 5 ++ spring-cloud-config-client/pom.xml | 10 +++ .../client/ConfigClientAutoConfiguration.java | 2 +- .../ConfigServiceBootstrapConfiguration.java | 28 +++++++ .../ConfigServicePropertySourceLocator.java | 2 + .../cloud/config/client/RetryProperties.java | 77 +++++++++++++++++++ 6 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/RetryProperties.java diff --git a/pom.xml b/pom.xml index 744eb3d9..2e1dd57d 100644 --- a/pom.xml +++ b/pom.xml @@ -41,6 +41,11 @@ spring-cloud-config-server 1.0.1.BUILD-SNAPSHOT + + org.springframework.retry + spring-retry + 1.1.2.RELEASE + org.eclipse.jgit org.eclipse.jgit diff --git a/spring-cloud-config-client/pom.xml b/spring-cloud-config-client/pom.xml index 53efee7d..f21c57a8 100644 --- a/spring-cloud-config-client/pom.xml +++ b/spring-cloud-config-client/pom.xml @@ -43,11 +43,21 @@ org.springframework spring-web + + org.springframework.retry + spring-retry + true + org.springframework.boot spring-boot-starter-actuator true + + org.springframework.boot + spring-boot-starter-aop + true + org.springframework.boot spring-boot-starter-test diff --git a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientAutoConfiguration.java b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientAutoConfiguration.java index 07489935..b6842588 100644 --- a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientAutoConfiguration.java +++ b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientAutoConfiguration.java @@ -62,5 +62,5 @@ public class ConfigClientAutoConfiguration { return new ConfigServerHealthIndicator(locator); } } - + } diff --git a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServiceBootstrapConfiguration.java b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServiceBootstrapConfiguration.java index a064fbf0..8ea5d5e6 100644 --- a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServiceBootstrapConfiguration.java +++ b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServiceBootstrapConfiguration.java @@ -16,12 +16,21 @@ package org.springframework.cloud.config.client; +import org.aspectj.lang.annotation.Aspect; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.aop.AopAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; 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.retry.annotation.EnableRetry; +import org.springframework.retry.annotation.Retryable; +import org.springframework.retry.interceptor.RetryInterceptorBuilder; +import org.springframework.retry.interceptor.RetryOperationsInterceptor; /** * @author Dave Syer @@ -48,4 +57,23 @@ public class ConfigServiceBootstrapConfiguration { return locator; } + @ConditionalOnClass({ Retryable.class, Aspect.class, AopAutoConfiguration.class }) + @Configuration + @EnableRetry(proxyTargetClass = true) + @Import(AopAutoConfiguration.class) + @EnableConfigurationProperties(RetryProperties.class) + protected static class RetryConfiguration { + + @Bean + @ConditionalOnMissingBean(name = "configServerRetryInterceptor") + public RetryOperationsInterceptor configServerRetryInterceptor( + RetryProperties properties) { + return RetryInterceptorBuilder + .stateless() + .backOffOptions(properties.getInitialInterval(), + properties.getMultiplier(), properties.getMaxInterval()) + .maxAttempts(properties.getMaxAttempts()).build(); + } + } + } diff --git a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServicePropertySourceLocator.java b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServicePropertySourceLocator.java index 75f3807e..87a42a4d 100644 --- a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServicePropertySourceLocator.java +++ b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServicePropertySourceLocator.java @@ -35,6 +35,7 @@ import org.springframework.http.MediaType; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; +import org.springframework.retry.annotation.Retryable; import org.springframework.util.StringUtils; import org.springframework.web.client.HttpServerErrorException; import org.springframework.web.client.RestTemplate; @@ -57,6 +58,7 @@ public class ConfigServicePropertySourceLocator implements PropertySourceLocator } @Override + @Retryable(interceptor="configServerRetryInterceptor") public org.springframework.core.env.PropertySource locate( org.springframework.core.env.Environment environment) { ConfigClientProperties client = defaults.override(environment); diff --git a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/RetryProperties.java b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/RetryProperties.java new file mode 100644 index 00000000..a29d7047 --- /dev/null +++ b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/RetryProperties.java @@ -0,0 +1,77 @@ +/* + * Copyright 2014-2015 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.config.client; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * @author Dave Syer + * + */ +@ConfigurationProperties("spring.config.retry") +public class RetryProperties { + + /** + * Initial retry interval in milliseconds. + */ + long initialInterval = 1000; + /** + * Multiplier for next interval. + */ + double multiplier = 1.1; + /** + * Maximum interval for backoff. + */ + long maxInterval = 2000; + /** + * Maximum number of attempts. + */ + int maxAttempts = 6; + + public long getInitialInterval() { + return this.initialInterval; + } + + public void setInitialInterval(long initialInterval) { + this.initialInterval = initialInterval; + } + + public double getMultiplier() { + return this.multiplier; + } + + public void setMultiplier(double multiplier) { + this.multiplier = multiplier; + } + + public long getMaxInterval() { + return this.maxInterval; + } + + public void setMaxInterval(long maxInterval) { + this.maxInterval = maxInterval; + } + + public int getMaxAttempts() { + return this.maxAttempts; + } + + public void setMaxAttempts(int maxAttempts) { + this.maxAttempts = maxAttempts; + } + +}