Specifying an exception-specific backoff policy with Spring-Retry
The accepted answer only deals with exception-specific RetryPolicy instances. Spring doesn't provide any functionality out of the box for exception-specific BackOffPolicy instances. Luckily it's straightforward to implement.
import org.springframework.classify.Classifier
import org.springframework.classify.ClassifierSupport
import org.springframework.classify.SubclassClassifier
import org.springframework.retry.RetryContext
import org.springframework.retry.backoff.BackOffContext
import org.springframework.retry.backoff.BackOffInterruptedException
import org.springframework.retry.backoff.BackOffPolicy
import org.springframework.retry.backoff.NoBackOffPolicy
class ExceptionClassifierBackoffPolicy implements BackOffPolicy {
private static class ExceptionClassifierBackoffContext implements BackOffContext, BackOffPolicy {
Classifier<Throwable, BackOffPolicy> exceptionClassifier
RetryContext retryContext
Map<BackOffPolicy, BackOffContext> policyContextMap = [:]
ExceptionClassifierBackoffContext(Classifier<Throwable, BackOffPolicy> exceptionClassifier, RetryContext retryContext) {
this.exceptionClassifier = exceptionClassifier
this.retryContext = retryContext
}
@Override
BackOffContext start(RetryContext context) {
return null
}
@Override
void backOff(BackOffContext backOffContext) throws BackOffInterruptedException {
def policy = exceptionClassifier.classify(retryContext.lastThrowable)
def policyContext = policyContextMap.get(policy)
if (!policyContext) {
policyContext = policy.start(retryContext)
policyContextMap.put(policy, policyContext)
}
policy.backOff(policyContext)
}
}
private Classifier<Throwable, BackOffPolicy> exceptionClassifier = new ClassifierSupport<Throwable, BackOffPolicy>(new NoBackOffPolicy());
void setPolicyMap(Map<Class<? extends Throwable>, BackOffPolicy> policyMap) {
exceptionClassifier = new SubclassClassifier<Throwable, BackOffPolicy>(policyMap, new NoBackOffPolicy());
}
@Override
BackOffContext start(RetryContext context) {
return new ExceptionClassifierBackoffContext(exceptionClassifier, context)
}
@Override
void backOff(BackOffContext backOffContext) throws BackOffInterruptedException {
def classifierBackOffContext = (ExceptionClassifierBackoffContext) backOffContext
classifierBackOffContext.backOff(backOffContext)
}
}
Then just:
BackOffPolicy backOffPolicy = new ExceptionClassifierBackoffPolicy()
def policyMap = [
(RuntimeException): new FixedBackOffPolicy(backOffPeriod: 1000),
(IOException) : new ExponentialRandomBackOffPolicy(initialInterval: 500, maxInterval: 360000, multiplier: 2)
] as Map<Class<? extends Throwable>, BackOffPolicy>
backOffPolicy.policyMap = backoffPolicyMap
Indeed, ExceptionClassifierRetryPolicy
is the way to go. I didn't manage to make it work with the policyMap
though.
Here is how I've used it:
@Component("yourRetryPolicy")
public class YourRetryPolicy extends ExceptionClassifierRetryPolicy
{
@PostConstruct
public void init()
{
final SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
simpleRetryPolicy.setMaxAttempts( 3 );
this.setExceptionClassifier( new Classifier<Throwable, RetryPolicy>()
{
@Override
public RetryPolicy classify( Throwable classifiable )
{
if ( classifiable instanceof YourException )
{
return new NeverRetryPolicy();
}
// etc...
return simpleRetryPolicy;
}
});
}
}
Then, you just have to set it on the retry template :
@Autowired
@Qualifier("yourRetryPolicy")
private YourRetryPolicy yourRetryPolicy;
//...
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy( yourRetryPolicy );