您注意到没有区别,因为Spring AOP在使用AspectJ语法时,实际上只模拟其功能的有限子集。由于Spring AOP基于动态代理,因此它仅提供对公共非静态方法执行的拦截。(使用 CGLIB 代理时,还可以截获包范围和受保护的方法。然而,AspectJ还可以拦截方法调用(不仅仅是执行),成员字段访问(静态和非静态),构造函数调用/执行,静态类初始化等等。
因此,让我们构造一个非常简单的 AspectJ 示例:
标记注释:
package de.scrum_master.app;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {}
驱动程序应用程序:
package de.scrum_master.app;
@MyAnnotation
public class Application {
private int nonStaticMember;
private static int staticMember;
public void doSomething() {
System.out.println("Doing something");
nonStaticMember = 11;
}
public void doSomethingElse() {
System.out.println("Doing something else");
staticMember = 22;
}
public static void main(String[] args) {
Application application = new Application();
application.doSomething();
application.doSomethingElse();
}
}
方面:
package de.scrum_master.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class MyAspect {
@Before("@within(de.scrum_master.app.MyAnnotation) && execution(public !static * *(..))")
public void adviceAtWithin(JoinPoint thisJoinPoint) {
System.out.println("[@within] " + thisJoinPoint);
}
@Before("@target(de.scrum_master.app.MyAnnotation) && execution(public !static * *(..))")
public void adviceAtTarget(JoinPoint thisJoinPoint) {
System.out.println("[@target] " + thisJoinPoint);
}
}
请注意,我在这里通过添加两个切入点来模拟Spring AOP行为。 && execution(public !static * *(..))
控制台日志:
[@within] execution(void de.scrum_master.app.Application.doSomething())
[@target] execution(void de.scrum_master.app.Application.doSomething())
Doing something
[@within] execution(void de.scrum_master.app.Application.doSomethingElse())
[@target] execution(void de.scrum_master.app.Application.doSomethingElse())
Doing something else
这并不奇怪。这正是您在春季AOP中看到的。现在,如果您从两个切入点中移除零件,则在弹簧AOP中输出仍然相同,但在AspectJ中(例如,如果您在春季激活AspectJ LTW,它也将更改为:&& execution(public !static * *(..))
[@within] staticinitialization(de.scrum_master.app.Application.<clinit>)
[@within] execution(void de.scrum_master.app.Application.main(String[]))
[@within] call(de.scrum_master.app.Application())
[@within] preinitialization(de.scrum_master.app.Application())
[@within] initialization(de.scrum_master.app.Application())
[@target] initialization(de.scrum_master.app.Application())
[@within] execution(de.scrum_master.app.Application())
[@target] execution(de.scrum_master.app.Application())
[@within] call(void de.scrum_master.app.Application.doSomething())
[@target] call(void de.scrum_master.app.Application.doSomething())
[@within] execution(void de.scrum_master.app.Application.doSomething())
[@target] execution(void de.scrum_master.app.Application.doSomething())
[@within] get(PrintStream java.lang.System.out)
[@within] call(void java.io.PrintStream.println(String))
Doing something
[@within] set(int de.scrum_master.app.Application.nonStaticMember)
[@target] set(int de.scrum_master.app.Application.nonStaticMember)
[@within] call(void de.scrum_master.app.Application.doSomethingElse())
[@target] call(void de.scrum_master.app.Application.doSomethingElse())
[@within] execution(void de.scrum_master.app.Application.doSomethingElse())
[@target] execution(void de.scrum_master.app.Application.doSomethingElse())
[@within] get(PrintStream java.lang.System.out)
[@within] call(void java.io.PrintStream.println(String))
Doing something else
[@within] set(int de.scrum_master.app.Application.staticMember)
在详细查看此内容时,您会看到更多的连接点被截获,但也有更多的连接点被截获,例如前面提到的连接点,但也适用于构造函数执行之前发生的非静态字段和对象。@within()
@target()
call()
set()
initialization()
当我们只看这个时,我们看到这个:@target()
[@target] initialization(de.scrum_master.app.Application())
[@target] execution(de.scrum_master.app.Application())
[@target] call(void de.scrum_master.app.Application.doSomething())
[@target] execution(void de.scrum_master.app.Application.doSomething())
Doing something
[@target] set(int de.scrum_master.app.Application.nonStaticMember)
[@target] call(void de.scrum_master.app.Application.doSomethingElse())
[@target] execution(void de.scrum_master.app.Application.doSomethingElse())
Doing something else
对于这些方面输出行中的每一个,我们还看到相应的匹配项。现在,让我们专注于不相同的内容,过滤输出的差异:@within()
[@within] staticinitialization(de.scrum_master.app.Application.<clinit>)
[@within] execution(void de.scrum_master.app.Application.main(String[]))
[@within] call(de.scrum_master.app.Application())
[@within] preinitialization(de.scrum_master.app.Application())
[@within] get(PrintStream java.lang.System.out)
[@within] call(void java.io.PrintStream.println(String))
Doing something
[@within] get(PrintStream java.lang.System.out)
[@within] call(void java.io.PrintStream.println(String))
Doing something else
[@within] set(int de.scrum_master.app.Application.staticMember)
在这里你看,在外观上
- 静态类初始化,
- 静态方法执行,
- 构造函数调用(尚未执行!)
- 构造对象预初始化,
- 另一个类中的成员变量访问 (),
System.out
- 从另一个类调用方法 (),
PrintStream.println(String)
- 设置静态类成员。
所有这些切入点有什么共同之处?没有目标对象,因为我们谈论的是静态方法或成员,静态类初始化,对象预初始化(尚未定义)或从其他类调用/访问不带有我们在这里定位的注释的东西。this
因此,您会看到在AspectJ中,两个切入点之间存在显着差异,在Spring AOP中,由于其局限性,它们并不明显。
我的建议是,如果您打算拦截目标对象实例中的非静态行为,请使用。如果您决定在Spring中激活AspectJ模式,甚至将一些代码移植到非Spring,支持Aspect的应用程序,那么切换到AspectJ将变得更加容易。@target()
更新 2022-07-15:当我最初写这个答案时,我忘记了注释不是在类类型上,而是在接口类型上的情况。在这种情况下,即使在Spring AOP中,您也会注意到差异,因为仍然会匹配代理接口,而不会匹配。为什么?@within
@target
-
@within
询问实际类型是否已批注。一个实现或扩展接口类型的类,也是由于类继承。因此,询问该接口类型是否具有注释将产生肯定的结果。@Marker MyInterface
MyInterface
@Marker
-
@target
询问运行时类型(即Spring AOP代理)是否具有注释,而注释没有。注释,即使它们带有元注释,也只能从一个类继承到另一个类,不能从接口继承到另一个类或从超级方法继承到重写方法,参见javadoc。因此,实现接口的代理类不携带与实现的接口相同的注释。因此,所依赖的切入点将不匹配。@Marker
@Inherited
@target