触发@Before建议的字段上的AspectJ注释

时间:2014-09-19 09:02:37

标签: java annotations field aspectj

我已经编写了AspectJ方面来执行由方法注释触发的@Around建议。现在我想做同样的事情,但是注释字段而不是方法。因此,对于下面的类的每个方法调用,它必须将accountSummary字段设置为正确的实现。有没有办法实现这个目标?我认为使用@Before建议是最好的方法。使用CDI不是一种选择 - 解决方案必须使用AspectJ。

public class PoolableBusinessLogic {
   @InjectServiceClientAdapter(legacy=LegacyAccountSummary.class,new=NewAccountSummary.class)
   private AccountSummary accountSummary;

   public void foo() {
      // use correct accountSummary impl, decided in @Before code
   }

   public void bar() {
      // use correct accountSummary impl, decided in @Before code
   }
}

2 个答案:

答案 0 :(得分:1)

我不确定你想要达到什么目标,所以我提出了两种替代解决方案。

首先,让我们创建一些应用程序类,以获得完全可编译的样本:

package de.scrum_master.app;

public interface AccountSummary {
    void doSomething();
}
package de.scrum_master.app;

public class LegacyAccountSummary implements AccountSummary {
    @Override
    public void doSomething() {
        System.out.println("I am " + this);
    }
}
package de.scrum_master.app;

public class NewAccountSummary implements AccountSummary {
    @Override
    public void doSomething() {
        System.out.println("I am " + this);
    }
}
package de.scrum_master.app;

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

@Retention(RetentionPolicy.RUNTIME)
public @interface InjectServiceClientAdapter {
    Class<?> legacyImpl();
    Class<?> newImpl();
}
package de.scrum_master.app;

public class PoolableBusinessLogic {
    @InjectServiceClientAdapter(legacyImpl = LegacyAccountSummary.class, newImpl = NewAccountSummary.class)
    private AccountSummary accountSummary;

    public void foo() {
        accountSummary.doSomething();
    }

    public void bar() {
        System.out.println("Account summary is " + accountSummary);
    }
}

现在我们需要一个入口点

package de.scrum_master.app;

public class Application {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            PoolableBusinessLogic businessLogic = new PoolableBusinessLogic();
            businessLogic.foo();
            businessLogic.bar();
            System.out.println();
        }
    }
}

显然这会产生错误,因为成员accountSummary尚未初始化:

Exception in thread "main" java.lang.NullPointerException
    at de.scrum_master.app.PoolableBusinessLogic.foo(PoolableBusinessLogic.java:8)
    at de.scrum_master.app.Application.main(Application.java:7)

现在我们有两个选择,具体取决于您想要实现的目标:

选项A:动态注入

场景:对于每个字段访问(即使在相同的PoolableBusinessLogic实例中),动态决定返回哪种类型的对象实例。在这个例子中,我将随机化以模拟另一个if-else标准。

顺便说一句,我希望我可以使用更具表现力的原生AspectJ语法。您可以轻松地将方面转换为注释样式。

package de.scrum_master.aspect;

import java.util.Random;
import org.aspectj.lang.SoftException;
import de.scrum_master.app.InjectServiceClientAdapter;

public aspect DynamicInjectionAspect {
    private static final Random RANDOM = new Random();

    Object around(InjectServiceClientAdapter adapterAnn) :
        get(* *) && @annotation(adapterAnn)
    {
        try {
            Class<?> implClass = RANDOM.nextBoolean() ? adapterAnn.legacyImpl() : adapterAnn.newImpl();
            return implClass.newInstance();
        } catch (Exception e) {
            throw new SoftException(e);
        }
    }
}

这会产生以下输出:

I am de.scrum_master.app.LegacyAccountSummary@4d9cfefb
Account summary is de.scrum_master.app.NewAccountSummary@7e28388b

I am de.scrum_master.app.NewAccountSummary@2986e62
Account summary is de.scrum_master.app.LegacyAccountSummary@6576e542

I am de.scrum_master.app.NewAccountSummary@60c58418
Account summary is de.scrum_master.app.LegacyAccountSummary@4763754a

I am de.scrum_master.app.NewAccountSummary@52a971e3
Account summary is de.scrum_master.app.NewAccountSummary@7274187a

I am de.scrum_master.app.LegacyAccountSummary@23f32c4a
Account summary is de.scrum_master.app.LegacyAccountSummary@31e0c0b6

正如您所看到的,在五个输出组中的每一个(即每个PoolableBusinessLogic实例)中都有不同的帐户摘要对象ID,有时(并非总是)甚至是不同的类名。

选项B:静态注入

场景:如果PoolableBusinessLogic实例的值为null,则每个if(false) &&实例动态决定静态分配给注释成员的对象实例类型。之后,不要再覆盖该成员,而是返回先前初始化的值。我将再次随机化以模拟另一个if-else标准。

注意:不要忘记停用第一个方面,例如:通过将package de.scrum_master.aspect; import java.lang.reflect.Field; import java.util.Random; import org.aspectj.lang.SoftException; import de.scrum_master.app.InjectServiceClientAdapter; public aspect StaticInjectionAspect { private static final Random RANDOM = new Random(); before(InjectServiceClientAdapter adapterAnn, Object targetObj) : get(* *) && @annotation(adapterAnn) && target(targetObj) { try { Field field = targetObj.getClass().getDeclaredField(thisJoinPoint.getSignature().getName()); field.setAccessible(true); if (field.get(targetObj) != null) return; Class<?> implClass = RANDOM.nextBoolean() ? adapterAnn.legacyImpl() : adapterAnn.newImpl(); field.set(targetObj,implClass.newInstance()); } catch (Exception e) { throw new SoftException(e); } } } 添加到其切入点。否则这两个方面将相互冲突。

I am de.scrum_master.app.NewAccountSummary@20d1fa4
Account summary is de.scrum_master.app.NewAccountSummary@20d1fa4

I am de.scrum_master.app.NewAccountSummary@2b984909
Account summary is de.scrum_master.app.NewAccountSummary@2b984909

I am de.scrum_master.app.LegacyAccountSummary@1ae3043b
Account summary is de.scrum_master.app.LegacyAccountSummary@1ae3043b

I am de.scrum_master.app.LegacyAccountSummary@2e2acb47
Account summary is de.scrum_master.app.LegacyAccountSummary@2e2acb47

I am de.scrum_master.app.LegacyAccountSummary@7b87b9fe
Account summary is de.scrum_master.app.LegacyAccountSummary@7b87b9fe

这有点丑陋,因为它涉及使用反射来查找成员字段。因为它可能(并且在我们的示例中确实是)私有的,所以我们需要在使用它之前使其可访问。

这会产生以下输出:

PoolableBusinessLogic

现在输出看起来不同:在五个输出组中的每一个(即每个{{1}}实例)中,两个输出行都显示完全相同的对象ID。

答案 1 :(得分:0)

对于选项A:动态注入在kriegaex的回答中,注释风格的方面将如下所示:

@Aspect
public class InjectServiceClientAdapterAspect {
    @Pointcut("get(* *) && @annotation(injectAnnotation)")
    public void getServiceClientAdapter(InjectServiceClientAdapter injectAnnotation) {
    }

    @Around("getServiceClientAdapter(injectAnnotation)")
    public Object injectServiceClientAdapter(final ProceedingJoinPoint joinPoint, final InjectServiceClientAdapter injectAnnotation) {      
       // injection code goes here                                          
}