AOP切入点的参数有一个带有注释的字段?

时间:2019-06-23 13:35:36

标签: spring spring-aop

问题:当某个方法使用特定类型的参数调用时,我想使用AOP手动调用一个方法。

现在,我可以通过两种不同的方式执行此操作: 1.'当使用特定类型参数调用某个方法时,使用AOP手动调用方法'。然后通过从连接点反射获取带注释的字段。

2。或使用字段名称作为注释值注释类型本身

但是除了这些,我该如何立即将它们放在切入点表达式中以检查带注释的字段是否存在?

示例:

class  A {
}

class B extends A{
  someField;
}

class C extends A{
  @CustomAnnotation
  someField;
}

有一些我想采取“先行”行动的重载方法: 像这样:

  public void doSomething(A a);
  public void doSomething(X x);

使用以下切入点,我可以在参数类型为A时捕获动作:

    @Pointcut("execution(* somePackage.doSomething((A)))")
    public void customPointCut() {
    }

    @Before("customPointCut()")
    public void customAction(JoinPoint joinPoint) throws Throwable{   
              //examining fields with reflection whether they are annotated or not
              //action
    }

使用此解决方案,可以同时捕获B类和C类。 我要完成的工作是将以下代码行放入切入点表达式中:

  

“通过反射检查字段是否被注释”

所以只有C类会被捕获。
像这样的东西:@Pointcut("execution(* somePackage.doSomething((A.fieldhas(@CustomAnnotation))))")

edit2:对于需求部分:我必须覆盖该值(它是一个私有字段,但是具有一个公共设置程序)。

1 个答案:

答案 0 :(得分:1)

好的,即使问了几次之后,我也没有清楚的答案,您何时何地想要操作字段值。因此,我向您展示了三种不同的方式。。所有方法都涉及使用成熟的AspectJ ,而且我还将使用本机语法,因为我要向您展示的第一种方式不起作用以注释样式的语法。您需要使用AspectJ编译器来编译方面。无论是在编译时还是通过加载时将其编织到应用程序代码中,都取决于您。我的解决方案在没有Spring的情况下完全可以运行,但是如果您是Spring用户,则可以将其与Spring结合使用,甚至与Spring AOP混合使用。请阅读Spring手册以获取更多说明。

我在示例代码中向您展示的方式是:

  1. 类型间声明(ITD):这是最复杂的方法,它使用hasfield()切入点指示符。为了使用它,需要使用特殊标志-XhasMember调用AspectJ编译器。在安装了AJDT的Eclipse中,该设置在项目设置中的“ AspectJ Compiler”,“其他”下命名为“具​​有成员”。我们在这里做的是:

    • 使所有带注释字段的类都实现标记接口HasMyAnnotationField
    • 每次调用具有实现接口的参数类型的方法时,都会在控制台上打印一些内容,并且可以选择通过反射操作字段值,这可能与您自己的解决方案类似。
  2. 通过set()建议在写访问期间操纵字段值。这将持续更改字段值,并且不需要任何带有标记接口,特殊编译器标志和反射的ITD,如解决方案1。

  3. 通过get()建议透明地操作从字段读取访问返回的值。该字段本身保持不变。

可能您想要#2或#3,出于完整性考虑,我正在显示解决方案#1。

足够多的话,这里是完整的MCVE

字段注释:

package de.scrum_master.app;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(RUNTIME)
@Target(FIELD)
public @interface MyAnnotation {}

使用字段注释对类进行采样:

package de.scrum_master.app;

public class MyClass {
  private int id;
  @MyAnnotation
  private String name;

  public MyClass(int id, String name) {
    this.id = id;
    this.name = name;
  }

  @Override
  public String toString() {
    return "MyClass [id=" + id + ", name=" + name + "]";
  }
}

驱动程序应用程序:

package de.scrum_master.app;

public class Application {
  public void doSomething() {}

  public void doSomethingElse(int i, String string) {}

  public void doSomethingSpecial(int i, MyClass myClass) {
    System.out.println("  " + myClass);
  }

  public int doSomethingVerySpecial(MyClass myClass) {
    System.out.println("  " + myClass);
    return 0;
  }

  public static void main(String[] args) {
    Application application = new Application();
    MyClass myClass1 = new MyClass(11, "John Doe");
    MyClass myClass2 = new MyClass(11, "Jane Doe");
    for (int i = 0; i < 3; i++) {
      application.doSomething();
      application.doSomethingElse(7, "foo");
      application.doSomethingSpecial(3, myClass1);
      application.doSomethingVerySpecial(myClass2);
    }
  }
}

没有任何方面的控制台日志:

  MyClass [id=11, name=John Doe]
  MyClass [id=11, name=Jane Doe]
  MyClass [id=11, name=John Doe]
  MyClass [id=11, name=Jane Doe]
  MyClass [id=11, name=John Doe]
  MyClass [id=11, name=Jane Doe]

这里不足为奇。我们创建了两个MyClass对象并调用了一些Application方法,其中只有两个实际上具有MyClass参数(即参数类型,其中至少一个字段用MyAnnotation注释)。我们希望当方面开始时会发生一些事情。但是在编写方面之前,我们首先需要其他方面:

