Search code examples
javaaspectjinfinite-looppointcut

Aspectj - how to call advised method from within same advice, without triggering an infinite loop


i want to log all method call i make in my code except the ones within the logger, using AspectJ.

@Aspect
public class Logger
{
    // Point Cuts
    //-----------
    @Pointcut("execution(* org.myDomain.*..*.*(..))")
    public void selectAll(){}

    @Pointcut("within(Logger) && call(* *(..))")
    public void codeWithinAspect(){}

    // Advices
    //-----------
    @Before("selectAll()")
    public void adviceThatWorksFine(JoinPoint joinPoint)
    {
        System.out.print(joinPoint.getSignature().toString());
        //Utils.printToConsole(joinPoint.getSignature().toString());    
    }

    @Before("selectAll() && !codeWithinAspect")
    public void adviceWithInfiniteLoop(JoinPoint joinPoint)
    {
        //System.out.print(joinPoint.getSignature().toString());
        Utils.printToConsole(joinPoint.getSignature().toString());  
    }
}

the first advice in the class works fine (it writes every method call to the console), the second advice causes an infinite loop when calling org.myDomain.utils.Utils.printToConsole() method, which is advised by the calling advice.

I have found it is a common problem as described in the link http://www.eclipse.org/aspectj/doc/released/faq.php#q:infiniterecursion but i could not understand how to write the pointcut so an infinite loop would not be created.

plaes help


Solution

  • There are several problems in your code:

    • !codeWithinAspect needs parentheses: !codeWithinAspect()
    • adviceWithInfiniteLoop() combines execution() and call() pointcuts in this way: execution(foo) && !call(bar). Because a call joinpoint can never be an execution joinpoint the second part of the condition is always true and has no effect. Thus, it does not avoid the infinite loop.
    • You do not just want to exclude joinpoints within aspect Logger but also those within the control flow (cflow()) of that aspect's methods, i.e. stuff directly or indirectly called by them.

    The solution is as follows:

    Utility class for log output:

    package org.myDomain.app;
    
    public class Utils {
        public static void printToConsole(Object object) {
            System.out.println(object);
        }
    }
    

    Driver application:

    package org.myDomain.app;
    
    public class Application {
        public  static void sayHelloTo(String counterpart) {
            Utils.printToConsole("Hello " + counterpart + "!");
        }
    
        public static void main(String[] args) {
            sayHelloTo("world");
        }
    }
    

    Logger aspect:

    package org.myDomain.aspect;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.myDomain.app.Utils;
    
    @Aspect
    public class Logger {
        @Pointcut("execution(* org.myDomain..*(..))")
        public void selectAll() {}
    
        @Pointcut("cflow(within(Logger))")
        public void codeWithinAspect() {}
    
        @Before("selectAll() && !codeWithinAspect()")
        public void advice(JoinPoint joinPoint) {
            Utils.printToConsole(joinPoint);
        }
    }
    

    Console output:

    execution(void org.myDomain.app.Application.main(String[]))
    execution(void org.myDomain.app.Application.sayHelloTo(String))
    execution(void org.myDomain.app.Utils.printToConsole(Object))
    Hello world!
    

    Enjoy!

    Update: If you want to exclude all advice execution control flows you can also use this pointcut:

    @Pointcut("cflow(adviceexecution())")
    public void codeWithinAspect() {}