Search code examples
javaspringaopaspectjspring-aop

How to intercept meta annotations on types or methods


Below is my custom annotation.

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Transactional(value = TransactionalCode.MANAGER, readOnly = true)
public @interface FinanceReadTx {}

I want to do something with the "MyAnnotation" so I declared @Around and method like below.

@Aspect
@Component
public class TransactionalInterceptor implements Ordered {
    @Around("within(@org.springframework.transaction.annotation.Transactional *) || " +
            "within(@(@org.springframework.transaction.annotation.Transactional *) *)")
    public Object proceed(ProceedingJoinPoint pjp) throws Throwable {
        try {
            setDbType(pjp);
            Object result = pjp.proceed();
            DataSourceContextHolder.clearDataSourceType();
            return result;
        } finally {
            // restore state
            DataSourceContextHolder.clearDataSourceType();
        }
    }
    // ...
}

The below service is "Autowired" by other classes. so I think it is not the problem related to AOP proxy.

@Service
public class UnconfirmedReportService {
    private static final int PREVIEW_SIZE = 8;
    @Autowired
    private UnconfirmedReportRepository unconfirmedReportRepository;
    // ...
    @FinanceHikariReadTx
    public List<UnconfirmedExcelDownloadView> getExcelData(UnconfirmedSearchCondition condition) {
        List<UnconfirmedExcelDownloadView> excelData = newArrayList();
        excelData.addAll(newArrayList(getPurchaseReportDetailExcel(condition)));
        return excelData;
    }
    // ...
}

The below code calls the above service

@Slf4j
@Component
public class UnconfirmedDashboardDetailExcelReader extends SellerExcelReaderTemplate<UnconfirmedExcelDownloadView, UnconfirmedSearchCondition> {
    @Autowired
    private UnconfirmedReportService unconfirmedReportservice;

    @Override public List<UnconfirmedExcelDownloadView> read(String conditionJson) {
        UnconfirmedSearchCondition condition = transformCondition(conditionJson);
        List<UnconfirmedExcelDownloadView> viewList = unconfirmedReportservice.getExcelData(condition);
        return viewList;
    }
    // ...
}

If the @MyAnnotation is annotated to a class, the proceed() is called but If a method is with the annotation like above code, it doesn't work. I want it to work with only methods.

What I try to solve this?


Solution

  • You are currently doing something similar to what I explained in this answer, i.e. matching (meta) annotations on classes.

    Now you are wondering why it does not match methods. I explained that here. Basically, @within() matches anything in annotated classes while @annotation() matches annotated methods. The problem is, @annotation() needs an exact type name.

    But there is another way to express an annotated method directly inside an execution() signature. Here you also have the option to specify meta annotations in a similar way as you are using it for annotated classes. Let us compare the two:

    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 MetaAnnotationInterceptor {
      @Before(
        "execution(* *(..)) && (" +
          "within(@de.scrum_master.app.MetaAnnotation *) || " +
          "within(@(@de.scrum_master.app.MetaAnnotation *) *) || " +
          "within(@(@(@de.scrum_master.app.MetaAnnotation *) *) *)" +
        ")"
      )
      public void annotatedClasses(JoinPoint thisJoinPoint){
        System.out.println(thisJoinPoint);
      }
    
      @Before(
        "execution(@de.scrum_master.app.MetaAnnotation * *(..)) || " +
        "execution(@(@de.scrum_master.app.MetaAnnotation *) * *(..)) || " +
        "execution(@(@(@de.scrum_master.app.MetaAnnotation *) *) * *(..)) "
      )
      public void annotatedMethods(JoinPoint thisJoinPoint){
        System.out.println(thisJoinPoint);
      }
    }
    

    The latter is what you are looking for. Just replace de.scrum_master.app.MetaAnnotation by org.springframework.transaction.annotation.Transactional, and it should work for your use case. Make sure not to mess up the number and nesting order of (), @ and *, otherwise you quickly end up with pointcut syntax errors.

    If you prefer to have one advice method instead or two, you can either create a big messy string containing both pointcuts or you define two separate @Pointcuts and combine them in the advice, chaining them with ||.