I'm failing in my effort to advice a spring data jpa repository. The goal is to instrument (around) all non-void
public methods in a particular repository annotated with a custom annotation (ResourceNotFound in this example) and throw an exception when the return value is either null
or an empty collection.
@Repository
@ResourceNotFound
@Transactional(readOnly = true)
public interface CityRepository extends JpaRepository<City, Long>, JpaSpecificationExecutor<City> { … }
The following advice is to wire all public methods of the implementations of the interface annotated with @ResourceNotFound
.
@Pointcut("within(com.digitalmisfits.spring.aop.annotation.ResourceNotFound *)")
public void beanAnnotatedWithResourceNotFound() {}
@Pointcut("execution(public * *(..))")
public void publicMethod() {}
@Around("beanAnnotatedWithResourceNotFound() && publicMethod()")
public Object publicMethodInsideAClassMarkedWithResourceNotFound(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("publicMethodInsideAClassMarkedWithResourceNotFound " + pjp.getTarget().toString());;
Object retVal = pjp.proceed();
if(((MethodSignature) pjp.getSignature()).getReturnType() != Void.TYPE && isObjectEmpty(retVal))
throw new RuntimeException("isObjectEmpty == true");
return retVal;
}
The publicMethodInsideAClassMarkedWithResourceNotFound(…)
method works when the pointcut isspecified as:
@Pointcut("execution(public * package.CityRepository+.*(..))")
However, the @ResourceNotFound
annotation is not being picked up. This might be due to the fact that the underlying class of the repository interface is a (proxied) SimpleJpaRepository
which does not have that particular annotation.
Is there a way to propagate @ResourceNotFound to the implementation?
-- update --
Changed the question to reflect the fact that the advice (around) only should apply to repositories with a custom annotation.
If you want to intercept the repository call on the repository level, you don't actually need to introduce a custom annotation for that. You should be able to get this working with a plain type match:
@Pointcut("execution(public !void org.springframework.data.repository.Repository+.*(..))")
This will intercept the execution of all non-void
methods of all Spring beans that extend the Spring Data Repository
interface.
A slightly related example can be found in the Spring Data examples repository.
Although the OP heavily relied on AspectJ solutions, the question as it stands doesn't directly suggest that solutions should be limited to AspectJ.
Therefore I'd like to offer a non-AspectJ way to advise a Spring Data JPA Repository. It is based upon adding a custom Interceptor
into barebone Spring AOP proxy interceptor chain.
First, configure your custom RepositoryFactoryBean
, e.g.
@Configuration
@EnableJpaRepositories(repositoryFactoryBeanClass = CustomRepositoryFactoryBean.class)
public class ConfigJpaRepositories {
}
Next, implement CustomRepositoryFactoryBean
to add your own RepositoryProxyPostProcessor
to the JpaRepositoryFactory
class CustomRepositoryFactoryBean<R extends JpaRepository<T, I>, T , I extends Serializable> extends JpaRepositoryFactoryBean<R, T, I> {
protected RepositoryFactorySupport createRepositoryFactory(EntityManager em) {
RepositoryFactorySupport factory = super.createRepositoryFactory(em);
factory.addRepositoryProxyPostProcessor(new ResourceNotFoundProxyPostProcessor());
return factory;
}
}
Your RepositoryProxyPostProcessor
implementation should add your MethodInterceptor
to the ProxyFactory
for a particular Repository (inspect RepositoryInformation
):
class ResourceNotFoundProxyPostProcessor implements RepositoryProxyPostProcessor {
@Override
public void postProcess(ProxyFactory factory, RepositoryInformation repositoryInformation) {
if (repositoryInformation.getRepositoryInterface().equals(CityRepository.class))
factory.addAdvice(new ResourceNotFoundMethodInterceptor());
}
}
and in your MethodInterceptor
(which, BTW, is subinterface of org.aopalliance.aop.Advice
, so still an advice :) ) you have a full power of AspectJ @Around
advice:
class ResourceNotFoundMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
ResourceNotFound resourceNotFound = method.getAnnotation(ResourceNotFound.class);
//...
Object result = invocation.proceed();
//...
return result;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With