George Zalizko
George Zalizko

Reputation: 85

Spring AOP: aspect @Around doesn't work

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

Answers (3)

CodeToLife
CodeToLife

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

kriegaex
kriegaex

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

  • avoid reflection and bind the annotation to an advice parameter directly via @annotation(),
  • log the joinpoint when the advice is triggered and
  • return "OK" on try #3 (just for fun, not necessary).
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

Matthias Danetzky
Matthias Danetzky

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

Related Questions