java - How to implement application-level retry logic using Spring AOP without modifying service classes? - Stack Overflow

admin2025-04-25  2

I want to implement a retry mechanism at the application level in a Spring Boot application. However, I don't want to add @Retryable or make any changes to the service methods or their classes. My goal is to implement the retry logic using Spring AOP and a centralized configuration, such as RetryTemplate, for consistency across the application.

Here’s what I have done so far:

Aspect Class I created an aspect that uses RetryTemplate to handle retries for all methods within @Service classes.

@Aspect
@Component
public class DbConnectionRetryAspect {

    private final RetryTemplate retryTemplate;

    public DbConnectionRetryAspect(RetryTemplate retryTemplate) {
        this.retryTemplate = retryTemplate;
    }

    @Around("@within(org.springframework.stereotype.Service)")
    public Object retry(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("Aspect invoked for method: " + joinPoint.getSignature());

        return retryTemplate.execute(context -> {
            System.out.println("Retry attempt: " + context.getRetryCount());
            try {
                return joinPoint.proceed();
            } catch (SQLTransientConnectionException e) {
                System.out.println("Retryable exception caught: " + e.getMessage());
                throw e; // Ensure the exception propagates for retries
            }
        });
    }
}

RetryTemplate Configuration I created a RetryTemplate bean to define the retry logic.

@Bean
public RetryTemplate retryTemplate() {
    RetryTemplate retryTemplate = new RetryTemplate();

    SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(
        5,  // maxAttempts
        Map.of(SQLTransientConnectionException.class, true)
    );
    retryTemplate.setRetryPolicy(retryPolicy);

    FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
    backOffPolicy.setBackOffPeriod(1000); // 1 second
    retryTemplate.setBackOffPolicy(backOffPolicy);

    return retryTemplate;
}

Service Class The service method throws a SQLTransientConnectionException when the operation fails. I don’t want to annotate this method with @Retryable.

@Service
public class MyService {

    public void performOperation() throws SQLTransientConnectionException {
        System.out.println("Service method invoked");
        throw new SQLTransientConnectionException("Simulated transient error");
    }
}

Configuration I added the following configuration to enable AspectJ:

@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AspectJConfig {
}

The Problem: While the aspect is being invoked, the retry logic is not working as expected. Specifically:

  • The RetryTemplate logs indicate that retries are attempted (Retry attempt: X), but the service method is only called once.

  • It seems the exception is not propagating correctly or the retry logic is not being applied to the service method.

What I Need Help With: How can I implement application-level retry logic using Spring AOP and RetryTemplate such that:

  • The retry mechanism works as expected.
  • No changes are required in the service classes or methods (e.g., no annotations like @Retryable).
  • The retry attempts and backoff policies are configurable via the centralized RetryTemplate.

Any guidance or suggestions would be greatly appreciated.

Thank you!

I want to implement a retry mechanism at the application level in a Spring Boot application. However, I don't want to add @Retryable or make any changes to the service methods or their classes. My goal is to implement the retry logic using Spring AOP and a centralized configuration, such as RetryTemplate, for consistency across the application.

Here’s what I have done so far:

Aspect Class I created an aspect that uses RetryTemplate to handle retries for all methods within @Service classes.

@Aspect
@Component
public class DbConnectionRetryAspect {

    private final RetryTemplate retryTemplate;

    public DbConnectionRetryAspect(RetryTemplate retryTemplate) {
        this.retryTemplate = retryTemplate;
    }

    @Around("@within(org.springframework.stereotype.Service)")
    public Object retry(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("Aspect invoked for method: " + joinPoint.getSignature());

        return retryTemplate.execute(context -> {
            System.out.println("Retry attempt: " + context.getRetryCount());
            try {
                return joinPoint.proceed();
            } catch (SQLTransientConnectionException e) {
                System.out.println("Retryable exception caught: " + e.getMessage());
                throw e; // Ensure the exception propagates for retries
            }
        });
    }
}

RetryTemplate Configuration I created a RetryTemplate bean to define the retry logic.

@Bean
public RetryTemplate retryTemplate() {
    RetryTemplate retryTemplate = new RetryTemplate();

    SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(
        5,  // maxAttempts
        Map.of(SQLTransientConnectionException.class, true)
    );
    retryTemplate.setRetryPolicy(retryPolicy);

    FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
    backOffPolicy.setBackOffPeriod(1000); // 1 second
    retryTemplate.setBackOffPolicy(backOffPolicy);

    return retryTemplate;
}

Service Class The service method throws a SQLTransientConnectionException when the operation fails. I don’t want to annotate this method with @Retryable.

@Service
public class MyService {

    public void performOperation() throws SQLTransientConnectionException {
        System.out.println("Service method invoked");
        throw new SQLTransientConnectionException("Simulated transient error");
    }
}

Configuration I added the following configuration to enable AspectJ:

@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AspectJConfig {
}

The Problem: While the aspect is being invoked, the retry logic is not working as expected. Specifically:

  • The RetryTemplate logs indicate that retries are attempted (Retry attempt: X), but the service method is only called once.

  • It seems the exception is not propagating correctly or the retry logic is not being applied to the service method.

What I Need Help With: How can I implement application-level retry logic using Spring AOP and RetryTemplate such that:

  • The retry mechanism works as expected.
  • No changes are required in the service classes or methods (e.g., no annotations like @Retryable).
  • The retry attempts and backoff policies are configurable via the centralized RetryTemplate.

Any guidance or suggestions would be greatly appreciated.

Thank you!

Share Improve this question edited Jan 16 at 12:25 M. Deinum 126k22 gold badges233 silver badges249 bronze badges asked Jan 16 at 12:23 concept_codingconcept_coding 211 silver badge2 bronze badges 9
  • If the retry attempt is logged that already indicates that it is working. Tested your code locally on my machine it works without issues. Which means the code you showed here doesn't have those symptoms and is probably related to something else in your project. – M. Deinum Commented Jan 16 at 12:31
  • @M.Deinum, Thank you for your response. I appreciate the feedback! Based on your observation, if the retry attempts are logged, the retry mechanism is indeed being invoked. However, I’ve still encountered the issue where the service method is only being called once, and no retries are being performed. If you have any suggestions on what to check or any additional tips to debug this issue, I would greatly appreciate it! – concept_coding Commented Jan 16 at 12:52
  • As stated the code as you provided here works without issue. Tip you don't need the AspectJConfig as Spring Boot takes care of that. The method not being invoked again the only thing I could think of is caching of the method result due to the use of @Cacheable or some other means of caching. – M. Deinum Commented Jan 16 at 13:13
  • @concept_coding are you calling the service method that you want to retry from a different object or from the same object? – Ivo van der Veeken Commented Jan 16 at 14:11
  • @IvovanderVeeken, I am calling the service method from a controller via the service bean. – concept_coding Commented Jan 16 at 16:58
 |  Show 4 more comments

1 Answer 1

Reset to default 0

The issue was caused by the @Transactional annotation on my custom @CustomService annotation. Since @Transactional proxies the class to handle transaction management, it interfered with the retry mechanism when combined with @Service. The retry logic was being applied to the transactional proxy, which was likely causing the method invocation to bypass the retry logic after the first call.

转载请注明原文地址:http://anycun.com/QandA/1745532226a90853.html