简体   繁体   English

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

[英]Spring AOP pointcut for custom annotation not working inner static class

At the moment, I have the following Pointcut.目前,我有以下切入点。

@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();
        }
    }
}

It's working fine when I add @MyAnnotation on root level classes as following.当我在根级类上添加@MyAnnotation时,它工作正常,如下所示。

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

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

It's also not working if I add the annotation on an inner static class.如果我在内部 static 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
     */
}

If I use this technique on an interface, it works.如果我在界面上使用这种技术,它就可以工作。

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

I'd be very grateful if you could show me how to make it work with classes and Spring beans.如果您能告诉我如何使它与类和 Spring bean 一起工作,我将不胜感激。

After digging deeper into the topic of AOP, I finally found a working solution.在深入研究了 AOP 的主题之后,我终于找到了一个可行的解决方案。

Originally, I'm using the following pointcuts.最初,我使用以下切入点。

@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;
    }
}

What I learned is that the methodInMyAnnotationType pointcut will only work if I put @MyAnnotation on the class that actually owns the method.我了解到,只有在我将@MyAnnotation放在实际拥有该方法的 class 上时, methodInMyAnnotationType切入点才有效。 However, if I put the annotation on class B that extends class A, the AOP cannot intercept methods from class A.但是,如果我将注释放在扩展 class A 的 class B 上,则 AOP 无法拦截来自 class A 的方法。

One potential solution I found is as following.我发现的一种潜在解决方案如下。

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

It means the pointcut is for ALL methods from current class AND parent class and the current class must be annotated with @MyAnnotation .这意味着切入点适用于当前 class 和父 class 的所有方法,并且当前 class 必须使用@MyAnnotation注释。 It looks promising.看起来很有希望。 Unfortunately, Spring AOP doesn't support @this pointcut primitive which produces UnsupportedPointcutPrimitiveException .不幸的是,Spring AOP 不支持产生UnsupportedPointcutPrimitiveException@this切入点原语。

After a bit more digging into the topic of this , I found the existence of target primitive and came up with the following solution.在深入研究了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;
}

It means the pointcut is for ALL methods from current class AND parent class.这意味着切入点适用于当前 class 和父 class 的所有方法。 In addition, the method must be annotated with @MyAnnotation or the class containing the method is annotated with @MyAnnotation or the object that has this method must be an instance of the marker interface MyAnnotable .此外,该方法必须使用@MyAnnotation注释或包含该方法的class 使用@MyAnnotation注释或具有此方法的object 必须是标记接口MyAnnotable的实例。 It looks nice and it works.它看起来不错并且有效。

My final class implementation looks like this.我最终的 class 实现看起来像这样。

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

    @Resource
    private ShiftModule.Annotated self;
}

Add-on information:附加信息:

I did give the following pointcut a try during my experimentation.在我的实验过程中,我确实尝试了以下切入点。

@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;
}

What I found is it does NOT work with annotated inner interface, meaning the code below will stop working.我发现它不适用于带注释的内部界面,这意味着下面的代码将停止工作。 The AOP aspect doesn't have any effects at all. AOP 方面根本没有任何影响。

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

This is not an answer, but comments are too limited to say what I want to say.这不是答案,但评论太有限,无法说出我想说的话。 This is actually feedback to the OP's own answer :这实际上是对 OP 自己的回答的反馈:

  • execution(* (@com.test.MyAnnotation *).*(..)) can also be written more readably as @within(com.test.MyAnnotation) in Spring AOP because Spring AOP only knows execution joinpoints anyway. execution(* (@com.test.MyAnnotation *).*(..))也可以在 Spring AOP 中写成@within(com.test.MyAnnotation) ,因为 Spring AOP 只知道执行连接点。 In AspectJ you would add && execution(* *(..)) to the pointcut.在 AspectJ 中,您可以将&& execution(* *(..))添加到切入点。

  • execution(@com.test.MyAnnotation * *.*(..)) can also be written more readably as @annotation(com.test.MyAnnotation) in Spring AOP because Spring AOP only knows execution joinpoints anyway. execution(@com.test.MyAnnotation * *.*(..))也可以写成 Spring AOP 中的@annotation(com.test.MyAnnotation)更具可读性,因为 Spring AOP 无论如何都只知道执行连接点。 In AspectJ you would add && execution(* *(..)) to the pointcut.在 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.我了解到,只有在我将@MyAnnotation放在实际拥有该方法的 class 上时, methodInMyAnnotationType切入点才有效。

    Of course, because this is a general limitation of Java annotations.当然,因为这是 Java 注解的一般限制。 They are never inherited to subclasses, from interfaces to classes or methods or from parent class methods to overwritten subclass methods.它们永远不会被继承到子类,从接口到类或方法,或者从父 class 方法到覆盖的子类方法。 The only exception is if you use @Inherited as a meta annotation for annotation type itself, then it gets inherited by subclasses (but again not from interface to implementing class).唯一的例外是,如果您使用@Inherited作为注释类型本身的元注释,那么它会被子类继承(但同样不是从接口到实现类)。 This is documented here .在此处记录。

  • As for this() vs target() and @this() vs @target , as you said the "this" versions are only supported by AspectJ (which you can optionally also use from within a Spring application).至于this() vs target()@this() vs @target ,正如您所说,“this”版本仅受 AspectJ 支持(您也可以选择在 Spring 应用程序中使用它)。 The reason is that "this" only makes a difference from "target" in a call() pointcut where "this" is the calling method and "target" is the called method.原因是“this”仅与call()切入点中的“target”有所不同,其中“this”是调用方法,“target”是被调用方法。 Because call() is also unavailable in Spring AOP, it would not make sense to support the corresponding "this" type pointcuts.因为call()在 Spring AOP 中也不可用,所以支持相应的“this”类型切入点是没有意义的。

  • If you are willing to switch to AspectJ, I have a workaround for making implementing classes "inherit" annotations from interfaces and for making specific methods "inherit" annotations too, see this answer .如果您愿意切换到 AspectJ,我有一个解决方法,可以让实现类从接口“继承”注释,也可以让特定方法“继承”注释,请参阅这个答案

I am just mentioning all this for educational purposes, not in order to replace your own solution as you seem to be happy with the mix of marker annotations and marker interfaces.我只是出于教育目的提及所有这些,而不是为了替换您自己的解决方案,因为您似乎对标记注释和标记界面的组合感到满意。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM