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

时间:2014-10-08 13:29:44

标签: java spring aspectj spring-data-jpa spring-aop

我没有努力建议一个spring data jpa存储库。目标是在一个特定的存储库中使用自定义注释(本例中为ResourceNotFound)注释所有非void公共方法,并在返回值为null或者为@Repository @ResourceNotFound @Transactional(readOnly = true) public interface CityRepository extends JpaRepository<City, Long>, JpaSpecificationExecutor<City> { … } 时抛出异常空集合。

@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注释未被提取。这可能是由于存储库接口的基础类是(代理的){{1}}而没有特定的注释。

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

- 更新 -

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

5 个答案:

答案 0 :(得分:9)

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

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

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

可以在Spring Data examples repository中找到一个稍微相关的示例。

答案 1 :(得分:4)

尽管OP严重依赖于AspectJ解决方案,但目前的问题并不直接表明解决方案应限于AspectJ。 因此,我想提供一种非AspectJ的方法来建议Spring Data JPA存储库。它基于将自定义Interceptor添加到准系统Spring AOP代理拦截器链中。

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

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

下一步,实现CustomRepositoryFactoryBean,将自己的RepositoryProxyPostProcessor添加到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;
  }

}

您的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(顺便说一句,它是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;
    }
}   

答案 2 :(得分:3)

我能够使用以下构造解决我的问题(基本上检查接口链并搜索特定的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;
}

答案 3 :(得分:1)

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

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

  

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

更新:因为我之前已经多次回答过这个问题,所以我刚刚记录了问题以及Emulate annotation inheritance for interfaces and methods with AspectJ中的解决方法。

更新:如果您注释实现类而不是接口本身(例如,通过创建由可继承注释注释的抽象基类),您可以通过检查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;
}

答案 4 :(得分:-2)

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