从Aspect函数访问callee类的Data成员

时间:2014-06-17 09:03:40

标签: java spring aop aspectj spring-aop

我有一个DoStuff类,其中包含ServiceSAOServiceInput的实例作为其数据成员。每当我调用一个函数时,可以说setDetail(String),我已经建立了一个调用publishEvent()的建议。要调用publishEvent(),我需要DoStuff类的ServiceSAOServiceInput个实例。 问题是我如何从方面函数(ServiceSAO)访问被调用者类(ServiceInput)的数据成员(DoStuffpublishEvent)?

DoStuff.java

public class DoStuff{
     @Autowired
     private ServiceSAO serviceSAO;
     private ServiceInput serviceInput;
     void init(){
          serviceSAO = new serviceSAO();
          serviceInput = ServiceUtil.getServiceInput(hostname,"test",....);
      }



  @PublishEventToService
     public void hello(){
          serviceInput.setDetail("batman");
      }

}

PublishEventToServiceAspect.java

@Aspect
public class PublishEventToServiceAspect{
       @After("execution(* com.xyz.ServiceInput.setDetails(..)) && @annotation(PublishEventToService)")
       public void publishEvent()
{
    String detail = serviceInput.getDetails();   //how can I get serviceInput here??
    someFuntion(serviceSAO, serviceInput);            //even tougher would be to get the serviceSAO instance??
}
}

编辑1 请注意,我可以有各种DoStuff类。所有这些都可能想调用publishEvent(),因此我需要一个通用的方法来提取实例。

编辑2 我现在可以使用(ServiceInput)joinPoint.getTarget()来获取ServiceInput实例 有权访问ServiceSAO吗?

2 个答案:

答案 0 :(得分:2)

首先,免责声明:我对AspectJ了解很多,但对Spring却知之甚少。所以我将在这里给出一个纯粹的AspectJ示例。

接下来,更正:您声称要访问 被调用者 成员,而Serge Ballesta的答案也是关于被调用者(target()绑定)。但这与您在代码片段中描述的内容相矛盾,因为您不希望访问与被调用者(ServiceInput对象)相关的任何内容,而是 调用者的成员DoStuff object)。对于呼叫者,您需要this()绑定,而不是target()

然后是另一个微妙的问题:您尝试访问的成员实际上是私有的,即为了访问它们,您需要为他们提供公共getter或使用特权方面。我假设没有getter,因为你的代码片段没有显示任何内容。现在AspectJ存在一个限制:特权方面仅在本机AspectJ语法not in annotation-style @AspectJ syntax中可用。因此,我将在我的解决方案中使用本机语法。如果您确实有公共getter或者可以轻松地将它们添加到您的类中,您可以将方面转换为@AspectJ语法,而无需特权访问。

我进一步假设你不仅限于AOP lite",即Spring AOP,但愿意使用成熟的AspectJ(它也与Spring完全兼容)。

虚拟助手类:

package de.scrum_master.app;

public class ServiceSAO {
    private String id;
    public ServiceSAO(String id) { this.id = id; }
    @Override public String toString() { return "ServiceSAO [id=" + id + "]"; }
}

-

package de.scrum_master.app;

public class ServiceInput {
    private String id;
    private String detail;

    public ServiceInput(String id) { this.id = id; }
    public void setDetail(String detail) { this.detail = detail; }
    @Override public String toString() { return "ServiceInput [id=" + id + ", detail=" + detail + "]"; }
}

-

package de.scrum_master.app;

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

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

具有私有成员和示例主要方法的应用程序类:

package de.scrum_master.app;

public class DoStuff {
    private ServiceSAO serviceSAO;
    private ServiceInput serviceInput;

    void init() {
        serviceSAO = new ServiceSAO("SAO");
        serviceInput = new ServiceInput("Input");
    }

    @PublishEventToService
    public void hello() {
        serviceInput.setDetail("batman");
    }

//  @PublishEventToService
    public void otherMethod() {
        serviceInput.setDetail("foobar");
    }

    public static void main(String[] args) {
        DoStuff doStuff = new DoStuff();
        doStuff.init();
        doStuff.hello();
        doStuff.otherMethod();
    }
}

请注意,hello()已注释,但otherMethod()未注释(注释已注释掉)。即我们希望为前者触发建议,而不是后者。

访问私有成员的特权方面:

package de.scrum_master.aspect;

import de.scrum_master.app.ServiceInput;
import de.scrum_master.app.PublishEventToService;
import de.scrum_master.app.DoStuff;

public privileged aspect PublishEventToServiceAspect {
    pointcut setDetail(DoStuff caller) :
        call(* ServiceInput.setDetail(..)) &&
        cflow(execution(@PublishEventToService * DoStuff+.*(..))) &&
        this(caller);

    after(DoStuff caller) : setDetail(caller) {
        System.out.println(thisJoinPointStaticPart);        
        System.out.println("  " + caller.serviceSAO);       
        System.out.println("  " + caller.serviceInput);     
    }
}

正如您所看到的,我们在call()拦截ServiceInput.setDetail(..)方法,而不是execution(),因为我们想要访问来电者上下文中的某些内容,而不是被叫方'第另请注意,call()仅适用于AspectJ,而不适用于限制为execution()切入点的Spring AOP。

我们还希望确保只拦截当前执行的类cflow()或其子类的任何控制流(DoStuff)中的调用(因此" + "在DoStuff+中注明@PublishEventToService

最后但并非最不重要的是,我们希望确保将调用方(this())分配给名为DoStuff的{​​{1}}绑定,我们可以在建议中使用该绑定。

如果所有这些条件都成立,那么让我们打印加入点和来电者的私人成员,以证明它确实有效。

示例输出:

doStuff

这正是我们所期望的以及您所要求的。现在,如果我们从execution(void de.scrum_master.app.DoStuff.hello()) ServiceSAO [id=SAO] ServiceInput [id=Input, detail=batman] 的注释中删除注释,我们有两个带注释的方法并打印方面:

otherMethod()

享受并随意询问您是否理解或需要您之前未解释过的修改。

答案 1 :(得分:0)

AspecJ提供thistarget切入点指示符,它们可用作参数绑定。它们允许建议将目标对象(在您的情况下为serviceInput)作为参数。

语法:

@After("execution(* com.xyz.ServiceInput.setDetails(..)) && @annotation(PublishEventToService)") && target(serviceInput)
public void publishEvent(ServiceInput serviceInput))
{
    String detail = serviceInput.getDetails();   // should work fine
    //someFuntion(serviceSAO, serviceInput);     // but no use for serviceSAO instance
}

无论如何,你的服务应该是单身,所以你可以通过依赖注入来获得它们。