Intercept all (including inherited) methods of one interface(type) annotated

587 Views Asked by At

I'm trying to make an pointcut of type @Around to intercept all methods of beans annotated with @Repository.

I've tried

@Around("execution(* (@org.springframework.stereotype.Repository *).*(..))")
public void aspect() {

}

Also(should be same)

@Around("@within(org.springframework.stereotype.Repository)")
public void aspect() {

}

From my testing, these poincut expressions match just the methods from the type directly annotated(not the ones inherited like the ones autogenerated DAO (findAll, findById). I want to intercept all methods (from child type annotated and also inherited methods from other types like CrudRepository.

One of usages is this

@org.springframework.stereotype.Repository
public interface SubRepository extends CrudRepository {
    
}

How could i implement this or it is not possible?. Thank you.

2

There are 2 best solutions below

2
kriegaex On

Here is an MCVE. Next time, please provide one by yourself. It should not be my job to do that for you.

Interface + sub-interface:

package de.scrum_master.spring.q70824392;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface SubRepository extends CrudRepository {
  String greet(String who);
}
package de.scrum_master.spring.q70824392;

public interface SubSubRepository extends SubRepository {
  int add(int a, int b);
}

Abstract class extending sub-interface + concrete class extending it:

package de.scrum_master.spring.q70824392;

import java.util.Optional;

public abstract class MyAbstractRepository implements SubSubRepository {
  @Override public Object save(Object entity) { return null; }
  @Override public Iterable saveAll(Iterable entities) { return null; }
  @Override public Optional findById(Object o) { return Optional.empty(); }
  @Override public boolean existsById(Object o) { return false; }
  @Override public Iterable findAll() { return null; }
  @Override public Iterable findAllById(Iterable iterable) { return null; }
  @Override public long count() { return 0; }
  @Override public void deleteById(Object o) {}
  @Override public void delete(Object entity) {}
  @Override public void deleteAllById(Iterable iterable) {}
  @Override public void deleteAll(Iterable entities) {}
  @Override public void deleteAll() {}
}
package de.scrum_master.spring.q70824392;

import org.springframework.stereotype.Component;

@Component
public class MyRepository extends MyAbstractRepository {
  @Override
  public String greet(String who) {
    return "Hello " + who;
  }

  @Override
  public int add(int a, int b) {
    return a + b;
  }
}

Class directly annotated with @Repository as a counter-example:

package de.scrum_master.spring.q70824392;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Component
@Repository
public class DirectlyAnnotatedRepository implements CrudRepository {
  public void doSomething() {}

  @Override public Object save(Object entity) { return null; }
  @Override public Iterable saveAll(Iterable entities) { return null; }
  @Override public Optional findById(Object o) { return Optional.empty(); }
  @Override public boolean existsById(Object o) { return false; }
  @Override public Iterable findAll() { return null; }
  @Override public Iterable findAllById(Iterable iterable) { return null; }
  @Override public long count() { return 0; }
  @Override public void deleteById(Object o) {}
  @Override public void delete(Object entity) {}
  @Override public void deleteAllById(Iterable iterable) {}
  @Override public void deleteAll(Iterable entities) {}
  @Override public void deleteAll() {}
}

Demo application:

package de.scrum_master.spring.q70824392;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;

@SpringBootApplication
@Configuration
public class DemoApplication {
  public static void main(String[] args) throws Throwable {
    try (ConfigurableApplicationContext appContext = SpringApplication.run(DemoApplication.class, args)) {
      doStuff(appContext);
    }
  }

  private static void doStuff(ConfigurableApplicationContext appContext) {
    DirectlyAnnotatedRepository directlyAnnotatedRepository = appContext.getBean(DirectlyAnnotatedRepository.class);
    directlyAnnotatedRepository.doSomething();
    directlyAnnotatedRepository.deleteAll();
    System.out.println("----------");

    SubSubRepository repo = appContext.getBean(SubSubRepository.class);
    repo.add(3, 4);
    repo.greet("world");
    repo.deleteAll();
  }
}

Aspect:

package de.scrum_master.spring.q70824392;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MyAspect {
  @Before("@within(org.springframework.stereotype.Repository)")
  public void directlyAnnotated(JoinPoint joinPoint) {
    System.out.println("Directly annotated: " + joinPoint);
  }

  @Before(
    "within((@org.springframework.stereotype.Repository *)+) && " +
    "within(de.scrum_master.spring.q70824392..*)"
  )
  public void subClassesSubInterfaces(JoinPoint joinPoint) {
    System.out.println("Sub-interfaces, sub-classes: " + joinPoint);
  }
}

Like you said, the first advice should only kick in for classes directly annotated with @Repository, i.e. in this example for the DirectlyAnnotatedRepository bean.

The second advice however targets all types annotated with @Repository and their subtypes - please note the +. Depending on what we want, we could add one more execution pointcut to limit the intercepted methods, but I simply chose all. Please also note that without the second within pointcut, we would target many Spring-internal components, too, which would work for some, but throw exceptions for others. So we are limiting the scope to only repository beans in our own application. Instead, we could also exclude the Spring ones we do not want.

Console log:

Directly annotated: execution(void de.scrum_master.spring.q70824392.DirectlyAnnotatedRepository.doSomething())
Sub-interfaces, sub-classes: execution(void de.scrum_master.spring.q70824392.DirectlyAnnotatedRepository.doSomething())
Directly annotated: execution(void de.scrum_master.spring.q70824392.DirectlyAnnotatedRepository.deleteAll())
Sub-interfaces, sub-classes: execution(void de.scrum_master.spring.q70824392.DirectlyAnnotatedRepository.deleteAll())
----------
Sub-interfaces, sub-classes: execution(int de.scrum_master.spring.q70824392.MyRepository.add(int,int))
Sub-interfaces, sub-classes: execution(String de.scrum_master.spring.q70824392.MyRepository.greet(String))
Sub-interfaces, sub-classes: execution(void de.scrum_master.spring.q70824392.MyAbstractRepository.deleteAll())
0
Peter Litvak On

If you have your repository classes/interfaces all named with *Repository suffix, what you need can be archived with the following:

@Around("execution(public * *..*Repository.*(..))")
    public Object aroundAnyRepositoryMethod(
        ProceedingJoinPoint joinPoint) throws Throwable {
    ...
}

or if you usually put your repositories in the separate sub-packages, then something like this to further filter:

@Around("execution(public * com.pkg1.pkg2..repository..*Repository.*(..))")
    public Object aroundAnyRepositoryMethod(
        ProceedingJoinPoint joinPoint) throws Throwable {
    ...
}

This will pick out all methods in your repository and all methods that it inherits or overrides.