我希望AspectJ在任何用@Measured注释的字段上将所有方法的调用周围注入测量代码,并捕获该方法的名称。
这就是我所拥有的:

@Pointcut("get(@my.annotation.Measured * *) && @annotation(measured)")
public void fieldAnnotatedWithMeasured(Measured measured) {}

@Around(value = "fieldAnnotatedWithMeasured(measured)", argNames = "joinPoint,measured")
public Object measureField(ProceedingJoinPoint joinPoint, Measured measured) throws Throwable {...}


用例:

public class A {

  @Measured private Service service;
  ...
  void call(){
    service.call(); // here I want to measure the call() time and capture its name
  }


这似乎只包含对字段的访问,而不是方法调用。我想捕获建议中的调用方法名称。

最佳答案

这不是您可以直接使用切入点执行的操作,因为您已经注意到,get()call()execution()切入点完全不同。 get()连接点在call()完成之前已完全通过。此外,call()不知道调用它的目标对象是否恰好被分配给一个或多个(带注释)类成员。

我认为您想要实现的目标在概念上是有问题的。您应该注释要测量的类或方法,而不是类成员。但是,对于它的价值,我将为您提供解决方案。注意:解决方案包括手动记账和反思。因此,它有点慢,但也许仍然足够快以达到您的目的。您可以决定是否尝试一下。请注意,这种解决方案让我感到不舒服,因为它感觉不像是AOP的良好应用。

好的,这是我们的测试设置:

字段注释:

package de.scrum_master.app;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Measured {}


示例类,稍后再玩:

package de.scrum_master.app;

public class MyClass {
    private String name;

    public MyClass(String name) {
        super();
        this.name = name;
    }

    @Override
    public String toString() {
        return "MyClass[" + name + "]";
    }

    void waitForAWhile() throws InterruptedException {
        Thread.sleep(200);
    }
}


使用示例类的驱动程序应用程序:

请注意,四个成员中只有两个-一个原始类型和一个对象类型-如何用@Measured注释,而另外两个则没有。我这样做的目的是为了拥有正面和负面的例子,以查看方面是否正常工作。

另一个重要的事情是,一旦不再将以前分配给带注释的类成员的对象分配给该对象,则方面将不再报告该对象。即oldMyClass.waitForAWhile();不应测量。

package de.scrum_master.app;

public class Application {
    String foo = "unmeasured";
    @Measured String bar = "measured";
    MyClass myClass1 = new MyClass("unmeasured");
    @Measured MyClass myClass2 = new MyClass("measured");

    void doSomething() throws InterruptedException {
        foo.length();
        bar.length();
        myClass1.waitForAWhile();
        myClass2.waitForAWhile();

        MyClass oldMyClass = myClass2;
        myClass2 = new MyClass("another measured");
        // This call should not be reported by the aspect because the object
        // is no longer assigned to a member annotated by @Measured
        oldMyClass.waitForAWhile();
        // This call should be reported for the new member value
        myClass2.waitForAWhile();
    }

    public static void main(String[] args) throws InterruptedException {
        new Application().doSomething();
    }
}


方面:

该方面照顾两件事:簿记和度量。详细地:


每当将值分配给@Measured字段时,它都会记录在一组measuredObjects中,因为这是以后知道在该对象上调用方法时实际上应该对其进行测量的唯一方法。
虽然在before() : set()通知中很容易掌握新值,但是不幸的是,没有直接的方法能掌握旧值。这就是为什么我们需要使用反射的丑陋小辅助方法getField(Signature signature)来找出原因的原因。
为什么我们仍然需要旧的价值?因为要保持干净的簿记,我们必须从measuredObjects集中删除未分配的对象。
还请注意,measuredObjects并不是我实现它的线程安全方式,但是如果需要,您可以仅使用同步集合。
call()建议首先检查是否可以在measuredObjects中找到目标对象,如果找不到,则停止执行。否则,它将测量方法调用的运行时。这很简单。


哦,顺便说一句,我在这里使用的是更简洁,更具表现力的本机AspectJ语法,而不是丑陋的注释样式。如果您对此有任何疑问,请告诉我。

package de.scrum_master.app;

import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Set;

import org.aspectj.lang.Signature;
import org.aspectj.lang.SoftException;

import de.scrum_master.app.Measured;

public aspect MyAspect {
    private Set<Object> measuredObjects = new HashSet<>();

    before(Measured measured, Object newValue, Object object) :
        set(* *) &&
        @annotation(measured) &&
        args(newValue) &&
        target(object)
    {
        try {
            Field field = getField(thisJoinPoint.getSignature());
            Object oldValue = field.get(object);
            System.out.println(thisJoinPoint);
            System.out.println("  old value = " + oldValue);
            System.out.println("  new value = " + newValue);
            measuredObjects.remove(oldValue);
            measuredObjects.add(newValue);
        }
        catch (Exception e) {
            throw new SoftException(e);
        }
    }

    Object around(Object object) :
        call(* *(..)) &&
        target(object) &&
        !within(MyAspect)
    {
        if (!measuredObjects.contains(object))
            return proceed(object);
        long startTime = System.nanoTime();
        Object result = proceed(object);
        System.out.println(thisJoinPoint);
        System.out.println("  object   = " + object);
        System.out.println("  duration = " + (System.nanoTime() - startTime) / 1e6 + " ms");
        return result;
    }

    private Field getField(Signature signature) throws NoSuchFieldException {
        Field field = signature.getDeclaringType().getDeclaredField(signature.getName());
        field.setAccessible(true);
        return field;
    }
}


控制台日志:

set(String de.scrum_master.app.Application.bar)
  old value = null
  new value = measured
set(MyClass de.scrum_master.app.Application.myClass2)
  old value = null
  new value = MyClass[measured]
call(int java.lang.String.length())
  object   = measured
  duration = 0.080457 ms
call(void de.scrum_master.app.MyClass.waitForAWhile())
  object   = MyClass[measured]
  duration = 200.472326 ms
set(MyClass de.scrum_master.app.Application.myClass2)
  old value = MyClass[measured]
  new value = MyClass[another measured]
call(void de.scrum_master.app.MyClass.waitForAWhile())
  object   = MyClass[another measured]
  duration = 200.461208 ms


如您所见,方面的行为正确。在将对象MyClass[measured]分配给@Measured字段时,它仅报告一次对对象MyClass[another measured]的方法调用,但是在取消分配并由"measured"替换后调用该方法时,不会报告该方法调用。随后会正确报告后者。您还将看到该方面如何工作,即使对于String 之类的基元也是如此。

请享用!

09-10 22:20