Implementing Retry Mechanisms in Java: Manual Loops, Static Proxy, JDK Dynamic Proxy, CGLib, AOP, Spring Retry and Guava‑Retry
This article explains why retry mechanisms are essential for remote service calls, compares several implementation approaches—including manual loops, static and dynamic proxies, AOP, Spring Retry annotations, and Guava‑Retry—provides complete Java code examples, and discusses the advantages and drawbacks of each method.
As internet services become more complex, remote calls to third‑party or internal services can fail due to network instability or internal errors, making a retry mechanism crucial for system robustness.
1. Manual Retry
Using a while loop to retry a method call up to a fixed number of attempts.
@Service
public class OrderServiceImpl implements OrderService {
public void addOrder() {
int times = 1;
while (times <= 5) {
try {
// intentionally throw exception
int i = 3 / 0;
// addOrder
} catch (Exception e) {
System.out.println("重试" + times + "次");
Thread.sleep(2000);
times++;
if (times > 5) {
throw new RuntimeException("不再重试!");
}
}
}
}
}The code works but has several drawbacks: no delay between retries, high code intrusion, and duplication when many services need retries.
2. Static Proxy
Wrap the business class with a proxy that contains the retry logic, keeping the original class unchanged.
@Service
public class OrderServiceProxyImpl implements OrderService {
@Autowired
private OrderServiceImpl orderService;
@Override
public void addOrder() {
int times = 1;
while (times <= 5) {
try {
// intentionally throw exception
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("不再重试!");
}
}
}
}
}This reduces intrusion but still requires a separate proxy class for each service.
3. JDK Dynamic Proxy
Using a single InvocationHandler to add retry logic for any interface‑based service.
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 throw exception
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);
}
}Works for any interface, but cannot proxy classes without interfaces.
4. CGLib Dynamic Proxy
Uses CGLib to create a subclass proxy, allowing classes without interfaces to be retried.
@Component
public class CGLibRetryProxyHandler implements MethodInterceptor {
private Object target;
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
int times = 1;
while (times <= 5) {
try {
// intentionally throw exception
int i = 3 / 0;
return method.invoke(target, objects);
} 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();
}
}Still requires changing the call site to obtain the proxy instance.
5. Manual AOP
Define a custom annotation and an Aspect that implements retry logic, allowing methods to be annotated without altering their bodies.
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
@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 retryTimes = 1; retryTimes <= maxRetryTimes; retryTimes++) {
try {
Object result = joinPoint.proceed();
return result;
} catch (Throwable throwable) {
error = throwable;
log.warn("调用发生异常,开始重试,retryTimes:{}", retryTimes);
}
Thread.sleep(retryInterval * 1000L);
}
throw new RuntimeException("重试次数耗尽", error);
}
}Methods simply add @MyRetryable to gain retry capability.
6. Spring‑Retry
Enable retry with @EnableRetry and annotate methods with @Retryable . Supports back‑off, recovery, and various policies.
@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 only retries on exceptions, not on undesired return values.
7. Guava‑Retry
Provides flexible retry based on exceptions and return values.
<dependency>
<groupId>com.github.rholder</groupId>
<artifactId>guava-retrying</artifactId>
<version>2.0.0</version>
</dependency>
@Override
public String guavaRetry(Integer num) {
Retryer
retryer = RetryerBuilder.
newBuilder()
.retryIfException()
.retryIfResult(result -> Objects.equals(result, "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 retry based on result values, offering more flexibility than Spring‑Retry.
Summary
From manual loops to AOP, JDK/CGLib proxies, Spring‑Retry, and Guava‑Retry, developers have multiple options to implement retry logic. For Spring‑based projects, annotation‑driven Spring‑Retry usually suffices; for non‑Spring environments, Guava‑Retry provides a powerful, standalone solution.
Architecture Digest
Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.
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.