Home > OS >  spring aop @within not working correctly for custom annotation
spring aop @within not working correctly for custom annotation

Time:07-11

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 !

CodePudding user response:

AspectJ has very limited functionality in case of spring infrastructure, however it is possible to implement your requirements via advisors (please also check: Spring Advisor in Java using @Bean)

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
@Inherited
public @interface CustomRepositoryAnnotation {
}


@Component
public class CustomRepositoryAnnotationAdvisor extends AbstractPointcutAdvisor {

    private static final Logger log = LoggerFactory.getLogger(CustomRepositoryAnnotationAdvisor.class);

    private final Advice advice;

    private final Pointcut pointcut;

    public CustomRepositoryAnnotationAdvisor() {
        this.advice = new MethodInterceptor() {
            @Nullable
            @Override
            public Object invoke(@NonNull MethodInvocation invocation) throws Throwable {
                log.info("DemoAnnotationAdvisor: {}", invocation.getMethod().getName());
                return invocation.proceed();
            }
        };
        this.pointcut = new AnnotationMatchingPointcut(CustomRepositoryAnnotation.class, null, true);
    }

    @Override
    public Pointcut getPointcut() {
        return pointcut;
    }

    @Override
    public Advice getAdvice() {
        return advice;
    }

}

Demo project on GH


UPD.

Have performed some research on the topic and found a brilliant article about Spring and AspectJ pointcut designators

First of all, @within is not supposed to work in your case (intercepting superinterface methods) - you ought to use @target, unfortunately @target pointcut designator seems to be broken in Spring AOP: existence of advice with single @target pointcut causes spring to create CGLIB proxies for every bean, which is undesirable and even not possible (jdk classes, final classes, classes without public constructor, etc), attempt to narrow down the scope of advice also fails: for example spring does not apply advice @target(annotation) && target(JpaRepository) for unknown reason.

At second, in case of Spring APO & AspectJ it is possible to achieve your requirements via introducing marker interface and using target(marker interface) as poincut designator, however, I would prefer to stay clear of those puzzles.

CodePudding user response:

I finally able to get it worked and below is the approach i used. Thanks to @Andrey B. Panfilov.

I created an marker interface which is extended by MyRepo and then in my around advice i used class type to check if it is assignable from that interface. If yes then log with true otherwise false.

@Around("target(org.springframework.data.jpa.repository.JpaRepository)")
public void log(final ProceedingJoinPoint jp){
  Class<?> clazz=MyRepoInterface.class;
  return clazz.isAssignableFrom(pjp.getTarget().getClass())?log(jp,true):log(pjp,false);
}

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

-----
public interface MyRepoInterface{}

spring-aop behaviour is still unknown for some point cuts. Like using AND , NOT with expression doesn't seems to work. I tried several mentioned approaches but none of them worked.

  • Related