【问题标题】:Aspect to find all the pointcuts executed at runtime方面查找在运行时执行的所有切入点
【发布时间】:2019-01-21 22:25:22
【问题描述】:

我需要编写一个切面(我们称之为 A)来显示运行时执行的所有切入点。有没有办法编写像 call(...) 这样的切入点,直接指向另一个切入点而不使用方面的名称?

我写的东西主要使用对通用函数和 inside() 的调用,这样当一个方面中的一个函数被调用时,我的方面 A 会打印一些东西。我觉得这不是一个理想的解决方案,因为我总是需要写下所有方面的名称,而且很多方面都会很长。

public class Main {
    public static void main(String[] args) {
        ClassA a = new ClassA();
        a.methodA();
        a.methodA();
        a.methodB();
        a.methodA();
    }
}

public class ClassA {
    public void methodA() {
            System.out.println("MethodA");
    }
    public void methodB() {
        System.out.println("MethodB");
    }
    public void methodC() {
        System.out.println("MethodC");
    }
}

public aspect MethodAAspect {
    pointcut MethA():
        call(public * ClassA.methodA());
    pointcut MethC():
        call(public * ClassA.methodC());
    after():
        MethA() {
            System.out.println("Aspect here, methodA ended.");
        }
    after():
        MethC() {
            System.out.println("Aspect here, methodC ended.");
        }       
}

如果在这个例子中,我需要一个方面来计算所有切入点被执行了多少次,或者在执行切入点时打印一些东西,我应该如何编写它?

【问题讨论】:

    标签: java aspectj pointcut


    【解决方案1】:

    我不确定我是否正确理解您想要实现的目标,因为您的描述有些不准确。例如,切入点不是“执行”的,应用程序方法或方面内的通知是。所以我不确定您是否只想记录每个方法调用(或者可能是方法执行,请参见下面的区别)或者可能需要某种元方面来计算方面建议被执行的次数。我在这里假设前一种更简单的情况,因为它对我来说最有意义。

    您的应用程序代码,但带有包名称:

    package de.scrum_master.app;
    
    public class ClassA {
      public void methodA() {
        System.out.println("MethodA");
      }
    
      public void methodB() {
        System.out.println("MethodB");
      }
    
      public void methodC() {
        System.out.println("MethodC");
      }
    }
    
    package de.scrum_master.app;
    
    public class Main {
      public static void main(String[] args) {
        ClassA a = new ClassA();
        a.methodA();
        a.methodA();
        a.methodB();
        a.methodA();
      }
    }
    

    切面拦截方法执行:

    这方面会拦截您自己代码中的所有方法执行(不是调用!)。

    package de.scrum_master.aspect;
    
    public aspect MyAspect {
      after() : execution(* *(..)) {
        System.out.println(thisJoinPoint);
      }
    }
    

    日志输出为:

    MethodA
    execution(void de.scrum_master.app.ClassA.methodA())
    MethodA
    execution(void de.scrum_master.app.ClassA.methodA())
    MethodB
    execution(void de.scrum_master.app.ClassA.methodB())
    MethodA
    execution(void de.scrum_master.app.ClassA.methodA())
    execution(void de.scrum_master.app.Main.main(String[]))
    

    请注意,Main.main(..) 的执行也会被记录,即使该方法从未被显式调用(但仍然执行!)。

    切面拦截方法执行:

    这方面会拦截您自己代码中的所有方法调用,其中还包括对第三方或 JDK 类的调用。

    请注意,为了避免死循环,我们必须添加&& !within(MyAspect),因为方面通知也调用了 JDK 方法。

    package de.scrum_master.aspect;
    
    public aspect MyAspect {
      after() : call(* *(..)) && !within(MyAspect) {
        System.out.println(thisJoinPoint);
      }
    }
    

    本例的日志输出为:

    MethodA
    call(void java.io.PrintStream.println(String))
    call(void de.scrum_master.app.ClassA.methodA())
    MethodA
    call(void java.io.PrintStream.println(String))
    call(void de.scrum_master.app.ClassA.methodA())
    MethodB
    call(void java.io.PrintStream.println(String))
    call(void de.scrum_master.app.ClassA.methodB())
    MethodA
    call(void java.io.PrintStream.println(String))
    call(void de.scrum_master.app.ClassA.methodA())
    

    当然您也可以使用call() 并限制对您自己的包的调用,例如:

    package de.scrum_master.aspect;
    
    public aspect MyAspect {
      after() : call(* de.scrum_master.app..*(..)) && !within(MyAspect) {
        System.out.println(thisJoinPoint);
      }
    }
    

    这里的日志输出是:

    MethodA
    call(void de.scrum_master.app.ClassA.methodA())
    MethodA
    call(void de.scrum_master.app.ClassA.methodA())
    MethodB
    call(void de.scrum_master.app.ClassA.methodB())
    MethodA
    call(void de.scrum_master.app.ClassA.methodA())
    

    这始终取决于您想要实现的目标。

    如果这不是您想要的,请更新您的问题以更准确,并通过评论通知我。然后我看看我能做什么。


    关于元方面的后续问题更新:

    package de.scrum_master.aspect;
    
    public aspect MetaAspect {
      before() : adviceexecution() && !within(MetaAspect) {
        System.out.println(thisJoinPoint);
      }
    }
    

    使用execution() 记录第一个MyAspect 版本:

    MethodA
    adviceexecution(void de.scrum_master.aspect.MyAspect.after(JoinPoint))
    execution(void de.scrum_master.app.ClassA.methodA())
    MethodA
    adviceexecution(void de.scrum_master.aspect.MyAspect.after(JoinPoint))
    execution(void de.scrum_master.app.ClassA.methodA())
    MethodB
    adviceexecution(void de.scrum_master.aspect.MyAspect.after(JoinPoint))
    execution(void de.scrum_master.app.ClassA.methodB())
    MethodA
    adviceexecution(void de.scrum_master.aspect.MyAspect.after(JoinPoint))
    execution(void de.scrum_master.app.ClassA.methodA())
    adviceexecution(void de.scrum_master.aspect.MyAspect.after(JoinPoint))
    execution(void de.scrum_master.app.Main.main(String[]))
    

    【讨论】:

    • 嘿,感谢您用如此详细的解释回答我的问题。我试图解决的问题是一个学术问题,所以它们并不总是有意义的,事实上,我试图实现的是创建一个元方面,每当执行来自另一个方面的建议时,它就会计数/记录/做某事。谢天谢地,我误解了教授的要求,而要求是按照您的解释进行。但是,我仍然很好奇像元方面这样的东西是否可能以及它是否有用。
    • 是的,这是可能的,请参阅我的更新,也可以阅读一些 AspectJ 文档。您的问题都在那里得到解答。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-02-27
    • 2015-05-15
    • 1970-01-01
    • 1970-01-01
    • 2011-12-09
    • 2013-04-14
    • 1970-01-01
    相关资源
    最近更新 更多