Java 如何为Spring数据(JPA)存储库提供工具/建议?

Java 如何为Spring数据(JPA)存储库提供工具/建议?,java,spring,aspectj,spring-data-jpa,spring-aop,Java,Spring,Aspectj,Spring Data Jpa,Spring Aop,我为spring数据jpa存储库提供建议的努力失败了。目标是在使用自定义注释(本例中为ResourceNotFound)注释的特定存储库中插入(绕过)所有非void公共方法,并在返回值为null或空集合时引发异常 @Repository @ResourceNotFound @Transactional(readOnly = true) public interface CityRepository extends JpaRepository<City, Long>, JpaSpec

我为spring数据jpa存储库提供建议的努力失败了。目标是在使用自定义注释(本例中为ResourceNotFound)注释的特定存储库中插入(绕过)所有非
void
公共方法,并在返回值为
null
或空集合时引发异常

@Repository 
@ResourceNotFound
@Transactional(readOnly = true)
public interface CityRepository extends JpaRepository<City, Long>, JpaSpecificationExecutor<City> { … }
当切入点被指定为以下内容时,
publicMethodInsideAClassMarkedWithResourceNotFound(…)
方法将起作用:

@Pointcut("execution(public * package.CityRepository+.*(..))")
但是,未拾取
@ResourceNotFound
注释。这可能是因为存储库接口的底层类是一个(代理的)
SimpleJpaRepository
,它没有特定的注释

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

--更新--


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

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

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

请注意,如果注释类型用于注释类以外的任何内容,则此元注释类型无效。还要注意,这个元注释只会导致注释从超类继承已实现接口上的注释无效。

更新:因为我以前已经回答过好几次这个问题,所以我刚刚记录了这个问题,以及中的解决方法

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

@Around(“执行(public!void(@com.digitalmisfits..ResourceNotFound*)。(..)”)
公共对象myAdvice(ProceedingJoinPoint thisJoinPoint)抛出可丢弃的{
System.out.println(此连接点);
Object retVal=thisJoinPoint.continue();
如果(isObjectEmpty(retVal))
抛出新的RuntimeException(“非法的空结果”);
返回返回;
}

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

 @Pointcut("execution(public !void org.springframework.data.repository.Repository+.*(..))")
这将拦截所有扩展Spring数据
存储库
接口的SpringBean的所有非
void
方法的执行


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

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

@Pointcut(“执行(public!void org.springframework.data.repository.repository+.*(…)”)
public void publicNonovoidRepositoryMethod(){}
@周围(“PublicNonovoidRepositoryMethod()”)
公共对象PublicNonovoidRepositoryMethod(ProceedingJoinPoint pjp)抛出可丢弃的{
Object retVal=pjp.procedure();
布尔hasClassAnnotation=false;
对于(类i:pjp.getTarget().getClass().getInterfaces()){
if(i.getAnnotation(ThrowResourceNotFound.class)!=null){
hasClassAnnotation=true;
打破
}
}
if(hasClassAnnotation&&isObjectEmpty(retVal))
抛出新的RuntimeException(messageSource.getMessage(“exception.resourceNotFound”,新对象[]{},LocaleContextHolder.getLocale());
返回返回;
}

尽管OP严重依赖AspectJ解决方案,但目前的问题并不直接表明解决方案应该局限于AspectJ。 因此,我想提供一种非AspectJ的方法来建议Spring数据JPA存储库。它基于在barebone 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;
  }

}
在您的
MethodInterceptor
(顺便说一句,它是
org.aopalliance.aop.Advice
的子接口,所以仍然是一个Advice:)中,您拥有AspectJ
@的全部功能

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

谢谢你的回复。Spring Data JPA的问题是生成了一个动态代理(基于SimpleParepository),这超出了我们的控制范围,因此您无法将注释添加到实际实现中(另外,在实现类上应用ResourceNotFound注释将不允许在单独的存储库上进行任何专门化)。当@Transactional应用于接口本身时,它是开箱即用的;没有发现这是如何实现的您可以在Spring中选择使用基于类的CGLIB代理。它们被创建为子类,因此继承声明为
@Inheritable
的注释。似乎基于类的CGLIB代理被标记为final,从而阻止进一步的子类化(在目标设置时引发异常)。您应该扩展类,而不是代理。这些应该是树上的“叶子”。底层类由Spring数据管理。基本上,您可以定义一个接口,应用存储库注释,并使用应用程序上下文中的EnableJpaRepositories注释启用它们。注入存储库时,spring数据生成一个代理(基于SimpleParepository)并注入它们。这归结为扩展代理而不是类i假设你读过这个问题了吗?那家伙自己已经走了那么远。他想要的不是你描述的
@Configuration
@EnableJpaRepositories(repositoryFactoryBeanClass = CustomRepositoryFactoryBean.class)
public class ConfigJpaRepositories {
}
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;
  }

}
class ResourceNotFoundProxyPostProcessor implements RepositoryProxyPostProcessor {

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

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