Java 如何为Spring数据(JPA)存储库提供工具/建议?
我为spring数据jpa存储库提供建议的努力失败了。目标是在使用自定义注释(本例中为ResourceNotFound)注释的特定存储库中插入(绕过)所有非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
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;
}
}