你之所以没有看到任何不同,是因为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);
}
}
请注意,我在这里通过向两个切入点添加
&& execution(public !static * *(..))
来模拟Spring AOP的行为。
控制台日志:[@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
并不意外。这正是您在Spring AOP中看到的内容。 现在,如果您从两个切入点中删除&& execution(public !static * *(..))
部分,在Spring AOP中输出仍然相同,但在AspectJ中(例如,在Spring中激活AspectJ LTW时),其会发生变化:
[@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中由于其限制,它们并不明显。
我给你的建议是,如果您的意图是拦截目标对象实例中的非静态行为,请使用@target()
。如果您决定在Spring中激活AspectJ模式,甚至将一些代码移植到非Spring、启用方面的应用程序中,这将使转换更容易。
更新2022-07-15:当我最初撰写这个答案时,我忘记了注释不在类类型上而在接口类型上的情况。在这种情况下,即使在Spring AOP中,您也会注意到一个区别,因为@within
仍然会匹配代理的接口,而@target
则不会。为什么呢?
@within
询问实际类型是否已注释。由于类继承,实现或扩展接口类型@Marker MyInterface
,也是MyInterface
,因此询问该接口类型是否具有@Marker
注释会得出积极结果。
@target
询问运行时类型,即Spring AOP代理,是否具有@Marker
注释,但它没有。注释,即使带有@Inherited
元注释,也只能从类到类继承,不能从接口到类或从超级方法到覆盖方法继承,参见javadoc。因此,实现接口的代理类不具有与实现的接口相同的注释。因此,在依赖于@target
的连接点将不匹配。