围绕带注释字段的调用的方面 [英] Aspect around call on annotated field

查看:109
本文介绍了围绕带注释字段的调用的方面的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I want AspectJ to inject the measuring code around all invocations of any method, on fields annotated with @Measured and capture the method's name. This is what I have:

@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
  }

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

This seems to surround only the access to the field, not the method invocation. I want to capture the invoked method name instide the advise.

推荐答案

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

This is not something you can do directly with a pointcut because get(), as you have noticed, is entirely different from call() or execution() pointcuts. The get() joinpoint has completely passed before the moment the call() is done. Furthermore, the call() has no idea about whether or not the target object it is called upon happens to be assigned to one or more (annotated) class members.

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

I think that what you want to achieve is conceptually problematic. You should rather annotate the classes or methods you want to measure, not class members. But for what it is worth, I am going to present you with a solution. Caveat: The solution involves manual bookkeeping and also reflection. Thus, it is kinda slow but maybe still fast enough for your purpose. You can decide if you give it a try. Please note that this solution is something I feel uneasy with because it does not feel like a good application of AOP.

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

Okay, so here is our test setup:

字段注释:

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注释,而另外两个则没有.我这样做的目的是为了获得正面和负面的例子,以查看方面是否正常工作.

Please note how only two of the four members - one primitive and one object type - are annotated by @Measured and two others are not. I did this in order to have positive as well as negative examples so as to see whether the aspect works correctly.

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

Another important thing is that an object formerly assigned to an annotated class member should no longer be reported by the aspect as soon as it is no longer assigned to that member. I.e. oldMyClass.waitForAWhile(); should not be measured.

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();
    }
}

方面:

该方面要注意两件事:簿记和度量.详细信息:

The aspect takes care of two things: bookkeeping and measuring. In detail:

  • 每当将一个值分配给@Measured字段时,它就会记录在一组measuredObjects中,因为这是以后知道在该对象上调用某个方法时实际上应该对其进行测量的唯一方法.
  • 虽然在before() : set()通知中很容易掌握新值,但是不幸的是,没有简单的方法能掌握旧值.这就是为什么我们需要使用反射的丑陋小辅助方法getField(Signature signature)来找出原因的原因.
  • 为什么我们仍然需要旧的价值?因为要保持干净的簿记,我们必须从measuredObjects集中删除未分配的对象.
  • 请注意,measuredObjects并不是我实现它的线程安全方式,但是如果需要,您可以只使用同步集合.
  • call()建议首先检查它是否可以在measuredObjects中找到目标对象,如果找不到,则停止执行.否则,它将测量方法调用的运行时.这很简单.
  • Whenever a value gets assigned to a @Measured field, it is recorded in a set of measuredObjects because this is the only way to later know that when a method is called on that object, it really should be measured.
  • While it is easy to get hold of the new value in a before() : set() advice, unfortunately there is no straightforward way to get hold of the old value. This is why we need the ugly little helper method getField(Signature signature) using reflection in order to find out.
  • Why do we need the old value anyway? Because in order to have clean bookkeeping we have to remove unassigned objects from the measuredObjects set.
  • Please also note that measuredObjects is not thread-safe the way I implemented it, but you can just use a synchronized collection if you need that.
  • The call() advice first checks if it can find the target object in measuredObjects and stops executing if it cannot. Otherwise it measures the method call's runtime. This is straightforward.

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

Oh, and by the way, I am using the cleaner and more expressive native AspectJ syntax here, not the ugly annotation style. If you have any problems with that, please let me know.

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[measured]的方法调用,但是在取消分配并由MyClass[another measured]替换后调用该方法时,不会报告该方法调用.随后会正确报告后者.您还将看到该方面如何工作,即使对于诸如String "measured"之类的基元也是如此.

As you can see, the aspect behaves correctly. It only reports the method call on object MyClass[measured] once, while it is assigned to a @Measured field, but not when a method is called upon it after it has already been unassigned and replaced by MyClass[another measured]. The latter is correctly reported subsequently. You also see how the aspect works beautifully even for primitives like the String "measured".

享受!

这篇关于围绕带注释字段的调用的方面的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