关于在注释字段上调用的方面

时间:2016-07-09 11:10:10

标签: java aop aspectj aspect

我希望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
  }

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

1 个答案:

答案 0 :(得分:1)

这不是您可以直接使用切入点执行的操作,因为您注意到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字段,但是在已经取消分配并由{{1}替换之后调用方法时则不会}。随后正确报告后者。您还可以看到方面如何在字符串MyClass[another measured]等原语中精美地工作。

享受!