具有@MyAnnotation字段的类的标记界面:

package de.scrum_master.app;

public interface HasMyAnnotationField {}

这是我们的方面:

方面显示了三种处理字段值的方式:

package de.scrum_master.aspect;

import java.lang.reflect.Field;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.SoftException;
import org.aspectj.lang.reflect.MethodSignature;

import de.scrum_master.app.HasMyAnnotationField;
import de.scrum_master.app.MyAnnotation;

public aspect ITDAndReflectionAspect {

  // Make classes with @MyAnnotation annotated fields implement marker interface
  declare parents : hasfield(@MyAnnotation * *) implements HasMyAnnotationField;

  // Intercept methods with parameters implementing marker interface
  before() : execution(* *(.., HasMyAnnotationField+, ..)) {
    System.out.println(thisJoinPoint);
    manipulateAnnotatedFields(thisJoinPoint);
  }

  // Reflectively manipulate @MyAnnotation fields of type String
  private void manipulateAnnotatedFields(JoinPoint thisJoinPoint) {
    Object[] methodArgs = thisJoinPoint.getArgs();
    MethodSignature signature = (MethodSignature) thisJoinPoint.getSignature();
    Class<?>[] parameterTypes = signature.getParameterTypes();
    int argIndex = 0;
    for (Class<?> parameterType : parameterTypes) {
      Object methodArg = methodArgs[argIndex++];
      for (Field field : parameterType.getDeclaredFields()) {
        field.setAccessible(true);
        if (field.getAnnotation(MyAnnotation.class) == null)
          continue;
        // If using 'hasfield(@MyAnnotation String *)' we can skip this type check 
        if (field.getType().equals(String.class)) {
          try {
            field.set(methodArg, "#" + ((String) field.get(methodArg)) + "#");
          } catch (IllegalArgumentException | IllegalAccessException e) {
            throw new SoftException(e);
          }
        }
      }
    }
  }

}
package de.scrum_master.aspect;

import de.scrum_master.app.MyAnnotation;

public aspect SetterInterceptor {
  // Persistently change field value during write access
  Object around(String string) : set(@MyAnnotation String *) && args(string) {
    System.out.println(thisJoinPoint);
    return proceed(string.toUpperCase());
  }
}
package de.scrum_master.aspect;

import de.scrum_master.app.MyAnnotation;

public aspect GetterInterceptor {
  // Transparently return changed value during read access
  Object around() : get(@MyAnnotation String *) {
    System.out.println(thisJoinPoint);
    return "~" + proceed() + "~";
  }
}

已激活所有三个方面的控制台日志:

set(String de.scrum_master.app.MyClass.name)
set(String de.scrum_master.app.MyClass.name)
execution(void de.scrum_master.app.Application.doSomethingSpecial(int, MyClass))
get(String de.scrum_master.app.MyClass.name)
  MyClass [id=11, name=~#JOHN DOE#~]
execution(int de.scrum_master.app.Application.doSomethingVerySpecial(MyClass))
get(String de.scrum_master.app.MyClass.name)
  MyClass [id=11, name=~#JANE DOE#~]
execution(void de.scrum_master.app.Application.doSomethingSpecial(int, MyClass))
get(String de.scrum_master.app.MyClass.name)
  MyClass [id=11, name=~##JOHN DOE##~]
execution(int de.scrum_master.app.Application.doSomethingVerySpecial(MyClass))
get(String de.scrum_master.app.MyClass.name)
  MyClass [id=11, name=~##JANE DOE##~]
execution(void de.scrum_master.app.Application.doSomethingSpecial(int, MyClass))
get(String de.scrum_master.app.MyClass.name)
  MyClass [id=11, name=~###JOHN DOE###~]
execution(int de.scrum_master.app.Application.doSomethingVerySpecial(MyClass))
get(String de.scrum_master.app.MyClass.name)
  MyClass [id=11, name=~###JANE DOE###~]

如您所见

    每次调用方法#doSomethingSpecial(..)之一时,
  1. 反射访问将字段值用doSomethingVerySpecial(..)包围-由于for循环,总共需要3倍,最后是###的后缀。

  2. 字段写访问仅在对象创建期间发生一次,并将字符串值永久更改为大写。

  3. 字段读取访问将存储的值透明地包装为~字符,这些字符未存储,否则它们将更像方法1中的#字符,因为读取访问发生多次。 / p>

请注意,您可以确定是要访问所有带注释的字段,例如hasfield(@MyAnnotation * *),还是只限于某种特定类型,例如set(@MyAnnotation String *)get(@MyAnnotation String *)

有关更多信息,例如有关通过declare parents进行的ITD以及示例代码中使用的更特殊的切入点类型,请参阅AspectJ文档。

更新:在将整体式方面分为3个不同的方面之后,我可以说,如果您不需要使用hasfield()的第一个解决方案,而需要另外两个解决方案之一,那么您可能可以使用@AspectJ批注样式来编写方面,并使用普通的Java编译器对其进行编译,并让加载时间编织者负责完成方面并将其编织到应用程序代码中。原生语法限制仅适用于第一方面。