Spring 自定义注释的 AOP 切入点在内部静态中不起作用 class

Spring AOP pointcut for custom annotation not working inner static class

目前,我有以下切入点。

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    @Aspect
    @Component
    public static class MyAnnotationAspect {
        @Pointcut("execution(* (@com.test.MyAnnotation *).*(..))")
        public void methodInMyAnnotationType() {}

        @Around("methodInMyAnnotationType()")
        public Object annotate(ProceedingJoinPoint pjp) throws Throwable {
            System.out.println("AOP WORKING");
            return pjp.proceed();
        }
    }
}

当我在根级别 class 添加 @MyAnnotation 时,它工作正常,如下所示。

@MyAnnotation
@Service
public class ShiftModule {
    @Resource
    private ShiftModule self;

    /* Executing anything using self.method() triggers the Aspect
     * for @MyAnnotation perfectly
     */
}

如果我在内部静态 class 上添加注释,它也不起作用。

@Service
public class ShiftModule {
    @Service
    @MyAnnotation
    public class AnnotatedShiftModule extends ShiftModule {}

    @Resource
    private AnnotatedShiftModule self;

    /* Executing anything using self.method() does NOT trigger the 
     * Aspect for @MyAnnotation or even framework's annotations
     * like @Async
     */
}

如果我在界面上使用此技术,它会起作用。

@Repository
public interface OrderRepo extends JpaRepository<Order,Long> {
    @Repository("annotatedOrderRepo")
    @MyAnnotation
    public interface AnnotatedOrderRepo extends OrderRepo {}
}

如果你能告诉我如何让它与 classes 和 Spring beans 一起工作,我将不胜感激。

深入研究 AOP 主题后,我终于找到了可行的解决方案。

最初,我使用以下切入点。

@Aspect
@Component
public static class MyAnnotationAspect {
    /**
     * Matches the execution of any methods in a type annotated with @MyAnnotation.
     */
    @Pointcut("execution(* (@com.test.MyAnnotation *).*(..))")
    public void methodInMyAnnotationType() {}

    /**
     * Matches the execution of any methods annotated with @MyAnnotation.
     */
    @Pointcut("execution(@com.test.MyAnnotation * *.*(..))")
    public void methodAnnotatedWithMyAnnotation() {}

    @Around("methodInMyAnnotationType() || methodAnnotatedWithMyAnnotation()")
    public Object aop(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("AOP IS WORKING");
        return pjp.proceed;
    }
}

我学到的是 methodInMyAnnotationType 切入点只有在我将 @MyAnnotation 放在实际拥有该方法的 class 上时才会起作用。但是,如果我将注释放在扩展 class A 的 class B 上,AOP 无法拦截来自 class A.

的方法

我找到的一个可能的解决方案如下。

@Pointcut("execution(* *(..)) && @this(com.test.MyAnnotation)")

这意味着切入点适用于当前 class 和父 class 的所有方法,并且当前 class 必须用 @MyAnnotation 注释。看起来很有前途。不幸的是,Spring AOP 不支持产生 UnsupportedPointcutPrimitiveException.

@this 切入点原语

在深入研究 this 的主题后,我发现了 target 原语的存在,并提出了以下解决方案。

@Pointcut("execution(@com.test.MyAnnotation * *.*(..))")
public void annotatedMethod() {}

@Pointcut("execution(* (@com.test.MyAnnotation *).*(..))")
public void annotatedClass() {}

@Pointcut("execution(* *(..)) && target(com.test.MyAnnotable)")
public void implementedInterface() {}

@Around("annotatedMethod() || annotatedClass() || implementedInterface()")
public Object aop(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("AOP IS WORKING");
    return pjp.proceed;
}

这意味着切入点适用于当前 class 和父 class 的所有方法。另外,方法必须注解为@MyAnnotation或者包含方法的class注解为@MyAnnotation或者拥有该方法的对象必须是标记接口的实例MyAnnotable.看起来不错,也很管用。

我的最终 class 实现如下所示。

@Service
public class ShiftModule {
    @Service
    public class Annotated extends ShiftModule implements MyAnnotable {}

    @Resource
    private ShiftModule.Annotated self;
}

插件信息:

我在实验过程中尝试了以下切入点。

@Pointcut("@annotation(com.test.MyAnnotation)")
public void annotatedMethod() {}

@Pointcut("@within(com.test.MyAnnotation)")
public void annotatedClass() {}

@Pointcut("target(com.test.MyAnnotable)")
public void implementedInterface() {}

@Around("execution(* *(..)) && (annotatedMethod() || annotatedClass() || implementedInterface()")
public Object aop(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("AOP IS WORKING");
    return pjp.proceed;
}

我发现它不适用于带注释的内部界面,这意味着下面的代码将停止工作。 AOP 方面根本没有任何影响。

@Repository
public interface OrderRepo extends JpaRepository<Order,Long> {
    @Repository("annotatedOrderRepo")
    @MyAnnotation
    public interface Annotated extends OrderRepo {}
}

这不是回答,只是评论太有限,说不出我想说的。这其实是反馈给:

  • execution(* (@com.test.MyAnnotation *).*(..)) 也可以在 Spring AOP 中写成 @within(com.test.MyAnnotation) 更具可读性,因为 Spring AOP 无论如何只知道执行连接点。在 AspectJ 中,您可以将 && execution(* *(..)) 添加到切入点。

  • execution(@com.test.MyAnnotation * *.*(..)) 也可以在 Spring AOP 中写成 @annotation(com.test.MyAnnotation) 更具可读性,因为 Spring AOP 无论如何只知道执行连接点。在 AspectJ 中,您可以将 && execution(* *(..)) 添加到切入点。

  • What I learned is that the methodInMyAnnotationType pointcut will only work if I put @MyAnnotation on the class that actually owns the method.

    当然可以,因为这是 Java 注释的一般限制。它们永远不会继承到 subclasses,从接口继承到 classes 或方法,或者从父 class 方法继承到覆盖的 subclass 方法。唯一的例外是,如果您使用 @Inherited 作为注释类型本身的元注释,那么它会被 subclasses 继承(但同样不是从接口到实现 class)。这记录在案 here.

  • 至于this() vs target()@this() vs @target,正如你所说的"this"版本仅支持AspectJ(您也可以选择在 Spring 应用程序中使用它)。原因是 "this" 仅在 call() 切入点中与 "target" 有所不同,其中 "this" 是调用方法,而 "target" 是被调用方法。因为 call() 在 Spring AOP 中也不可用,所以支持相应的 "this" 类型切入点是没有意义的。

  • 如果您愿意切换到 AspectJ,我有一个解决方法,可以从接口实现 classes "inherit" 注释和制作特定方法 "inherit"也有注释,参见 .

我只是出于教育目的提及所有这些内容,而不是为了替换您自己的解决方案,因为您似乎对标记注释和标记接口的组合很满意。