복붙노트

[SPRING] 스프링 데이터 (JPA) 저장소를 계측 / 조언하는 방법은 무엇입니까?

SPRING

스프링 데이터 (JPA) 저장소를 계측 / 조언하는 방법은 무엇입니까?

나는 봄 데이터 jpa 저장소에 대한 충고에 실패하고있다. 목표는 커스텀 주석 (이 예에서는 ResourceNotFound)으로 주석 된 특정의 리포지터리 (repository) 내의 비 void 공개 메소드를 모두 계측 해, 반환 값이 하늘 또는 공집 인 경우에 예외를 슬로우하는 것입니다.

@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이 다음과 같이 지정 될 때 작동합니다 :

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

그러나 @ResourceNotFound 주석은 선택되지 않습니다. 이는 저장소 인터페이스의 기본 클래스가 해당 특수 주석이없는 (프록시 된) SimpleJpaRepository라는 사실 때문일 수 있습니다.

@ResourceNotFound를 구현에 전달할 수있는 방법이 있습니까?

- 업데이트 -

조언 (주위)이 맞춤 주석이있는 리포지토리에만 적용되어야한다는 사실을 반영하도록 질문을 변경했습니다.

해결법

  1. ==============================

    1.OP가 AspectJ 솔루션에 크게 의존했지만, 문제는 AspectJ로 솔루션을 제한해야한다는 직접적인 제안은 아닙니다. 따라서 스프링 데이터 JPA 저장소에 조언을 제공하는 비 AspectJ 방식을 제안하고자한다. 그것은 Barebone Spring AOP 프록시 인터셉터 체인에 커스텀 인터셉터를 추가 할 때 사용됩니다.

    OP가 AspectJ 솔루션에 크게 의존했지만, 문제는 AspectJ로 솔루션을 제한해야한다는 직접적인 제안은 아닙니다. 따라서 스프링 데이터 JPA 저장소에 조언을 제공하는 비 AspectJ 방식을 제안하고자한다. 그것은 Barebone Spring AOP 프록시 인터셉터 체인에 커스텀 인터셉터를 추가 할 때 사용됩니다.

    먼저 사용자 정의 RepositoryFactoryBean을 구성하십시오.

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

    그런 다음 CustomRepositoryFactoryBean을 구현하여 JpaRepositoryFactory에 고유 한 RepositoryProxyPostProcessor를 추가합니다.

    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를 특정 Repository의 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 advice :

    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. ==============================

    2.저장소 수준에서 저장소 호출을 가로 채기를 원한다면 실제로 사용자 지정 주석을 도입 할 필요는 없습니다. 일반 유형 일치로이 작업을 수행 할 수 있어야합니다.

    저장소 수준에서 저장소 호출을 가로 채기를 원한다면 실제로 사용자 지정 주석을 도입 할 필요는 없습니다. 일반 유형 일치로이 작업을 수행 할 수 있어야합니다.

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

    Spring Data Repository 인터페이스를 확장하는 모든 Spring bean의 void가 아닌 모든 메소드의 실행을 차단합니다.

    약간 관련된 예제는 Spring Data Examples 저장소에서 찾을 수 있습니다.

  3. ==============================

    3.다음 구문을 사용하여 문제를 해결할 수있었습니다 (기본적으로 인터페이스 체인을 검사하고 특정 Annotation을 검색).

    다음 구문을 사용하여 문제를 해결할 수있었습니다 (기본적으로 인터페이스 체인을 검사하고 특정 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;
    }
    
  4. ==============================

    4.문제는 AspectJ 나 Spring-AOP가 아니라 Java 자체에있다.

    문제는 AspectJ 나 Spring-AOP가 아니라 Java 자체에있다.

    일반적으로 상위 클래스의 주석은 하위 클래스에 상속되지 않지만 명시 적으로 @Inherited를 사용하여 상속되어야한다고 지정할 수 있습니다. 이 경우에도 상속은 인터페이스에서 클래스를 구현하는 것이 아니라 클래스 계층을 따라 수행됩니다 (Javadoc 참조).

  5. ==============================

    5.

    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) );
    
  6. from https://stackoverflow.com/questions/26258158/how-to-instrument-advice-a-spring-data-jpa-repository by cc-by-sa and MIT license