Search code examples
spring-bootaopaspectjspring-aopaopalliance

spring aop @within not working correctly for custom annotation


I have created a custom annotation for some logging purpose. This annotation is applied over spring jpa repository created in project by extending JpaRepository. So what is happening now is that for read methods it is working correctly but for save part @Around advice never get invoked. Below is my @Around advice

@Around("@within(com.myproject.annotations.RepoAware)")
public void log(final ProceedingJoinPoint jp){
  return log(jp,true);
}

my log method is taking one boolean argument on the basis of which i log something.Below is the repo code

@Repository
@RepoAware
public interface MyRepo extends JpaRepository<Student,Long>{
}

Now when i call repo method that is not part of my repository MyRepo like save, saveAll or specifically the method that exists in parent hierarchy then @Around advice not working. When i applied debugger then i can see that during save call proxy is of type CrudRepository. So when i override the save method in MyRepo.class it starts working. I am confused here because MyRepo eventually has CrudRepository extended through JpaRepository. Please let me know how to fix this or what i am doing wrong here.

Also provide help over how to use not expression in pointcut. Say for above example i want to target my all repositories except that have @RepoAware annotation. I created below advice but it's also not working.

@Around("target(org.springframework.data.jpa.repository.JpaRepository) and !@within(com.myproject.annotations.RepoAware)")
public Object logDBMetrics(final ProceedingJoinPoint pjp) throws Throwable {
        return log(pjp,false);
}

above advice get invoked also for repos that have @RepoAware annotation.

Thanks in advance !


Solution

  • The solution for a Spring AOP aspect, i.e. the proxy-based Spring-style AOP framework, looks like this, if you want to avoid runtime reflection:

    @Pointcut(
      "execution(* (" +
      "@com.example.accessingdatajpa.CustomRepositoryAnnotation " +
      "org.springframework.data.repository.CrudRepository+" +
      ").*(..))"
    )
    

    This is targeting

    • executions of any methods
    • in any class where
      • the class is CrudRepository or one of its subclasses (+),
      • the class type is annotated by @CustomRepositoryAnnotation.

    Update 2: A simpler, more generic version of the solution pointcut is:

    @Pointcut("execution(* (@com.example.accessingdatajpa.CustomRepositoryAnnotation *..*).*(..))")
    

    FYI, normally * instead of *..* should work as a simpler replacement for "any class in any package" in AspectJ syntax, but it seems that in this case it is not working.


    What does not work and why:

    • @within(CustomRepositoryAnnotation): The save*, findAll*, delete* methods are not defined in a class annotated by @CustomRepositoryAnnotation, but in CrudRepository.
    • @target(CustomRepositoryAnnotation): Interface annotations are not inherited by implementing classes (therefore also not by dynamic proxies) in Java. This is a general JDK issue and unrelated to Spring or even Spring AOP. See my answer here, scroll down to the "update" section.

    Update 1, answering questions asked in a comment:

    @target(CustomRepositoryAnnotation) ... - how was you able to start application?

    By excluding Spring packages as described in my answer here and also excluding final types from the JDK, which of course cannot subclassed and therefore not be be proxied either:

    "@target(com.example.accessingdatajpa.CustomRepositoryAnnotation)"
        + " && !within(org.springframework..*) && !within(is(FinalType))"
    

    But in this case, the pointcut does not match, because of what I explained above.

    btw, it does not explain why target(MarkerInterface) does work, it seems that annotation support is broken in Spring AOP.

    No, it is not broken, just your expectation that annotations be inherited by classes implementing annotated interfaces is wrong.

    target(MarkerInterface) works, because the runtime type implements the interface, and therefore the pointcut matches. Class inheritance is not the same as annotation inheritance. Like it or not, the latter only exists to the limited degree explained in the answer I linked to and stated in the @Inherited javadoc.