参数的AOP切入点有一个带注释的字段吗? [英] AOP pointcut for argument which has a field with annotation?
问题描述
问题:当使用具有特定批注的字段的特定类型参数调用特定方法时,我希望使用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手册。
我在我的示例代码中向您展示的方式是:
类型间声明(ITD):这是最复杂的方式,它使用
hasfield()
切入点指示符。为了使用它,需要使用特殊标志-XhasMember
调用AspectJ编译器。在安装了AJDT的Eclipse中,该设置在项目设置中的"AspectJ Compiler"、"Other"下命名为"Has Members"。我们在这里做的是:- 使所有带有注释字段的类实现标记接口
HasMyAnnotationField
- 每当调用具有实现接口的参数类型的方法时,都会在控制台上打印一些内容,并且可以选择通过反射操作字段值,这可能类似于您自己的解决方案。
- 使所有带有注释字段的类实现标记接口
通过
set()
建议在写访问期间操作字段值。这会持久地更改字段值,并且不需要任何具有标记接口、特殊编译器标志和像解决方案1那样的反射的ITD。通过
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###~]
如您所见,
由于
for
循环,每次调用doSomethingSpecial(..)
或doSomethingVerySpecial(..)
中的一个方法时,反射访问都会将字段值包围#
,总共是3x,结果是###
前缀和后缀。字段写访问在对象创建期间只发生一次,并且会将字符串值永久更改为大写。
字段读访问权限会将存储的值透明地包装在
~
个未存储的字符中,否则它们会变得更像方法1中的#
字符,因为读访问权限会多次发生。
还请注意,您可以决定是像hasfield(@MyAnnotation * *)
那样访问所有带注释的字段,还是像set(@MyAnnotation String *)
或get(@MyAnnotation String *)
那样仅限于某个类型。
有关更多信息,例如关于ITD VIAdeclare parents
和我的示例代码中使用的更奇特的切入点类型,请参阅AspectJ文档。
更新:在我将我的整体方面分成3个单独的方面后,我可以说,如果您不需要使用hasfield()
的第一个解决方案,而是使用其他两个方面中的一个,您可能可以使用@AspectJ注释样式来编写方面,用普通的Java编译器编译它们,并让加载时编织器负责完成方面并将其编织到应用程序代码中。本机语法限制仅适用于第一个方面。
这篇关于参数的AOP切入点有一个带注释的字段吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!