Implementing Retry Mechanisms in Java: Manual Loops, Static Proxy, JDK Dynamic Proxy, CGLib Proxy, AOP, Spring Retry and Guava Retry
This article explains why retry mechanisms are essential for remote service calls in Java applications and demonstrates six practical implementations—from simple while‑loop retries to static proxies, JDK and CGLib dynamic proxies, custom AOP, Spring Retry annotations, and the Guava‑retry library—complete with code examples and usage tips.
As internet services become more complex, remote calls to third‑party APIs or internal services can fail due to network instability or server errors; adding a retry mechanism improves system robustness and stability.
1. Manual Retry (while loop)
Using a simple @Service public class OrderServiceImpl implements OrderService { public void addOrder() { int times = 1; while (times <= 5) { try { // intentionally cause exception int i = 3 / 0; // addOrder logic } catch (Exception e) { System.out.println("重试" + times + "次"); Thread.sleep(2000); times++; if (times > 5) { throw new RuntimeException("不再重试!"); } } } } } demonstrates the basic idea but has drawbacks such as lack of delay handling, high code intrusion, and duplication across many services.
2. Static Proxy
Encapsulating retry logic in a separate proxy class reduces intrusion:
@Service
public class OrderServiceProxyImpl implements OrderService {
@Autowired
private OrderServiceImpl orderService;
@Override
public void addOrder() {
int times = 1;
while (times <= 5) {
try {
int i = 3 / 0;
orderService.addOrder();
} catch (Exception e) {
System.out.println("重试" + times + "次");
try { Thread.sleep(2000); } catch (InterruptedException ex) { ex.printStackTrace(); }
times++;
if (times > 5) {
throw new RuntimeException("不再重试!");
}
}
}
}
}While more elegant than manual code, creating a proxy for each service can become cumbersome.
3. JDK Dynamic Proxy
Using a single invocation handler to apply retry to any interface implementation:
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 {
int i = 3 / 0;
return method.invoke(subject, args);
} catch (Exception e) {
System.out.println("重试【" + times + "】次");
try { Thread.sleep(2000); } catch (InterruptedException ex) { ex.printStackTrace(); }
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);
}
}Usage in a controller:
@RestController
@RequestMapping("/order")
public class OrderController {
@Qualifier("orderServiceImpl")
@Autowired
private OrderService orderService;
@GetMapping("/addOrder")
public String addOrder() {
OrderService orderServiceProxy = (OrderService) RetryInvocationHandler.getProxy(orderService);
orderServiceProxy.addOrder();
return "addOrder";
}
}JDK proxies require the target class to implement an interface.
4. CGLib Dynamic Proxy
CGLib can proxy concrete classes without interfaces:
@Component
public class CGLibRetryProxyHandler implements MethodInterceptor {
private Object target;
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
int times = 1;
while (times <= 5) {
try {
int i = 3 / 0;
return method.invoke(target, args);
} catch (Exception e) {
System.out.println("重试【" + times + "】次");
try { Thread.sleep(2000); } catch (InterruptedException ex) { ex.printStackTrace(); }
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();
}
}Controller usage:
@GetMapping("/addOrder")
public String addOrder() {
OrderService orderServiceProxy = (OrderService) cgLibRetryProxyHandler.getCglibProxy(orderService);
orderServiceProxy.addOrder();
return "addOrder";
}5. Manual AOP (custom annotation)
Define a @MyRetryable annotation and a RetryAspect that intercepts annotated methods, applying configurable retry count and interval:
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRetryable {
int retryTimes() default 3;
int retryInterval() default 1;
}
@Slf4j
@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);
}
}Apply to a service method:
@Service
public class OrderServiceImpl implements OrderService {
@Override
@MyRetryable(retryTimes = 5, retryInterval = 2)
public void addOrder() {
int i = 3 / 0;
}
}6. Spring‑Retry
Add the dependency and enable retry with @EnableRetry . Annotate methods with @Retryable and optionally provide a @Recover method:
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
@Override
@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);
}
}Spring‑Retry also offers many policies (NeverRetryPolicy, AlwaysRetryPolicy, SimpleRetryPolicy, etc.) for fine‑grained control.
7. Guava‑Retry
Guava‑retry provides flexible retry based on exceptions or return values:
@Override
public String guavaRetry(Integer num) {
Retryer
retryer = RetryerBuilder.
newBuilder()
.retryIfException()
.retryIfResult(r -> Objects.equals(r, "error"))
.withWaitStrategy(WaitStrategies.fixedWait(2, TimeUnit.SECONDS))
.withStopStrategy(StopStrategies.stopAfterAttempt(3))
.withRetryListener(new RetryListener() {
@Override
public
void onRetry(Attempt
attempt) {
System.out.println("RetryListener: 第" + attempt.getAttemptNumber() + "次调用");
}
})
.build();
try { retryer.call(() -> testGuavaRetry(num)); } catch (Exception e) { e.printStackTrace(); }
return "test";
}Guava‑retry can also be used directly with RetryTemplate for non‑Spring projects.
Conclusion
From manual loops to Spring annotations and Guava‑retry, the presented techniques cover most retry scenarios. Spring projects should prefer Spring‑Retry, while non‑Spring Java applications can adopt Guava‑retry for a lightweight yet powerful solution.
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.