无论如何要创建一个类的方法的切入点'成员?

时间:2014-07-17 01:02:21

标签: java aop aspectj pointcut

给定一个包含一堆成员的类,每个成员都有自己的getter / setter / etc方法,有没有办法设计一个只会触发成员的切入点。包含在父类中的方法?

例如:

public MyClass{
   List myList = new ArrayList<String>();
}

如果我想创建一个切入点来建议myList.add(),有没有办法做到这一点?我不希望建议所有ArrayList.add()调用。仅限属于MyClass成员的Collections.add()。

我尝试过玩withincflow,但无济于事:

pointcut addPointcut() : cflow( execution( * *.getMyList() ) ) && call( * *.add(..));

但它似乎不起作用。我认为假设add()调用实际上不是get()控制流的一部分,它似乎没有正确触发。

经过多次游戏,我发现以下解决方案似乎有效:

pointcut addPointcut(): within( MyClass ) && call( * *.add(..) );

这是正确的实施吗?

我试图将切入点限制为仅在传递@Entity对象时建议调用add(),但它不起作用。例如:

pointcut addEntityPointcut(): within( MyClass ) && call( * *.add(@javax.persistence.Entity *) );

然而addPointcut()在使用@Entity作为参数调用时有效。

参数类型是基于实际的调用方法,还是基于add()签名?

修改

我太快就错误地得出了错误的结论。睡了之后,我才意识到我的切入点不起作用。

public class FirstClass{
   List<String> strings = new ArrayList<>();
   // getters and setters
}

public class Execute{

    public main(){
      FirstClass fc = new FirstClass();
      fc.getStrings().add( "This call is advised" );   // <---- Is there any way to advise this add() method?

      List<String> l = new ArrayList<>();
      l.add( "This call is not advised" );   // <---- this one should not be advised
    }
}

我正在寻找一种方法来建议从任何类调用的add()方法。但是,我只想在FirstClass中包含的成员List上建议add()方法,即使从FirstClass外部调用也是如此。

1 个答案:

答案 0 :(得分:2)

  

参数类型是基于实际的调用方法,还是基于add()签名?

在AspectJ中call()切入点,您需要指定方法或构造函数签名。在这种情况下,add()方法没有@Entity注释的任何参数,因此您尝试执行的操作不起作用。这是使用反射的解决方法:

示例注释:

package de.scrum_master.app;

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

@Retention(RetentionPolicy.RUNTIME)
public @interface Entity {}

示例实体:

package de.scrum_master.app;

@Entity
public class MyEntity {}

驱动程序应用程序:

package de.scrum_master.app;

import java.util.ArrayList;
import java.util.List;

public class Application {
    List<Object> myList = new ArrayList<>();

    public static void main(String[] args) {
        Application application = new Application();
        application.myList.add("foo");
        application.myList.add(new MyEntity());
        application.myList.add("bar");
        application.myList.add(new MyEntity());
    }
}

<强>方面:

package de.scrum_master.aspect;

import de.scrum_master.app.Application;
import de.scrum_master.app.Entity;

public aspect EntityAddInterceptor {
    pointcut addEntity(Object addedObject) :
        within(Application) && call(* *.add(*)) && args(addedObject);

    before(Object addedObject) : addEntity(addedObject) {
        if (addedObject.getClass().isAnnotationPresent(Entity.class))
            System.out.println(thisJoinPointStaticPart + " -> " + addedObject);
    }
}

<强>输出:

call(boolean java.util.List.add(Object)) -> de.scrum_master.app.MyEntity@19dc6592
call(boolean java.util.List.add(Object)) -> de.scrum_master.app.MyEntity@54906181

至于控制流匹配变体,我认为从命名角度来看,假设getMyList()不添加任何内容,只返回一个列表是有意义的。可能你宁愿做application.getMyList().add("foo")这样的事情,在这种情况下,add()实际上在getMyList()的控制流之外(之后),因为它对其结果起作用。

如果OTOH你有一个假设的方法addToList(Object element)真的叫add()你可以使用cflow()。让我们修改代码示例:

修改过的驱动程序应用程序:

package de.scrum_master.app;

import java.util.ArrayList;
import java.util.List;

public class Application {
    List<Object> myList = new ArrayList<>();

    public void addToMyList(Object element) { reallyAddToMyList(element); }
    private void reallyAddToMyList(Object element) { myList.add(element); }

    public static void main(String[] args) {
        Application application = new Application();
        application.myList.add("foo");
        application.myList.add(new MyEntity());
        application.addToMyList("bar");
        application.addToMyList(new MyEntity());
    }
}

修改后的方面:

package de.scrum_master.aspect;

import de.scrum_master.app.Entity;

public aspect EntityAddInterceptor {
    pointcut addEntity(Object addedObject) :
        cflow(execution(* *.addToMyList(*))) && (call(* *.add(*)) && args(addedObject));

    before(Object addedObject) : addEntity(addedObject) {
        if (addedObject.getClass().isAnnotationPresent(Entity.class))
            System.out.println(thisJoinPointStaticPart + " -> " + addedObject);
    }
}

新输出:

call(boolean java.util.List.add(Object)) -> de.scrum_master.app.MyEntity@323ba00

如您所见,只记录了一个呼叫。它来自reallyAddToMyList(),而不是来自main()的那个。


更新2014-07-21 - 更好的方面修改:

这个更优雅的解决方案的功劳归到了AspectJ mailing list上提到它的Andy Clement(AspectJ维护者)。它显示了我上面的两个变体,但使用的是&& @args(Entity)而不是if (addedObject.getClass().isAnnotationPresent(Entity.class))

package de.scrum_master.aspect;

import de.scrum_master.app.Application;
import de.scrum_master.app.Entity;

public aspect EntityAddInterceptor {
    pointcut addEntity(Object addedObject) :
        within(Application) && call(* *.add(*)) && args(addedObject) && @args(Entity);

    before(Object addedObject) : addEntity(addedObject) {
        System.out.println(thisJoinPointStaticPart + " -> " + addedObject);
    }

    pointcut addEntitySpecial(Object addedObject) :
        cflow(execution(* *.addToMyList(*))) && (call(* *.add(*)) && args(addedObject))  && @args(Entity);

    before(Object addedObject) : addEntitySpecial(addedObject) {
        System.out.println(thisJoinPointStaticPart + " -> " + addedObject + " [special]");
    }
}

两种变体的输出活动如下:

call(boolean java.util.List.add(Object)) -> de.scrum_master.app.MyEntity@229ff6d1
call(boolean java.util.List.add(Object)) -> de.scrum_master.app.MyEntity@1976bf9e
call(boolean java.util.List.add(Object)) -> de.scrum_master.app.MyEntity@1976bf9e [special]