Search code examples
spring-bootjunit5spring-aop

How to make pointcut that indicate child class's method call which didn't override parent's method?


There is one interface Foo, having two default methods m1, m2.

There are 3 classes Foo1, Foo2, Foo3 implementing Foo. Foo3 does not override method m1, but Foo1, Foo2 do.

I want to create an aspect which targets Foo3's m1 method.

I tried @Pointcut("bean(foo3) && execution(* *.m1(..)), and it works properly while Spring Boot is running. But when I create a proxy object programmatically for unit testing, it does not work.

I know that bean(foo3) makes it work under Spring Boot, but I cannot find any alternative way to express the pointcut, which still works in the test. How can I make it work in this situation?

public interface Foo {
    default Object m1() {
        // doSomething
        return Something;
    }

    default Object m2() {
        // doSomething
        return Something;
    }
}
public class Foo3 implements Foo {
    // not overriding any method.
}
@Component @Aspect
public class ExampleAspect {
    @Pointcut("bean(foo3) && execution(* *.m1(..))")
    public void exampleAspect() {}

    @Around("exampleAspect()")
    public Object proxyMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        // ...
        Object result = joinPoint.proceed();
        // ...
    }
}
public Foo3Test {
    Foo instance = new Foo3();
    AspectJProxyFactory factory = new AspectJProxyFactory(instance);
    factory.addAspect(new ExampleAspect());
    Foo proxy = factory.getProxy();
    proxy.m1(); // it didn't use proxy. invoke real method.
}

I tried these pointcuts:

  • target(Foo3) && execution(* Foo.m1(..)) does not work even under Spring Boot
  • within(Foo3) && execution(* Foo.m1(..)) does not work even under Spring Boot
  • execution(Foo.m1(..)) works but all m1 methods are proxied

Solution

  • I am not a Spring expert, but in order to make the special Spring AOP pointcut designator bean work, I guess you might need an application context in your test. Hoewever, I understand the need to create quickly running tests without all that ceremony. Fortunately, this is not difficult to achieve. Simply change your pointcut to:

    @Pointcut("target(Foo3) && execution(* m1(..))")
    public void exampleAspect() {}
    

    I tried, it works beautifully. Here is how I tested it:

    package de.scrum_master.spring.q76824399;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.stereotype.Component;
    
    @Component
    @Aspect
    public class ExampleAspect {
      @Pointcut("target(Foo3) && execution(* m1(..))")
      public void exampleAspect() {}
    
      @Around("exampleAspect()")
      public Object proxyMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println(joinPoint + " -> " + joinPoint.getTarget());
        return joinPoint.proceed();
      }
    }
    
    package de.scrum_master.spring.q76824399;
    
    import org.springframework.aop.aspectj.annotation.AspectJProxyFactory;
    
    public class Foo3Test {
      public static void main(String[] args) {
        ExampleAspect aspect = new ExampleAspect();
        Foo[] instances = { new Foo1(), new Foo2(), new Foo3() };
        for (Foo instance : instances) {
          AspectJProxyFactory factory = new AspectJProxyFactory(instance);
          factory.addAspect(aspect);
          Foo proxy = factory.getProxy();
          proxy.m1();
        }
      }
    }
    

    And this is the console log:

    10:45:11.823 [main] DEBUG org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory - Found AspectJ method: public java.lang.Object de.scrum_master.spring.q76824399.ExampleAspect.proxyMethod(org.aspectj.lang.ProceedingJoinPoint) throws java.lang.Throwable
    10:45:12.055 [main] DEBUG org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory - Found AspectJ method: public java.lang.Object de.scrum_master.spring.q76824399.ExampleAspect.proxyMethod(org.aspectj.lang.ProceedingJoinPoint) throws java.lang.Throwable
    10:45:12.071 [main] DEBUG org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory - Found AspectJ method: public java.lang.Object de.scrum_master.spring.q76824399.ExampleAspect.proxyMethod(org.aspectj.lang.ProceedingJoinPoint) throws java.lang.Throwable
    execution(Object de.scrum_master.spring.q76824399.Foo.m1()) -> de.scrum_master.spring.q76824399.Foo3@6eda5c9
    

    Please note how the aspect finds all 3 instances of Foo1, Foo2, Foo3, but the advice is only triggered for the Foo3 instance.