Change the strategy for key generation in stateful retry

There were a couple of issues to fix here. The first was that only
the method arguments and not the method signature (or label) were
being used in the key generator. Plus the arguments were being
used as an array, which has a different hashcode on each invocation
(a Collection would be better). Plus the interceptor builder
didn't set the key generator in a circuit breaker, so all the method
calls with different args are unique and they are supposed to be the
same.

See gh-49
This commit is contained in:
Dave Syer
2016-08-23 12:06:44 +01:00
parent 08cb981f58
commit 411d47d7b2
4 changed files with 30 additions and 20 deletions

View File

@@ -41,6 +41,7 @@ import org.springframework.retry.backoff.FixedBackOffPolicy;
import org.springframework.retry.backoff.NoBackOffPolicy;
import org.springframework.retry.backoff.Sleeper;
import org.springframework.retry.backoff.UniformRandomBackOffPolicy;
import org.springframework.retry.interceptor.FixedKeyGenerator;
import org.springframework.retry.interceptor.MethodArgumentsKeyGenerator;
import org.springframework.retry.interceptor.MethodInvocationRecoverer;
import org.springframework.retry.interceptor.NewMethodArgumentsIdentifier;
@@ -209,6 +210,7 @@ public class AnnotationAwareRetryOperationsInterceptor implements IntroductionIn
label = method.toGenericString();
}
return RetryInterceptorBuilder.circuitBreaker()
.keyGenerator(new FixedKeyGenerator("circuit"))
.retryOperations(template)
.recoverer(getRecoverer(target, method))
.label(label)

View File

@@ -347,6 +347,11 @@ public abstract class RetryInterceptorBuilder<T extends MethodInterceptor> {
return this;
}
public CircuitBreakerInterceptorBuilder keyGenerator(MethodArgumentsKeyGenerator keyGenerator) {
this.keyGenerator = keyGenerator;
return this;
}
@Override
public CircuitBreakerInterceptorBuilder recoverer(
MethodInvocationRecoverer<?> recoverer) {

View File

@@ -91,8 +91,8 @@ public class StatefulRetryOperationsInterceptor implements MethodInterceptor {
}
/**
* Rollback classifier for the retry state. Default to null (meaning rollback
* for all).
* Rollback classifier for the retry state. Default to null (meaning rollback for
* all).
*
* @param rollbackClassifier the rollbackClassifier to set
*/
@@ -141,24 +141,26 @@ public class StatefulRetryOperationsInterceptor implements MethodInterceptor {
+ ObjectUtils.getIdentityHexString(invocation) + ")");
}
Object[] args = invocation.getArguments();
Object arg = args;
if (args.length == 1) {
arg = args[0];
}
final Object item = arg;
String name = invocation.getMethod().toGenericString();
Object key = this.keyGenerator != null ? this.keyGenerator.getKey(args) : item;
RetryState retryState = new DefaultRetryState(
key,
Object[] args = invocation.getArguments();
Object defaultKey = Arrays.asList(args);
if (args.length == 1) {
defaultKey = args[0];
}
Object key = Arrays.asList(name, this.keyGenerator != null
? this.keyGenerator.getKey(invocation.getArguments()) : defaultKey );
RetryState retryState = new DefaultRetryState(key,
this.newMethodArgumentsIdentifier != null
&& this.newMethodArgumentsIdentifier.isNew(args),
this.rollbackClassifier);
Object result = this.retryOperations.execute(
new MethodInvocationRetryCallback(invocation, label), this.recoverer != null
? new ItemRecovererCallback(args, this.recoverer) : null,
retryState);
Object result = this.retryOperations
.execute(new MethodInvocationRetryCallback(invocation, label),
this.recoverer != null
? new ItemRecovererCallback(args, this.recoverer) : null,
retryState);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Exiting proxied method in stateful retry with result: ("
@@ -181,9 +183,10 @@ public class StatefulRetryOperationsInterceptor implements MethodInterceptor {
private MethodInvocationRetryCallback(MethodInvocation invocation, String label) {
this.invocation = invocation;
if (label!=null) {
if (label != null) {
this.label = label;
} else {
}
else {
this.label = invocation.getMethod().toGenericString();
}
}

View File

@@ -60,12 +60,12 @@ public class CircuitBreakerInterceptorStatisticsTests {
@Test
public void testCircuitOpenWhenNotRetryable() throws Throwable {
Object result = callback.service();
Object result = callback.service("one");
RetryStatistics stats = repository.findOne("test");
// System.err.println(stats);
assertEquals(1, stats.getStartedCount());
assertEquals(RECOVERED, result);
result = callback.service();
result = callback.service("two");
assertEquals(RECOVERED, result);
assertEquals("There should be two recoveries", 2, stats.getRecoveryCount());
assertEquals("There should only be one error because the circuit is now open", 1,
@@ -101,7 +101,7 @@ public class CircuitBreakerInterceptorStatisticsTests {
private RetryContext status;
@CircuitBreaker(label = "test", maxAttempts = 1)
public Object service() throws Exception {
public Object service(String input) throws Exception {
this.status = RetrySynchronizationManager.getContext();
Integer attempts = (Integer) status.getAttribute("attempts");
if (attempts == null) {