Search code examples
javaaspectjaspectaspectj-maven-plugin

How to change pointCut expression inside an aspect which has been compiled when I use this aspect in other project?


I have a logging aspect like this, it will print args, result or any other exceptions. I use aspectj-maven-plugin to compile this aspect. Then, the compiled result will be packaged to a jar. I will offer this jar to our maven repository, so they can depend this jar in their project. If they need log any request info, they just need write an annonation @Log at the method defination. I aslo want to offer "a package assign abilility": My colleage could set a package parameter when compile their project, then all the method invoke inside that package will be cut。

In summary, like the customPointCut in the following code, I want change the pointcut defination when I need this compiled asject in another project.

@Aspect
public class LogAspect {

    static private final Logger log = Logger.getLogger(LogAspect.class);

    @Pointcut("execution(@com.stellariver.milky.aspectj.tool.log.Log * *(..))")
    private void annoPointCut() {}

    @Pointcut("execution(* com.project.package..*.*(..))")
    private void customPointCut() {}

    @Around("annoPointCut() || customPointCut()")
    public Object valid(ProceedingJoinPoint pjp) throws Throwable {
        Object result = null;
        try {
            result = pjp.proceed();
        } catch (Throwable throwable) {
            throw throwable;
        } finally {
            log(xx,xx);
        }
        return result;
    }

}

I could not find any parameterized aspect solution in aspect project or aspect-maven-plugin project


Solution

  • In Java, annotation values must be compile-time constants, so you cannot change them. This is not an AspectJ issue, but a Java limitation.

    So what you do in AspectJ is to define your logging aspect as abstract and the package pointcut also as abstract:

    @Aspect
    public abstract class AutoLogAspect {
        // (...)
    
        @Pointcut("")
        public abstract void packagePC();
    
        // (...)
    }
    

    Then, in your demo project, you override the abstract aspect, defining a concrete pointcut:

    package com.example.log.demo;
    
    import com.stellariver.milky.aspectj.tool.log.AutoLogAspect;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    
    @Aspect
    public class MyLogAspect extends AutoLogAspect {
        @Pointcut("within(com.example.log..*) && execution(* *(..))")
        public void packagePC() {}
    }
    

    Now, you can remove the @Log annotation from your sample class:

    package com.example.log.demo;
    
    public class Main {
        public static void main(String[] args) {
            Fool fool = new Fool();
            fool.testLogOfPackage("Hello World");
        }
    
        public static class Fool {
            public void testLogOfPackage(String str) {
                System.out.println(str);
            }
        }
    }
    

    When compiling with Maven, you willl see:

    [INFO] --- aspectj-maven-plugin:1.14.0:compile (default) @ log-demo ---
    [INFO] Showing AJC message detail for messages of types: [error, warning, fail]
    [INFO] Join point 'method-execution(void com.example.log.demo.Main$Fool.testLogOfPackage(java.lang.String))' in Type 'com.example.log.demo.Main$Fool' (Main.java:10) advised by around advice from 'com.example.log.demo.MyLogAspect' (aspectj-tool-0.1.8-SNAPSHOT.jar!AutoLogAspect.class(from AutoLogAspect.java))
    [INFO] Join point 'method-execution(void com.example.log.demo.Main.main(java.lang.String[]))' in Type 'com.example.log.demo.Main' (Main.java:4) advised by around advice from 'com.example.log.demo.MyLogAspect' (aspectj-tool-0.1.8-SNAPSHOT.jar!AutoLogAspect.class(from AutoLogAspect.java))
    

    When running the sample program, the output will be:

    Hello World
    
    =====msg: execution(Main.Fool.testLogOfPackage(..))
    ==header: 09:12:00.329 [main-INFO ] com.stellariver.milky.aspectj.tool.log.AutoLogAspect:386 : [success:true] [cost:0] [result:]
    ===milky: [class:com.stellariver.milky.aspectj.tool.log.AutoLogAspect] [method:log] [line:66]
    ====args: [arg0:Hello World] [arg1:] [arg2:] [arg3:] [arg4:] [arg5:]
    
    =====msg: execution(Main.main(..))
    ==header: 09:12:00.334 [main-INFO ] com.stellariver.milky.aspectj.tool.log.AutoLogAspect:386 : [success:true] [cost:7] [result:]
    ===milky: [class:com.stellariver.milky.aspectj.tool.log.AutoLogAspect] [method:log] [line:66]
    ====args: [arg0:[Ljava.lang.String;@4ff4357f] [arg1:] [arg2:] [arg3:] [arg4:] [arg5:]
    

    See? There is no need to copy & paste the whole aspect. You simply make the package pointcut abstract and force consuming projects to override it. Clean and simple.

    P.S.: If the consuming project would use load-time weaving instead of compile-time weaving, you could define the overriding aspect in aop.xml directly, no need to compile a source aspect or use AspectJ Maven Plugin at all in the consuming project. But CTW or LTW, both works nicely, the rest is up to you.