如何检测/建议Spring Data(JPA)存储库?

我没有努力建议一个spring data jpa存储库。 目标是检测(围绕)使用自定义注释(在此示例中为ResourceNotFound)注释的特定存储库中的所有非void公共方法,并在返回值为null或空集合时抛出exception。

 @Repository @ResourceNotFound @Transactional(readOnly = true) public interface CityRepository extends JpaRepository, JpaSpecificationExecutor { … } 

以下建议是连接用@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; } 

当切入点指定为:时, publicMethodInsideAClassMarkedWithResourceNotFound(…)方法有效:

 @Pointcut("execution(public * package.CityRepository+.*(..))") 

但是, @ResourceNotFound注释未被拾取。 这可能是由于存储库接口的基础类是(代理的) SimpleJpaRepository而没有该特定注释的事实。

有没有办法将@ResourceNotFound传播给实现?

– 更新 –

更改了问题以反映这样的事实:建议(周围)仅适用于具有自定义注释的存储库。

尽管OP严重依赖于AspectJ解决方案,但现在的问题并没有直接暗示解决方案应该仅限于AspectJ。 因此,我想提供一种非AspectJ方式来建议Spring Data JPA Repository。 它基于将自定义Interceptor添加到准系统Spring AOP代理拦截器链中。

首先,配置您的自定义RepositoryFactoryBean ,例如

 @Configuration @EnableJpaRepositories(repositoryFactoryBeanClass = CustomRepositoryFactoryBean.class) public class ConfigJpaRepositories { } 

接下来,实现CustomRepositoryFactoryBean将您自己的RepositoryProxyPostProcessor添加到JpaRepositoryFactory

 class CustomRepositoryFactoryBean, T , I extends Serializable> extends JpaRepositoryFactoryBean { protected RepositoryFactorySupport createRepositoryFactory(EntityManager em) { RepositoryFactorySupport factory = super.createRepositoryFactory(em); factory.addRepositoryProxyPostProcessor(new ResourceNotFoundProxyPostProcessor()); return factory; } } 

您的RepositoryProxyPostProcessor实现应该将您的MethodInterceptor添加到特定存储库的ProxyFactory (检查RepositoryInformation ):

 class ResourceNotFoundProxyPostProcessor implements RepositoryProxyPostProcessor { @Override public void postProcess(ProxyFactory factory, RepositoryInformation repositoryInformation) { if (repositoryInformation.getRepositoryInterface().equals(CityRepository.class)) factory.addAdvice(new ResourceNotFoundMethodInterceptor()); } } 

并且在你的MethodInterceptor (BTW,是org.aopalliance.aop.Advice子接口,所以仍然是一个建议:))你有一个完整的AspectJ @Around建议:

 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; } } 

如果要拦截存储库级别的存储库调用,则实际上不需要为此引入自定义注释。 您应该可以使用普通类型匹配来实现此function:

  @Pointcut("execution(public !void org.springframework.data.repository.Repository+.*(..))") 

这将拦截扩展Spring Data Repository接口的所有Spring bean的所有非void方法的执行。

可以在Spring Data示例存储库中找到一个稍微相关的示例。

我能够使用以下构造解决我的问题(基本上检查接口链并搜索特定的Annotation):

 @Pointcut("execution(public !void org.springframework.data.repository.Repository+.*(..))") public void publicNonVoidRepositoryMethod() {} @Around("publicNonVoidRepositoryMethod()") public Object publicNonVoidRepositoryMethod(ProceedingJoinPoint pjp) throws Throwable { Object retVal = pjp.proceed(); boolean hasClassAnnotation = false; for(Class i: pjp.getTarget().getClass().getInterfaces()) { if(i.getAnnotation(ThrowResourceNotFound.class) != null) { hasClassAnnotation = true; break; } } if(hasClassAnnotation && isObjectEmpty(retVal)) throw new RuntimeException(messageSource.getMessage("exception.resourceNotFound", new Object[]{}, LocaleContextHolder.getLocale())); return retVal; } 

问题不是AspectJ或Spring-AOP固有的,而是Java本身:

通常,父类的注释不会被子类inheritance,但您可以显式使用@Inherited来指定它应该被inheritance。 即使在这种情况下,inheritance只发生在类层次结构中,而不是从实现类的接口,请参阅Javadoc :

请注意,如果使用带注释的类型来注释除类之外的任何内容,则此元注释类型不起作用。 另请注意,此元注释仅导致注释从超类inheritance; 已实现接口上的注释无效。

更新:因为我之前已经多次回答过这个问题,所以我刚刚记录了问题,并在使用AspectJ模拟接口和方法的注释inheritance时采用了解决方法 。

更新:如果您注释实现类而不是接口本身(例如,通过创建由可inheritance注释注释的抽象基类),您可以通过检查void返回类型等来简化您的建议,如下所示:

 @Around("execution(public !void (@com.digitalmisfits..ResourceNotFound *).*(..))") public Object myAdvice(ProceedingJoinPoint thisJoinPoint) throws Throwable { System.out.println(thisJoinPoint); Object retVal = thisJoinPoint.proceed(); if (isObjectEmpty(retVal)) throw new RuntimeException("Illegal empty result"); return retVal; } 
 Class[] objs = Arrays.stream(joinPoint.getArgs()).map(item -> item.getClass()).toArray(Class[]::new); System.out.println("[AspectJ] args interfaces :"+objs); Class clazz = Class.forName(joinPoint.getSignature().getDeclaringTypeName()); System.out.println("[AspectJ] signature class :"+clazz); Method method = clazz.getDeclaredMethod(joinPoint.getSignature().getName(), objs) ; System.out.println("[AspectJ] signature method :"+method); Query m = method.getDeclaredAnnotation(Query.class) ; System.out.println("[AspectJ] signature annotation value:"+ (m!=null?m.value():m) );