【问题标题】:Difference between @target and @within (Spring AOP)@target 和 @within 之间的区别(Spring AOP)
【发布时间】:2018-07-01 15:47:45
【问题描述】:

弹簧手册说:

目标所在的任何连接点(仅在 Spring AOP 中执行方法) 对象有一个@Transactional 注释: @target(org.springframework.transaction.annotation .Transactional)

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

但我看不出它们之间有什么区别!

我尝试用谷歌搜索:

两者的一个区别是@within()是静态匹配的,要求对应的注解类型只有 类保留。而 @target() 在运行时匹配, 要求同样具有 RUNTIME 保留。除此之外, 在 Spring 的上下文中,这里的 join 没有区别 两个人选择的点。

所以我尝试添加带有 CLASS 保留的自定义注释,但是 Spring 抛出异常(因为注释 必须具有 RUNTIME 保留)

【问题讨论】:

    标签: java spring aop aspectj spring-aop


    【解决方案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);
      }
    }
    

    请注意,我在这里通过将&& 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、启用方面的应用程序,这将使切换到 AspectJ 变得更加容易。

    【讨论】:

      【解决方案2】:

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

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

      ClassRetAnnotation.java

      package mypackage;
      
      import java.lang.annotation.Retention;
      import java.lang.annotation.RetentionPolicy;
      
      @Retention(RetentionPolicy.CLASS)
      public @interface ClassRetAnnotation {}
      

      RuntimeRetAnnotation.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

      【讨论】:

      • 我看不出这是如何回答 OP 的问题的。
      • @kriegaex OP 指出 Spring 抛出异常是因为注解必须有 RUNTIME 保留;我想证明这仅适用于@target poincut 指示符。我非常喜欢你的详细回答,反正我注意到问题的上下文显然仅限于 Spring AOP,所以我的回答仅限于 OP 报告的问题。干杯。
      • 关于问题的范围和答案,您是对的。但是因为 Spring AOP 借用了 AspectJ 的两个切入点描述符并且它们的行为相同(例外是注释保留的细微差别),我认为确实有必要扩大范围以首先了解大局,然后再关注剩下的内容Spring AOP 中的大图。我最后的建议(这里没有双关语)还暗示了为什么了解所有这些对 Spring AOP 用户来说很重要:他们中的许多人后来迁移到 AspectJ 以克服 Spring AOP 的限制。
      • (续) 迁移后,他们注意到方面的行为有所不同,但因为他们不了解为什么以及驯服 AspectJ 的难易程度他们很快放弃了方面,恢复到 Spring AOP,使用可怕的变通方法来处理可以用 AspectJ 以简单的方式漂亮地完成的事情。
      • @kriegaex 我同意,当然,它们在 Spring AOP 中的行为相同!再次感谢您,我的回答只留下了两分钱。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-12-31
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多