Reputation: 85
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);
}
Upvotes: 6
Views: 9410
Reputation: 4141
5/2023 for AspectJ
MyApplication.java
@SpringBootApplication
public class AopjApplication {
public static void main(String[] args) {
ConfigurableApplicationContext cntx = SpringApplication.run(AopjApplication.class, args);
A a=cntx.getBean(A.class);
a.chirp();
}
}
adding AspectJ to build.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.aspectj:aspectjweaver:1.9.19' //currently last version
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
A.java :
public class A {
public void chirp() {
System.out.print(11111);
}
}
MyConfig.java
@Configuration
@EnableAspectJAutoProxy
public class MyConfig {
@Bean
public A a() {
return new A();
}
@Bean
public B b() {
return new B();
}
. . .
@Bean
public MyAspect myAspect() {
return new MyAspect();
}
}
MyAspect.java
@Aspect
public class MyAspect {
@Before("execution(* mypackage.A.chirp())")
public void aa() {
System.out.print(2222);
}
}
I ve run that and now console shows :
2023-05-21T22:47:34.035+05:00 INFO 9332 --- [ main] mypackage.AopjApplication : Starting AopjApplication using Java 17.0.6 with PID 9332 (C:\Users\erics\eclipse-workspace\aopj\bin\main started by erics in C:\Users\erics\eclipse-workspace\aopj)
2023-05-21T22:47:34.045+05:00 INFO 9332 --- [ main] mypackage.AopjApplication : No active profile set, falling back to 1 default profile: "default"
2023-05-21T22:47:35.566+05:00 INFO 9332 --- [ main] mypackage.AopjApplication : Started AopjApplication in 2.33 seconds (process running for 3.303)
2222
11111
Upvotes: 0
Reputation: 67297
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
Upvotes: 18
Reputation: 682
I've had a similar problem and I managed to solve it using AspectJ:
https://github.com/mdanetzky/tour-of-heroes-java
Also - it took me some time to find out, that my IDEA didn't rebuild aspects properly, so it might be worth trying to clean/rebuild the project before you try some more drastic measures.
Upvotes: 1