如何检测/建议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) );