I've made a simple web application using Spring Boot and Spring Initializr and tried to write @Aspect with @Around advice.
When I add my custom annotation @RetryOnFailure to the controllers' endpoint method - it works, but when I add this annotation to the controllers' method, that executed by controllers endpoint - it doesn't work. I spend a lot of time for understanding the reason for such behavior, but without any result. So please help.
Project located here: https://github.com/zalizko/spring-aop-playground
@Aspect
@Component
public final class MethodRepeater {
@Around("execution(* *(..)) && @annotation(RetryOnFailure)")
public Object wrap(final ProceedingJoinPoint joinPoint) throws Throwable {
// code is here
}
}
So, my goal is that:
@RequestMapping
public String index() {
inTry();
return "OK";
}
@RetryOnFailure(attempts = 3, delay = 2, unit = TimeUnit.SECONDS)
public void inTry() {
throw new RuntimeException("Exception in try " + ++counter);
}
You made a typical Spring AOP beginners' mistake: You forgot that proxy-based AOP only works if proxy methods are called from outside, not via this
(avoiding the proxy). But the internal call inTry()
is the same as this.inTry()
. Thus, the aspect never triggers for inTry
and you have to rearrange your code like this:
package spring.aop;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
@RestController("/")
public class HomeController {
static int counter = 0;
@RequestMapping
@RetryOnFailure(attempts = 3, delay = 2, unit = TimeUnit.SECONDS)
public String index() {
throw new RuntimeException("Exception in try " + ++counter);
}
}
I also changed the aspect a little bit so as to
@annotation()
,package spring.aop;
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 final class MethodRepeater {
@Around("execution(* spring.aop..*(..)) && @annotation(retryOnFailure)")
public Object wrap(final ProceedingJoinPoint joinPoint, RetryOnFailure retryOnFailure) throws Throwable {
System.out.println(joinPoint);
return proceed(joinPoint, retryOnFailure);
}
private Object proceed(ProceedingJoinPoint joinPoint, RetryOnFailure retryOnFailure) throws Throwable {
int attempt = 1;
while (true) {
try {
return joinPoint.proceed();
} catch (final Throwable ex) {
System.out.println("Try #" + attempt + " failed: " + ex);
if (++attempt >= retryOnFailure.attempts())
return "OK";
if (retryOnFailure.delay() > 0L)
retryOnFailure.unit().sleep(retryOnFailure.delay());
}
}
}
}
Now it works and the console log says:
execution(String spring.aop.HomeController.index())
Try #1 failed: java.lang.RuntimeException: Exception in try 1
Try #2 failed: java.lang.RuntimeException: Exception in try 2