Search code examples
javaaspectjpointcut

2 advices colliding on the same fucntion


Hei, I started learning Aspectj and I have built 2 aspects. Both aspects have a pointcut that match the same function, and both aspects have an around advice that will do something on that pointcut. However only one advice will be "executed", and I do not understand why.

let me show you:

@Aspect
public class secondAspect {
    @Pointcut("call(  * main.*(..))")
    public void pointCutThatMatchesEveryFunctionFromMain(){

    }
    @Around("pointCutThatMatchesEveryFunctionFromMain()")
    public void adviceOnPointCutThatMatchesEveryFunctionFromMain(JoinPoint jp){
        System.out.println("A method was called");
        System.out.println(jp.getTarget().toString());
    }
}

that is the second aspect

@Aspect
public class firstAspect {


    @Pointcut("call( public * main.*(..)) && args(x,y)")
    public void pointCutOnMainClassIntArgs(int x, int y) {

    }

    @Pointcut("call( public * main.*(..)) && args(x,y)")
    public void pointCutOnMainClassFloatArgs(float x, float y) {

    }


    @Around("pointCutOnMainClassIntArgs(x,y)")
    public void doSomethingOnThisPointCut(JoinPoint pjp, int x, int y) {
        System.out.println(String.format("The method name is %s and was called with parameteres %d %d", pjp.getTarget().toString(), x, y));
    }


    @Around("pointCutOnMainClassFloatArgs(x,y)")
    public void doSomethingOnThisPointCutWithFloatArgs(JoinPoint pjp, float x, float y) {
        System.out.println(String.format("The method name is %s and was called with parameteres %f %f", pjp.getTarget().toString(), x, y));
    }


}

the first aspect


public class main {
    public static void main(String[] args) {
        main maine = new main();
        maine.calculate(2,3);
        maine.calculate(2.0f,5.0f);
    }
    public void calculate(int x, int y){
        System.out.println(x+y);
    }
    public void calculate(float x, float y){
        System.out.println(x+y);
    }

}

and this is the class that i want to modify. Only the secondAspect's adivce is executed. And I don't get it why.


Solution

  • The short answer is: Change your advice type from @Around to @Before, then it works and the console output will turn into:

    A method was called
    main@5f341870
    The method name is main@5f341870 and was called with parameteres 2 3
    The method name is main@5f341870 and was called with parameteres 2,000000 3,000000
    5
    A method was called
    main@5f341870
    The method name is main@5f341870 and was called with parameteres 2,000000 5,000000
    7.0
    

    Caveat: If you inspect the above log output, you will notice that your advice doSomethingOnThisPointCutWithFloatArgs also matches the method with the int parameters, which probably was not your intention. You need to be more precise than main.*(..) and better use main.*(int, int) vs. main.*(float, float) in your pointcuts.

    The long answer is: You should read an AspectJ tutorial. For example, an @Around advice needs

    • a ProoceedingJoinPoint method parameter instead of a simple JoinPoint,
    • to explicitly call the joinpoint's proceed() method in order to actually call the intercepted target method. You did not do that, which is the explanation which only one random aspect (the first one found by AspectJ) was executed, but not the second because you designed your advice code in such a way that you never proceeded to the target method.
    • to return Object or a more specific non-void type if you want to match methods returning something other than void. You either need to return the result of proceed() or something else you wish to be returned as the result of the target method.

    Some more suggestions:

    • Please follow Java coding guidelines and do not start class or aspect names with lower-case letters.
    • Specifically do not use a class name main, then a method main and due to the naming clash a local variable maine. I guess it does not get much uglier than that.
    • When you print an object like System.out.println(jp.getTarget().toString()), the toString() is superfluous because this method will always be called implicitly when printing objects.
    • When you have multiple aspects targeting the same joinpoints and wish to enforce a specific order in which they are called, learn how to use @DeclarePrecedence.
    • I recommend to get rid of separate @Pointcut definitions and define your pointcuts inline unless you wish to re-use a pointcut.
    • If you want to know what is going on in AspectJ, why not print the full joinpoint instead of "a method was called"? It helps understand what is going on tremendously.
    • Learn the semantic difference between call() and execution(): While the former intercepts all callers (i.e. the sources of method calls), the latter intercepts the calls themselves no matter where they originate from. See here and the AspectJ manual for more information.
    • Try to avoid putting your classes and aspects into the default package. This is not good style. Also you will notice that AspectJ pointcuts can be quite sensitive to package names.
    • Instead of combining PrintStream.println and String.format, just use PrintStream.printf.

    How about this?

    package de.scrum_master.app;
    
    public class Application {
      public static void main(String[] args) {
        Application application = new Application();
        application.calculate(2, 3);
        application.calculate(2.0f, 5.0f);
      }
    
      public void calculate(int x, int y) {
        System.out.println(x + y);
      }
    
      public void calculate(float x, float y) {
        System.out.println(x + y);
      }
    }
    
    package de.scrum_master.aspect;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    
    @Aspect
    public class FirstAspect {
      @Before("execution(public !static * *(int, int)) && args(x, y) && target(targetInstance)")
      public void publicNonStaticMethodsWith2IntParameters(JoinPoint joinPoint, int x, int y, Object targetInstance) {
        System.out.printf("[FA] %s -> %s [%s, %s]%n", joinPoint, targetInstance, x, y);
      }
    
      @Before("execution(public !static * *(float, float)) && args(x, y) && target(targetInstance)")
      public void publicNonStaticMethodsWith2FloatParameters(JoinPoint joinPoint, float x, float y, Object targetInstance) {
        System.out.printf("[FA] %s -> %s [%s, %s]%n", joinPoint, targetInstance, x, y);
      }
    }
    
    package de.scrum_master.aspect;
    import java.util.Arrays;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    
    @Aspect
    public class SecondAspect {
      @Before("execution(public !static * *(..)) && target(targetInstance)")
      public void publicNonStaticMethods(JoinPoint joinPoint, Object targetInstance) {
        System.out.printf("[SA] %s -> %s %s%n", joinPoint, targetInstance, Arrays.deepToString(joinPoint.getArgs()));
      }
    }
    
    [FA] execution(void de.scrum_master.app.Application.calculate(int, int)) -> de.scrum_master.app.Application@6c3708b3 [2, 3]
    [SA] execution(void de.scrum_master.app.Application.calculate(int, int)) -> de.scrum_master.app.Application@6c3708b3 [2, 3]
    5
    [SA] execution(void de.scrum_master.app.Application.calculate(float, float)) -> de.scrum_master.app.Application@6c3708b3 [2.0, 5.0]
    [FA] execution(void de.scrum_master.app.Application.calculate(float, float)) -> de.scrum_master.app.Application@6c3708b3 [2.0, 5.0]
    7.0