@target和@within之间的差异(春季AOP)

2022-09-04 01:05:14

弹簧手册 说:

任何连接点(仅在Spring AOP中执行方法),其中目标对象具有@Transactional注释:@target(org.springframework.transaction.annotation )。事务性)

任何连接点(仅在Spring AOP中执行方法),其中目标对象的声明类型具有@Transactional注释:@within(org.springframework.transaction.annotation )。事务性)

但我看不出它们之间有任何区别!

我试图谷歌它:

两者之间的一个区别是@within() 是静态匹配的,要求相应的注释类型仅具有 CLASS 保留期。而@target() 在运行时是匹配的,要求具有相同的运行时保留。除此之外,在 Spring 的上下文中,由两个选择的连接点之间没有区别。

所以我试图添加具有CLASS保留期的自定义注释,但是Spring抛出了一个异常(因为注释必须具有运行时保留期)


答案 1

您注意到没有区别,因为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 MyInterfaceMyInterface@Marker

  • @target询问运行时类型(即Spring AOP代理)是否具有注释,而注释没有。注释,即使它们带有元注释,也只能从一个类继承到另一个类,不能从接口继承到另一个类或从超级方法继承到重写方法,参见javadoc。因此,实现接口的代理类不携带与实现的接口相同的注释。因此,所依赖的切入点将不匹配。@Marker@Inherited@target


答案 2

您引用的信息是正确的,但是只有@target切入点指示符需要具有运行时保留的注释,而@within只需要 CLASS 保留。

让我们考虑以下两个简单的注释:

类重试注释.java

package mypackage;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.CLASS)
public @interface ClassRetAnnotation {}

运行时重试注释.java

package mypackage;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface RuntimeRetAnnotation {}

现在,如果您定义了如下所示的方面,则在运行时不会有异常

@Component
@Aspect
public class MyAspect {

    @Before("@within(mypackage.ClassRetAnnotation)")
    public void within() { System.out.println("within"); }

    @Before("@target(mypackage.RuntimeRetAnnotation)")
    public void target() { System.out.println("target"); }
}

我希望这个例子有助于澄清你所指出的微妙差异。

弹簧参考:https://docs.spring.io/spring/docs/5.0.x/spring-framework-reference/core.html#aop-pointcuts


推荐