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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/26258158/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 09:35:41  来源:igfitidea点击:

How to instrument / advice a Spring Data (JPA) repository?

javaspringaspectjspring-data-jpaspring-aop

提问by epdittmer

I'm failing in my effort to advice a spring data jpa repository. The goal is to instrument (around) all non-voidpublic methods in a particular repository annotated with a custom annotation (ResourceNotFound in this example) and throw an exception when the return value is either nullor an empty collection.

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

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

The following advice is to wire all public methods of the implementations of the interface annotated with @ResourceNotFound.

以下建议是连接所有用 注释的接口实现的公共方法@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;
}

The publicMethodInsideAClassMarkedWithResourceNotFound(…)method works when the pointcut isspecified as:

publicMethodInsideAClassMarkedWithResourceNotFound(…)方法的工作原理,当切入点isspecified为:

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

However, the @ResourceNotFoundannotation is not being picked up. This might be due to the fact that the underlying class of the repository interface is a (proxied) SimpleJpaRepositorywhich does not have that particular annotation.

但是,@ResourceNotFound注释没有被提取。这可能是因为存储库接口的底层类是一个(代理)SimpleJpaRepository,它没有那个特定的注释。

Is there a way to propagate @ResourceNotFound to the implementation?

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

-- update --

- 更新 -

Changed the question to reflect the fact that the advice (around) only should apply to repositories with a custom annotation.

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

采纳答案by igor.zh

Although the OP heavily relied on AspectJ solutions, the question as it stands doesn't directly suggest that solutions should be limited to AspectJ. Therefore I'd like to offer a non-AspectJ way to advise a Spring Data JPA Repository. It is based upon adding a custom Interceptorinto barebone Spring AOP proxy interceptor chain.

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

First, configure your custom RepositoryFactoryBean, e.g.

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

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

Next, implement CustomRepositoryFactoryBeanto add your own RepositoryProxyPostProcessorto the JpaRepositoryFactory

接下来,实施CustomRepositoryFactoryBean将您自己的添加RepositoryProxyPostProcessorJpaRepositoryFactory

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

}

Your RepositoryProxyPostProcessorimplementation should add your MethodInterceptorto the ProxyFactoryfor a particular Repository (inspect RepositoryInformation):

您的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());
    }

}

and in your MethodInterceptor(which, BTW, is subinterface of org.aopalliance.aop.Advice, so still an advice :) ) you have a full power of AspectJ @Aroundadvice:

并且在您的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;
    }
}   

回答by Oliver Drotbohm

If you want to intercept the repository call on the repository level, you don't actually need to introduce a custom annotation for that. You should be able to get this working with a plain type match:

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

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

This will intercept the execution of all non-voidmethods of all Spring beans that extend the Spring Data Repositoryinterface.

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

A slightly related example can be found in the Spring Data examples repository.

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

回答by epdittmer

I was able to solve my problem using the following construct (basically inspecting the interface chain and search for the specific 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;
}

回答by kriegaex

The problem is not inherent to AspectJ or Spring-AOP but to Java itself:

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

Normally annotations from parent classes are not inherited by subclasses, but you can explicitly use @Inheritedto specify that it should be inherited. Even in this case, inheritance only occurs along the class hierarchy, not from interfaces to implementing classes, see Javadoc:

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

Note that this meta-annotation type has no effect if the annotated type is used to annotate anything other than a class. Note also that this meta-annotation only causes annotations to be inherited from superclasses; annotations on implemented interfaces have no effect.

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

Update:Because I have answered this question several times before, I have just documented the problem and also a workaround in Emulate annotation inheritance for interfaces and methods with AspectJ.

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

Update:If you annotate your implementing classes instead of the interface itself (e.g. by creating an abstract base class which is annotated by the inheritable annotation), you can simplify your advice with the check for void return type etc. like this:

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

回答by Luo C.R

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