如何统一实现类中各方法catch块的重试逻辑?
Hey, great question! Duplicating retry logic across every method is a total anti-pattern—let's fix that with cleaner, more maintainable approaches instead of shoving retry logic into a custom exception (which violates the single responsibility principle, since exceptions should only represent errors, not handle business logic).
Here are your best options, ordered by elegance and maintainability:
1. Use a Template Method Pattern (No Extra Dependencies)
This is a simple, dependency-free way to centralize retry logic. Create a reusable template class that wraps your business logic, so you only write the retry loop once.
Step 1: Build the Retry Template
public class RetryTemplate { // Configure these as needed, or add setters for flexibility private int maxRetries = 3; private long retryIntervalMillis = 1000; // For methods with return values public <T> T execute(Callable<T> task) throws Exception { int attempt = 0; while (true) { try { return task.call(); } catch (Exception e) { attempt++; if (attempt >= maxRetries) { throw e; // Give up after max retries, propagate the exception } // Add your executeLater logic here if needed, or just delay Thread.sleep(retryIntervalMillis); } } } // Overload for void methods public void execute(Runnable task) throws Exception { execute(() -> { task.run(); return null; }); } // Optional: Getters/setters to adjust retry config dynamically public void setMaxRetries(int maxRetries) { this.maxRetries = maxRetries; } public void setRetryIntervalMillis(long retryIntervalMillis) { this.retryIntervalMillis = retryIntervalMillis; } }
Step 2: Refactor Your Class to Use the Template
class MyClass { private final RetryTemplate retryTemplate = new RetryTemplate(); public void method1(int a, int b) { try { retryTemplate.execute(() -> { // Your original "do some logic" with a and b goes here return null; }); } catch (Exception e) { // Your existing exception handling for final failure } } public void method2() { try { retryTemplate.execute(() -> { // Your original "do some logic" goes here return null; }); } catch (Exception e) { // Your existing exception handling } } public void method3(String abcd, int a) { try { retryTemplate.execute(() -> { // Your original "do some logic" with abcd and a goes here return null; }); } catch (Exception e) { // Your existing exception handling } } }
Pros: No external dependencies, type-safe (no reflection), easy to understand and modify.
2. AOP + Custom Annotation (Spring Environment)
If you're using Spring, this is the cleanest approach—completely decouple retry logic from your business methods using aspect-oriented programming.
Step 1: Create a Custom Retry Annotation
import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RetryOnFailure { int maxRetries() default 3; long retryIntervalMillis() default 1000; }
Step 2: Implement the Retry Aspect
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; @Aspect @Component public class RetryAspect { @Around("@annotation(retryAnnotation)") public Object handleRetry(ProceedingJoinPoint joinPoint, RetryOnFailure retryAnnotation) throws Throwable { int maxRetries = retryAnnotation.maxRetries(); long interval = retryAnnotation.retryIntervalMillis(); int attempt = 0; while (true) { try { return joinPoint.proceed(); // Execute the original method } catch (Throwable e) { attempt++; if (attempt >= maxRetries) { throw e; // Stop retrying, propagate the exception } // If you need to call your existing executeLater method: // String methodName = joinPoint.getSignature().getName(); // Object[] args = joinPoint.getArgs(); // executeLater(methodName, args); Thread.sleep(interval); } } } }
Step 3: Annotate Your Methods
class MyClass { @RetryOnFailure(maxRetries = 3, retryIntervalMillis = 1000) public void method1(int a, int b) { // Your original "do some logic" with a and b—no try/catch needed! } @RetryOnFailure // Uses default values (3 retries, 1s interval) public void method2() { // Your original "do some logic" } @RetryOnFailure(maxRetries = 5) public void method3(String abcd, int a) { // Your original "do some logic" with abcd and a } // Optional: Keep your existing executeLater method if needed public boolean executeLater(String methodName, Object args[]){ // Execute given method with the supplied args return true; } }
Pros: Zero boilerplate in business methods, highly configurable via annotations, easy to enable/disable per method.
3. Use a Mature Retry Framework (e.g., Spring Retry)
For complex retry scenarios (like exponential backoff, retry only on specific exceptions), leverage a battle-tested framework instead of rolling your own.
Example with Spring Retry
First, add the Maven dependencies:
<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> <version>2.0.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> </dependency>
Enable retry in your Spring config:
import org.springframework.retry.annotation.EnableRetry; import org.springframework.context.annotation.Configuration; @Configuration @EnableRetry public class RetryConfig { }
Annotate your methods:
import org.springframework.retry.annotation.Retryable; import org.springframework.retry.annotation.Recover; class MyClass { @Retryable( value = Exception.class, maxAttempts = 3, backoff = @Backoff(delay = 1000) // 1s delay between retries ) public void method1(int a, int b) { // Your original logic } @Retryable public void method2() { // Your original logic } // Optional: Recovery method for when all retries fail @Recover public void handleRetryFailure(Exception e, int a, int b) { // Your existing exception handling logic goes here } }
Pros: Built-in support for advanced retry strategies, minimal code, well-documented, and maintained by Spring.
Why Your Original Custom Exception Idea Isn't Ideal
Putting retry logic inside a custom exception mixes error representation with business logic. This makes your code harder to reason about—if another part of your code throws this exception, it might accidentally trigger retry logic when you don't want it. Exceptions should only communicate that something went wrong, not handle recovery.
内容的提问来源于stack exchange,提问作者VidhyaRani

