Search code examples
javaspringspring-bootannotationsaop

How to have Spring aspect support for repeatable annotation?


I have created a java 17 repeatable annotation and want to create an aspect around the method containing the annotation is invoked. This seems to work when method is annotated once but fails to invoke when I have repeatable annotation. I am using aspectjrt version 1.9.7. Am I doing something wrong or aspect doesn't support repeatable annotations? Any workaround for the same?

Annotation class ->

@Repeatable(Schedules.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Schedule {
  String dayOfMonth() default "first";
  String dayOfWeek() default "Mon";
  int hour() default 12;
}

The repeatable class ->

@Retention(value = RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Schedules {
    Schedule[] value();
}

The aspect class ->

@Aspect
@Component
@Slf4j
public class Aspect {

    @Around("@annotation(Schedule)")
    public Object trace(ProceedingJoinPoint joinPoint) throws Throwable {

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        Schedule filters = method.getAnnotation(Schedule.class);
        //Business Logic
        return joinPoint.proceed();
    }
}


Solution

  • This actually is not an AOP problem, but you need to understand how repeatable annotations work in Java: If the annotation appears multiple times on the annotated element, it is not represented as a single annotation anymore but as an array of annotations in the value() of the annotation type mentioned in @Repeatable, i.e. in your case @Schedules (plural "s"!).

    For your aspect, it means that you need two pointcuts, one for the single-annotation case and one for the multi-annotation one. I am suggesting to factor out the common advice code into a helper method of the aspect which always takes an array of the repeatable annotations, then just pass the value of the wrapper annotation on in one case and a one-element array in the other case.

    Feel free to ask follow-up questions, if anything is unclear. But it should be straightforward, almost trivial.

    Resources:

    P.S.: You should learn about how to bind annotations to advice method parameters:

    @Around("@annotation(schedule)")
    public Object traceSingle(ProceedingJoinPoint joinPoint, Schedule schedule) throws Throwable
    
    // ...
    
    @Around("@annotation(schedules)")
    public Object traceMultiple(ProceedingJoinPoint joinPoint, Schedules schedules) throws Throwable