Merge remote-tracking branch 'origin/1.3.x'

This commit is contained in:
Ryan Baxter
2018-01-18 19:53:37 -05:00
5 changed files with 259 additions and 15 deletions

View File

@@ -458,7 +458,43 @@ public class MyConfiguration {
NOTE: `client` in the above examples should be replaced with your Ribbon client's
name.
==== Multiple RestTemplate objects
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`
bean and inject it as normal. To access the load balanced `RestTemplate` use

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

@@ -40,6 +40,7 @@ import java.util.List;
* @author Spencer Gibb
* @author Dave Syer
* @author Will Tran
* @author Gang Li
*/
@Configuration
@ConditionalOnClass(RestTemplate.class)
@@ -118,6 +119,12 @@ public class LoadBalancerAutoConfiguration {
public LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory() {
return new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory();
}
@Bean
@ConditionalOnMissingBean
public LoadBalancedRetryListenerFactory loadBalancedRetryListenerFactory() {
return new LoadBalancedRetryListenerFactory.DefaultRetryListenerFactory();
}
}
@Configuration
@@ -129,9 +136,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

@@ -1,5 +1,9 @@
package org.springframework.cloud.client.loadbalancer;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -12,17 +16,17 @@ 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 java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.mockito.ArgumentMatchers.nullable;
@@ -35,6 +39,7 @@ import static org.mockito.Mockito.when;
/**
* @author Ryan Baxter
* @author Gang Li
*/
@RunWith(MockitoJUnitRunner.class)
public class RetryLoadBalancerInterceptorTest {
@@ -43,6 +48,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 {
@@ -69,7 +75,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);
@@ -86,7 +92,7 @@ public class RetryLoadBalancerInterceptorTest {
LoadBalancedRetryPolicyFactory lbRetryPolicyFactory = mock(LoadBalancedRetryPolicyFactory.class);
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);
@@ -105,7 +111,7 @@ public class RetryLoadBalancerInterceptorTest {
when(this.lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class));
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);
@@ -127,7 +133,7 @@ public class RetryLoadBalancerInterceptorTest {
when(this.lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class));
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);
@@ -155,7 +161,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);
@@ -223,7 +229,7 @@ public class RetryLoadBalancerInterceptorTest {
when(this.lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class));
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);
@@ -248,13 +254,92 @@ public class RetryLoadBalancerInterceptorTest {
when(this.lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class));
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();
when(this.lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class));
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);
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);
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);
when(this.lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class));
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;
@@ -278,4 +363,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) {
}
}};
}
}
}