参数的AOP切入点有一个带注释的字段吗? [英] AOP pointcut for argument which has a field with annotation?

查看:0
本文介绍了参数的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:对于Requirements部分:我必须覆盖该值(它是一个私有字段,但有一个公共setter)。

推荐答案

好的,即使在问了几次之后,我也没有从您那里得到明确的答复,您希望在何时何地操作您的字段值。因此,我将向您介绍三种不同的方法。都涉及使用成熟的AspectJ,我还将使用本机语法,因为我要向您展示的第一种方法不能在注释式语法中工作。您需要使用AspectJ编译器编译方面。无论是在编译时还是通过加载时将其编织到应用程序代码中,都由您决定。我的解决方案完全不使用Spring,但如果您是一名Spring用户,您可以将其与Spring结合使用,甚至可以与Spring AOP结合使用。有关更多说明,请阅读Spring手册。

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

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

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

  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() + "~";
  }
}

全部激活3个方面的控制台日志:

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###~]

如您所见,

  1. 由于for循环,每次调用doSomethingSpecial(..)doSomethingVerySpecial(..)中的一个方法时,反射访问都会将字段值包围#,总共是3x,结果是###前缀和后缀。

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

  3. 字段读访问权限会将存储的值透明地包装在~个未存储的字符中,否则它们会变得更像方法1中的#字符,因为读访问权限会多次发生。

还请注意,您可以决定是像hasfield(@MyAnnotation * *)那样访问所有带注释的字段,还是像set(@MyAnnotation String *)get(@MyAnnotation String *)那样仅限于某个类型。

有关更多信息,例如关于ITD VIAdeclare parents和我的示例代码中使用的更奇特的切入点类型,请参阅AspectJ文档。

更新:在我将我的整体方面分成3个单独的方面后,我可以说,如果您不需要使用hasfield()的第一个解决方案,而是使用其他两个方面中的一个,您可能可以使用@AspectJ注释样式来编写方面,用普通的Java编译器编译它们,并让加载时编织器负责完成方面并将其编织到应用程序代码中。本机语法限制仅适用于第一个方面。

这篇关于参数的AOP切入点有一个带注释的字段吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