Supplement spring-retry listener features (#298)

* Supplement spring-retry listener features
This commit is contained in:
saga
2017-12-22 23:32:39 +08:00
committed by Ryan Baxter
parent fb76beb3f2
commit f54e4c8cc6
5 changed files with 255 additions and 13 deletions

View File

@@ -428,6 +428,42 @@ public class MyConfiguration {
NOTE: `client` in the above examples should be replaced with your Ribbon client's
name.
If you want to add one or more `RetryListener` to your retry you will need to
create a bean of type `LoadBalancedRetryListenerFactory` and return the `RetryListener` array
you would like to use for a given service.
[source,java,indent=0]
----
@Configuration
public class MyConfiguration {
@Bean
LoadBalancedRetryListenerFactory retryListenerFactory() {
return new LoadBalancedRetryListenerFactory() {
@Override
public RetryListener[] createRetryListeners(String service) {
return new RetryListener[]{new RetryListener() {
@Override
public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
//TODO Do you business...
return true;
}
@Override
public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
//TODO Do you business...
}
@Override
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
//TODO Do you business...
}
}};
}
};
}
}
----
=== Multiple RestTemplate objects
If you want a `RestTemplate` that is not load balanced, create a `RestTemplate`

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2013-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.client.loadbalancer;
import org.springframework.retry.RetryListener;
/**
* Factory class to return the retry listeners.
* @author Gang Li
*/
public interface LoadBalancedRetryListenerFactory {
RetryListener[] createRetryListeners(String service);
class DefaultRetryListenerFactory implements LoadBalancedRetryListenerFactory {
@Override
public RetryListener[] createRetryListeners(String service) {
return new RetryListener[0];
}
}
}

View File

@@ -39,6 +39,7 @@ import org.springframework.web.client.RestTemplate;
* @author Spencer Gibb
* @author Dave Syer
* @author Will Tran
* @author Gang Li
*/
@Configuration
@ConditionalOnClass(RestTemplate.class)
@@ -123,6 +124,12 @@ public class LoadBalancerAutoConfiguration {
public LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory() {
return new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory();
}
@Bean
@ConditionalOnMissingBean
public LoadBalancedRetryListenerFactory loadBalancedRetryListenerFactory() {
return new LoadBalancedRetryListenerFactory.DefaultRetryListenerFactory();
}
}
@Configuration
@@ -134,9 +141,10 @@ public class LoadBalancerAutoConfiguration {
LoadBalancerClient loadBalancerClient, LoadBalancerRetryProperties properties,
LoadBalancedRetryPolicyFactory lbRetryPolicyFactory,
LoadBalancerRequestFactory requestFactory,
LoadBalancedBackOffPolicyFactory backOffPolicyFactory) {
LoadBalancedBackOffPolicyFactory backOffPolicyFactory,
LoadBalancedRetryListenerFactory retryListenerFactory) {
return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
lbRetryPolicyFactory, requestFactory, backOffPolicyFactory);
lbRetryPolicyFactory, requestFactory, backOffPolicyFactory, retryListenerFactory);
}
@Bean

View File

