Mastering Retry Strategies in Java: From Manual Loops to Spring Retry and Guava
This article walks through various Java retry mechanisms—including manual loops, static and dynamic proxies, AOP, Spring Retry annotations, and Guava‑retry—explaining their implementations, advantages, drawbacks, and when to choose each approach for robust remote service calls.
1. Manual Retry
Manual retry uses a simple while loop to repeat a remote call, but it lacks interval control, introduces invasive code, and leads to duplicated logic across services.
@Service
public class OrderServiceImpl implements OrderService {
public void addOrder() {
int times = 1;
while (times <= 5) {
try {
// intentionally cause exception
int i = 3 / 0;
System.out.println("重试" + times + "次");
Thread.sleep(2000);
times++;
if (times > 5) {
throw new RuntimeException("不再重试!");
}
} catch (Exception e) {
// handle exception (already logged)
}
}
}
}Running the code demonstrates the retry attempts but also shows the shortcomings mentioned above.
2. Static Proxy
A static proxy wraps the business class with a proxy that contains the retry logic, keeping the original business code untouched while centralising retry handling. However, a separate proxy class is required for each service.
@Service
public class OrderServiceProxyImpl implements OrderService {
@Autowired
private OrderServiceImpl orderService;
@Override
public void addOrder() {
int times = 1;
while (times <= 5) {
try {
// intentionally cause exception
int i = 3 / 0;
orderService.addOrder();
} catch (Exception e) {
System.out.println("重试" + times + "次");
Thread.sleep(2000);
times++;
if (times > 5) {
throw new RuntimeException("不再重试!");
}
}
}
}
}3. JDK Dynamic Proxy
JDK dynamic proxy allows a single InvocationHandler to add retry logic for any interface, reducing boilerplate code.
public class RetryInvocationHandler implements InvocationHandler {
private final Object subject;
public RetryInvocationHandler(Object subject) {
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
int times = 1;
while (times <= 5) {
try {
// intentionally cause exception
int i = 3 / 0;
return method.invoke(subject, args);
} catch (Exception e) {
System.out.println("重试【" + times + "】次");
Thread.sleep(2000);
times++;
if (times > 5) {
throw new RuntimeException("不再重试!");
}
}
}
return null;
}
public static Object getProxy(Object realSubject) {
InvocationHandler handler = new RetryInvocationHandler(realSubject);
return Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), handler);
}
}4. CGLib Dynamic Proxy
CGLib proxy works for classes that do not implement an interface, overcoming the limitation of JDK proxies.
@Component
public class CGLibRetryProxyHandler implements MethodInterceptor {
private Object target;
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
int times = 1;
while (times <= 5) {
try {
// intentionally cause exception
int i = 3 / 0;
return method.invoke(target, args);
} catch (Exception e) {
System.out.println("重试【" + times + "】次");
Thread.sleep(2000);
times++;
if (times > 5) {
throw new RuntimeException("不再重试!");
}
}
}
return null;
}
public Object getCglibProxy(Object objectTarget) {
this.target = objectTarget;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(objectTarget.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
}5. Manual AOP
Define a custom annotation and an Aspect to weave retry logic around annotated methods, eliminating repetitive code.
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRetryable {
int retryTimes() default 3; // maximum attempts
int retryInterval() default 1; // seconds between attempts
}
@Aspect
@Component
public class RetryAspect {
@Pointcut("@annotation(com.hcr.sbes.retry.annotation.MyRetryable)")
private void retryMethodCall() {}
@Around("retryMethodCall()")
public Object retry(ProceedingJoinPoint joinPoint) throws InterruptedException {
MyRetryable retry = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(MyRetryable.class);
int maxRetryTimes = retry.retryTimes();
int retryInterval = retry.retryInterval();
Throwable error = new RuntimeException();
for (int i = 1; i <= maxRetryTimes; i++) {
try {
return joinPoint.proceed();
} catch (Throwable t) {
error = t;
log.warn("调用发生异常,开始重试,retryTimes:{}", i);
Thread.sleep(retryInterval * 1000L);
}
}
throw new RuntimeException("重试次数耗尽", error);
}
}6. Spring Retry
Spring Retry provides three annotations— @Retryable, @Backoff, and @Recover —to configure retry attempts, back‑off strategies, and recovery handling.
@Service
public class OrderServiceImpl implements OrderService {
@Retryable(maxAttempts = 3, backoff = @Backoff(delay = 2000, multiplier = 2))
public void addOrder() {
System.out.println("重试...");
int i = 3 / 0;
}
@Recover
public void recover(RuntimeException e) {
log.error("达到最大重试次数", e);
}
}7. Guava Retry
Guava‑retry offers flexible policies that can react to exceptions or specific return values, with customizable wait and stop strategies.
public String guavaRetry(Integer num) {
Retryer<String> retryer = RetryerBuilder.<String>newBuilder()
.retryIfException()
.retryIfResult(result -> Objects.equals(result, "error"))
.withWaitStrategy(WaitStrategies.fixedWait(2, TimeUnit.SECONDS))
.withStopStrategy(StopStrategies.stopAfterAttempt(3))
.withRetryListener(new RetryListener() {
@Override
public <V> void onRetry(Attempt<V> attempt) {
System.out.println("RetryListener: 第" + attempt.getAttemptNumber() + "次调用");
}
})
.build();
try {
retryer.call(() -> testGuavaRetry(num));
} catch (Exception e) {
e.printStackTrace();
}
return "test";
}Summary
For Spring‑based projects, using Spring Retry annotations solves most retry requirements. For non‑Spring environments, Guava‑retry provides a powerful, framework‑agnostic alternative, while dynamic proxies and manual AOP give fine‑grained control when needed.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
