【问题标题】:AspectJ annotation on field that triggers @Before advice触发@Before建议的字段上的AspectJ注释
【发布时间】:2014-09-19 09:02:37
【问题描述】:

我已经编写了执行由方法注释触发的@Around 建议的AspectJ 方面。现在我想做同样的事情,但是在字段中注释而不是方法。因此,对于下面类的每个方法调用,它必须将 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
   }
}

【问题讨论】:

  • 我通过创建两个注解解决了这个问题,一个类型注解 (ServiceClientAdapterConsumer) 和一个字段注解 (InjectServiceClientAdapter) 具有以下切入点:@Pointcut("within(@ServiceClientAdapterConsumer *) && execution(public * *(..))")。然后在@Before 建议中,我遍历带注释的类的字段,并为使用 InjectServiceClientAdapter 注释的任何字段注入正确的实现。没有类注释有没有办法做到这一点?

标签: java annotations field aspectj


【解决方案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-else 标准。

注意:不要忘记停用第一个方面,例如通过将if(false) &amp;&amp; 添加到其切入点。否则这两个方面会相互冲突。

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 实例),两个输出行都显示完全相同的对象 ID。

【讨论】:

  • 哇!这是迄今为止我在任何论坛上任何地方的任何问题的最佳答案。谢谢!
【解决方案2】:

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

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

【讨论】:

    猜你喜欢
    • 2018-06-28
    • 1970-01-01
    • 2011-09-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多