Spring AOP pointcut для пользовательской аннотации не работает внутри класса stati c - PullRequest
0 голосов
/ 14 апреля 2020

На данный момент у меня есть следующий 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();
        }
    }
}

Он работает нормально, когда я добавляю @MyAnnotation на root классы уровня следующим образом.

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

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

Это также не работает, если я добавляю аннотацию для внутреннего класса c.

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

Я бы Буду очень признателен, если вы покажете мне, как заставить его работать с классами и бобами Spring.

Ответы [ 2 ]

2 голосов
/ 15 апреля 2020

Это не ответ, но комментарии слишком ограничены, чтобы говорить то, что я хочу сказать. Это на самом деле обратная связь с собственным ответом OP :

  • execution(* (@com.test.MyAnnotation *).*(..)) также может быть написано более читабельно как @within(com.test.MyAnnotation) в Spring AOP, потому что Spring AOP знает только выполнение точки соединения в любом случае. В AspectJ вы бы добавили && execution(* *(..)) к pointcut.

  • execution(@com.test.MyAnnotation * *.*(..)) также можно записать более читабельно как @annotation(com.test.MyAnnotation) в Spring AOP, потому что Spring AOP в любом случае знает только точки соединения выполнения. В AspectJ вы бы добавили && execution(* *(..)) к pointcut.

  • Я понял, что pointcut methodInMyAnnotationType будет работать, только если я добавлю @MyAnnotation в класс, который фактически владеет методом.

    Конечно, потому что это общее ограничение Java аннотаций. Они никогда не наследуются подклассам, от интерфейсов до классов или методов или от методов родительского класса до перезаписанных методов подкласса. Единственное исключение - если вы используете @Inherited в качестве метааннотации для самого типа аннотации, тогда она наследуется подклассами (но опять же не от интерфейса до реализующего класса). Это задокументировано здесь .

  • Что касается this() против target() и @this() против @target, как вы сказали, "this" версии поддерживается только AspectJ (который также можно использовать из приложения Spring). Причина в том, что «this» отличается от «target» только в pointcut call(), где «this» - это вызывающий метод, а «target» - вызываемый метод. Поскольку call() также недоступен в Spring AOP, не имеет смысла поддерживать соответствующие pointcut-типы "this".

  • Если вы готовы перейти на AspectJ, у меня есть Обходной путь для создания реализующих классов «наследующих» аннотации от интерфейсов и для создания определенных c методов также «наследующих» аннотации, см. этот ответ .

Я просто упоминая все это в образовательных целях, а не для того, чтобы заменить свое собственное решение, поскольку вы, кажется, довольны сочетанием аннотаций маркеров и интерфейсов маркеров.

0 голосов
/ 15 апреля 2020

После углубления в топи c АОП я наконец нашел рабочее решение.

Первоначально я использую следующие точки:

@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 pointcut будет работать, только если я добавлю @MyAnnotation в класс, который фактически владеет методом. Однако, если я добавлю аннотацию к классу B, которая расширяет класс A, AOP не сможет перехватить методы из класса A.

Одно из возможных решений, которое я нашел, заключается в следующем.

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

Это означает, что pointcut предназначен для ВСЕХ методов из текущего класса И родительского класса, а текущий класс должен быть помечен @MyAnnotation. Это выглядит многообещающе. К сожалению, Spring AOP не поддерживает @this примитив pointcut, который выдает UnsupportedPointcutPrimitiveException.

. После еще большего изучения топи c из 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;
}

Это означает, что pointcut предназначен для ВСЕХ методов из текущего класса И родительского класса. Кроме того, метод должен быть аннотирован @MyAnnotation, или класс, содержащий метод, аннотирован @MyAnnotation, или объект, имеющий этот метод, должен быть экземпляром интерфейса маркера MyAnnotable. Это выглядит красиво и работает.

Моя последняя реализация класса выглядит следующим образом.

@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 {}
}
...