@@ -33,6 +33,7 @@ import org.springframework.retry.RecoveryCallback;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryException;
import org.springframework.retry.RetryListener;
import org.springframework.retry.backoff.BackOffPolicy;
import org.springframework.retry.backoff.NoBackOffPolicy;
import org.springframework.retry.policy.NeverRetryPolicy;
@@ -42,6 +43,7 @@ import org.springframework.util.Assert;
/**
* @author Ryan Baxter
* @author Will Tran
* @author Gang Li
*/
public class RetryLoadBalancerInterceptor implements ClientHttpRequestInterceptor {
@@ -51,6 +53,7 @@ public class RetryLoadBalancerInterceptor implements ClientHttpRequestIntercepto
private LoadBalancerRetryProperties lbProperties;
private LoadBalancerRequestFactory requestFactory;
private LoadBalancedBackOffPolicyFactory backOffPolicyFactory;
private LoadBalancedRetryListenerFactory retryListenerFactory;
@Deprecated
@@ -64,8 +67,11 @@ public class RetryLoadBalancerInterceptor implements ClientHttpRequestIntercepto
this.lbProperties = lbProperties;
this.requestFactory = requestFactory;
this.backOffPolicyFactory = new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory();
this.retryListenerFactory = new LoadBalancedRetryListenerFactory.DefaultRetryListenerFactory();
}
@Deprecated
//TODO remove in 2.0.x
public RetryLoadBalancerInterceptor(LoadBalancerClient loadBalancer,
LoadBalancerRetryProperties lbProperties,
LoadBalancedRetryPolicyFactory lbRetryPolicyFactory,
@@ -76,8 +82,24 @@ public class RetryLoadBalancerInterceptor implements ClientHttpRequestIntercepto
this.lbProperties = lbProperties;
this.requestFactory = requestFactory;
this.backOffPolicyFactory = backOffPolicyFactory;
this.retryListenerFactory = new LoadBalancedRetryListenerFactory.DefaultRetryListenerFactory();;
}
public RetryLoadBalancerInterceptor(LoadBalancerClient loadBalancer,
LoadBalancerRetryProperties lbProperties,
LoadBalancedRetryPolicyFactory lbRetryPolicyFactory,
LoadBalancerRequestFactory requestFactory,
LoadBalancedBackOffPolicyFactory backOffPolicyFactory,
LoadBalancedRetryListenerFactory retryListenerFactory) {
this.loadBalancer = loadBalancer;
this.lbRetryPolicyFactory = lbRetryPolicyFactory;
this.lbProperties = lbProperties;
this.requestFactory = requestFactory;
this.backOffPolicyFactory = backOffPolicyFactory;
this.retryListenerFactory = retryListenerFactory;
}
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
@@ -90,6 +112,10 @@ public class RetryLoadBalancerInterceptor implements ClientHttpRequestIntercepto
BackOffPolicy backOffPolicy = backOffPolicyFactory.createBackOffPolicy(serviceName);
template.setBackOffPolicy(backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy);
template.setThrowLastExceptionOnExhausted(true);
RetryListener[] retryListeners = this.retryListenerFactory.createRetryListeners(serviceName);
if (retryListeners != null && retryListeners.length != 0) {
template.setListeners(retryListeners);
}
template.setRetryPolicy(
!lbProperties.isEnabled() || retryPolicy == null ? new NeverRetryPolicy()
: new InterceptorRetryPolicy(request, retryPolicy, loadBalancer,

View File

@@ -15,27 +15,29 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.mock.http.client.MockClientHttpResponse;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryException;
import org.springframework.retry.RetryListener;
import org.springframework.retry.TerminatedRetryException;
import org.springframework.retry.backoff.BackOffContext;
import org.springframework.retry.backoff.BackOffInterruptedException;
import org.springframework.retry.backoff.BackOffPolicy;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
import org.springframework.retry.policy.NeverRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* @author Ryan Baxter
* @author Gang Li
*/
@RunWith(MockitoJUnitRunner.class)
public class RetryLoadBalancerInterceptorTest {
@@ -44,6 +46,7 @@ public class RetryLoadBalancerInterceptorTest {
private LoadBalancerRetryProperties lbProperties;
private LoadBalancerRequestFactory lbRequestFactory;
private LoadBalancedBackOffPolicyFactory backOffPolicyFactory = new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory();
private LoadBalancedRetryListenerFactory retryListenerFactory = new LoadBalancedRetryListenerFactory.DefaultRetryListenerFactory();
@Before
public void setUp() throws Exception {
@@ -71,7 +74,7 @@ public class RetryLoadBalancerInterceptorTest {
when(client.execute(eq("foo"), eq(serviceInstance), any(LoadBalancerRequest.class))).thenThrow(new IOException());
lbProperties.setEnabled(false);
RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, lbProperties, lbRetryPolicyFactory,
lbRequestFactory, backOffPolicyFactory);
lbRequestFactory, backOffPolicyFactory, retryListenerFactory);
byte[] body = new byte[]{};
ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class);
interceptor.intercept(request, body, execution);
@@ -92,7 +95,7 @@ public class RetryLoadBalancerInterceptorTest {
when(client.execute(eq("foo_underscore"), eq(serviceInstance), any(LoadBalancerRequest.class))).thenReturn(clientHttpResponse);
lbProperties.setEnabled(true);
RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, lbProperties, lbRetryPolicyFactory, lbRequestFactory,
backOffPolicyFactory);
backOffPolicyFactory, retryListenerFactory);
byte[] body = new byte[]{};
ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class);
ClientHttpResponse rsp = interceptor.intercept(request, body, execution);
@@ -110,7 +113,7 @@ public class RetryLoadBalancerInterceptorTest {
when(client.execute(eq("foo"), eq(serviceInstance), any(LoadBalancerRequest.class))).thenReturn(clientHttpResponse);
lbProperties.setEnabled(true);
RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, lbProperties, lbRetryPolicyFactory,
lbRequestFactory, backOffPolicyFactory);
lbRequestFactory, backOffPolicyFactory, retryListenerFactory);
byte[] body = new byte[]{};
ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class);
interceptor.intercept(request, body, execution);
@@ -131,7 +134,7 @@ public class RetryLoadBalancerInterceptorTest {
when(client.execute(eq("foo"), eq(serviceInstance), any(LoadBalancerRequest.class))).thenReturn(clientHttpResponse);
lbProperties.setEnabled(true);
RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, lbProperties, lbRetryPolicyFactory,
lbRequestFactory, backOffPolicyFactory);
lbRequestFactory, backOffPolicyFactory, retryListenerFactory);
byte[] body = new byte[]{};
ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class);
ClientHttpResponse rsp = interceptor.intercept(request, body, execution);
@@ -159,7 +162,7 @@ public class RetryLoadBalancerInterceptorTest {
thenReturn(clientHttpResponseNotFound).thenReturn(clientHttpResponseOk);
lbProperties.setEnabled(true);
RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, lbProperties, lbRetryPolicyFactory,
lbRequestFactory, backOffPolicyFactory);
lbRequestFactory, backOffPolicyFactory, retryListenerFactory);
byte[] body = new byte[]{};
ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class);
ClientHttpResponse rsp = interceptor.intercept(request, body, execution);
@@ -216,7 +219,7 @@ public class RetryLoadBalancerInterceptorTest {
when(client.execute(eq("foo"), eq(serviceInstance), any(LoadBalancerRequest.class))).thenThrow(new IOException()).thenReturn(clientHttpResponse);
lbProperties.setEnabled(true);
RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, lbProperties, lbRetryPolicyFactory, lbRequestFactory,
backOffPolicyFactory);
backOffPolicyFactory, retryListenerFactory);
byte[] body = new byte[]{};
ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class);
ClientHttpResponse rsp = interceptor.intercept(request, body, execution);
@@ -241,13 +244,93 @@ public class RetryLoadBalancerInterceptorTest {
when(client.execute(eq("foo"), eq(serviceInstance), any(LoadBalancerRequest.class))).thenThrow(new IOException()).thenReturn(clientHttpResponse);
lbProperties.setEnabled(true);
RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, lbProperties, lbRetryPolicyFactory,
lbRequestFactory, backOffPolicyFactory);
lbRequestFactory, backOffPolicyFactory, retryListenerFactory);
byte[] body = new byte[]{};
ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class);
ClientHttpResponse rsp = interceptor.intercept(request, body, execution);
verify(lbRequestFactory).createRequest(request, body, execution);
}
@Test
public void retryListenerTest() throws Throwable {
HttpRequest request = mock(HttpRequest.class);
when(request.getURI()).thenReturn(new URI("http://listener"));
ClientHttpResponse clientHttpResponse = new MockClientHttpResponse(new byte[]{}, HttpStatus.OK);
LoadBalancedRetryPolicy policy = mock(LoadBalancedRetryPolicy.class);
when(policy.canRetryNextServer(any(LoadBalancedRetryContext.class))).thenReturn(true);
LoadBalancedRetryPolicyFactory lbRetryPolicyFactory = mock(LoadBalancedRetryPolicyFactory.class);
when(lbRetryPolicyFactory.create(eq("listener"), any(ServiceInstanceChooser.class))).thenReturn(policy);
LoadBalancedBackOffPolicyFactory backOffPolicyFactory = mock(LoadBalancedBackOffPolicyFactory.class);
MyBackOffPolicy backOffPolicy = new MyBackOffPolicy();
when(backOffPolicyFactory.createBackOffPolicy(eq("listener"))).thenReturn(backOffPolicy);
ServiceInstance serviceInstance = mock(ServiceInstance.class);
when(client.choose(eq("listener"))).thenReturn(serviceInstance);
when(client.execute(eq("listener"), eq(serviceInstance), any(LoadBalancerRequest.class))).thenThrow(new IOException()).thenReturn(clientHttpResponse);
lbProperties.setEnabled(true);
MyRetryListeners retryListeners = new MyRetryListeners();
RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, lbProperties, lbRetryPolicyFactory, lbRequestFactory,
backOffPolicyFactory, retryListeners);
byte[] body = new byte[]{};
ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class);
ClientHttpResponse rsp = interceptor.intercept(request, body, execution);
verify(client, times(2)).execute(eq("listener"), eq(serviceInstance), any(LoadBalancerRequest.class));
assertThat(rsp, is(clientHttpResponse));
verify(lbRequestFactory, times(2)).createRequest(request, body, execution);
assertThat(backOffPolicy.getBackoffAttempts(), is(1));
assertThat(retryListeners.getOnError(), is(1));
}
@Test(expected = TerminatedRetryException.class)
public void retryListenerTestNoRetry() throws Throwable {
HttpRequest request = mock(HttpRequest.class);
when(request.getURI()).thenReturn(new URI("http://noRetry"));
ClientHttpResponse clientHttpResponse = new MockClientHttpResponse(new byte[]{}, HttpStatus.OK);
LoadBalancedRetryPolicy policy = mock(LoadBalancedRetryPolicy.class);
when(policy.canRetryNextServer(any(LoadBalancedRetryContext.class))).thenReturn(true);
LoadBalancedRetryPolicyFactory lbRetryPolicyFactory = mock(LoadBalancedRetryPolicyFactory.class);
when(lbRetryPolicyFactory.create(eq("noRetry"), any(ServiceInstanceChooser.class))).thenReturn(policy);
LoadBalancedBackOffPolicyFactory backOffPolicyFactory = mock(LoadBalancedBackOffPolicyFactory.class);
MyBackOffPolicy backOffPolicy = new MyBackOffPolicy();
when(backOffPolicyFactory.createBackOffPolicy(eq("noRetry"))).thenReturn(backOffPolicy);
ServiceInstance serviceInstance = mock(ServiceInstance.class);
when(client.choose(eq("noRetry"))).thenReturn(serviceInstance);
when(client.execute(eq("noRetry"), eq(serviceInstance), any(LoadBalancerRequest.class))).thenThrow(new IOException()).thenReturn(clientHttpResponse);
lbProperties.setEnabled(true);
MyRetryListenersNotRetry retryListeners = new MyRetryListenersNotRetry();
RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, lbProperties, lbRetryPolicyFactory, lbRequestFactory,
backOffPolicyFactory, retryListeners);
byte[] body = new byte[]{};
ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class);
ClientHttpResponse rsp = interceptor.intercept(request, body, execution);
}
@Test
public void retryWithDefaultConstructorTest() throws Throwable {
HttpRequest request = mock(HttpRequest.class);
when(request.getURI()).thenReturn(new URI("http://default"));
ClientHttpResponse clientHttpResponse = new MockClientHttpResponse(new byte[]{}, HttpStatus.OK);
LoadBalancedRetryPolicy policy = mock(LoadBalancedRetryPolicy.class);
when(policy.canRetryNextServer(any(LoadBalancedRetryContext.class))).thenReturn(true);
LoadBalancedRetryPolicyFactory lbRetryPolicyFactory = mock(LoadBalancedRetryPolicyFactory.class);
when(lbRetryPolicyFactory.create(eq("default"), any(ServiceInstanceChooser.class))).thenReturn(policy);
LoadBalancedBackOffPolicyFactory backOffPolicyFactory = mock(LoadBalancedBackOffPolicyFactory.class);
MyBackOffPolicy backOffPolicy = new MyBackOffPolicy();
when(backOffPolicyFactory.createBackOffPolicy(eq("default"))).thenReturn(backOffPolicy);
ServiceInstance serviceInstance = mock(ServiceInstance.class);
when(client.choose(eq("default"))).thenReturn(serviceInstance);
when(client.execute(eq("default"), eq(serviceInstance), any(LoadBalancerRequest.class))).thenThrow(new IOException()).thenReturn(clientHttpResponse);
lbProperties.setEnabled(true);
RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, lbProperties, lbRetryPolicyFactory, lbRequestFactory,
backOffPolicyFactory);
byte[] body = new byte[]{};
ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class);
ClientHttpResponse rsp = interceptor.intercept(request, body, execution);
verify(client, times(2)).execute(eq("default"), eq(serviceInstance), any(LoadBalancerRequest.class));
assertThat(rsp, is(clientHttpResponse));
verify(lbRequestFactory, times(2)).createRequest(request, body, execution);
assertThat(backOffPolicy.getBackoffAttempts(), is(1));
}
class MyBackOffPolicy implements BackOffPolicy {
private int backoffAttempts = 0;
@@ -271,4 +354,56 @@ public class RetryLoadBalancerInterceptorTest {
return backoffAttempts;
}
}
class MyRetryListeners implements LoadBalancedRetryListenerFactory {
private int onError = 0;
@Override
public RetryListener[] createRetryListeners(String service) {
return new RetryListener[] {new RetryListener() {
@Override
public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
return true;
}
@Override
public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
}
@Override
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
onError++;
}
}};
}
public int getOnError() {
return onError;
}
}
class MyRetryListenersNotRetry implements LoadBalancedRetryListenerFactory {
@Override
public RetryListener[] createRetryListeners(String service) {
return new RetryListener[] {new RetryListener() {
@Override
public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
return false;
}
@Override
public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
}
@Override
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
}
}};
}
}
}