프로그래밍 언어/Java

[Spring] 프록시 팩토리

작은소행성 2024. 5. 6. 22:23

프록시 팩토리란 

프록시 패턴은 인터페이스가 있으면 JDK 동적 프록시를 사용하고 

구체 클래스만 있다면 CGLIB를 사용하며 이 설정을 변경할 수도 있다. 

 

스프링에서 이 두개를 사용할 때 InvocationHandelr, MethodInterceptor 를 따로 만들지 않고 Advice를 호출해서 두개다 호출해서 사용한다. 

스프링에서 Pointcut 이라는 개념으로  프록시 팩토리를 사용해서 Advice만 호출하면 나머지는 스프링에서 호출해서 사용할 수 있게해 이 문제를 일관성 있게 해결한다. 

 

 

프록시 팩토리 관련 자주 사용하는 용어 정리

포인트컷(pointcut)

어디에 부가 기능을 적용할지 판단하는 필터링 로직 

주로 클래스와 메서드 이름으로 필터링함

어떤 point에 기능을 적용할지 안할지 잘라서(cut) 구분하는 것

 

어드바이스(advice)

프록시가 호출하는 부가 기능 

단순하게 프록시 로직이라 생각하면 됨

 

어드바이저(advisor)

하나의 포인트컷과 하나의 어드바이스를 가지고 있는것 

 

역할과 책임

이 용어를 구분하는 것은 역할과 책임을 명확하게 분리한 것으로 

포인트컷은 대상 여부를 확인하는 필터 역할만 담당하고 

어드바이스는 부가 기능 로직만 담당한다. 

어드바이저는 이 두개를 합치면 된다.  advisor = pointcut + advice

 

어드바이저 예시

프록시 팩토리를 통해 프록시를 생성할 때 어드바이저를 제공하면 어디에 어떤 기능을 제공하는지 알 수 있다. 

@Slf4j
public class AdvisorTest {

    @Test
    void advisorTest1() {
        ServiceInterface target = new ServiceImpl();
        ProxyFactory proxyFactory = new ProxyFactory(target);
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(Pointcut.TRUE, new TimeAdvice());
        proxyFactory.addAdvisor(advisor);
        ServiceInterface proxy = (ServiceInterface) proxyFactory.getProxy();

        proxy.save();
        proxy.find();
    }
}

 

  • new DefaultPointcutAdvisor : Advisor 인터페이스의 가장 일반적인 구현체로 생성자를 통해 하나의 pointcut, advice를 넣어준다.
  • new TimeAdvice() : 아래 코드 내용으로 어드바이스를 제공한다. 
  • proxyFactory.addAdvisor(advisor) : 프록시 팩토리에 적용할 어드바이저를 지정함으로 어디에 어떤 부가 기능을 적용해야할 지 어드바이저 하나로 알 수 있다.
@Slf4j
public class TimeAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        log.info("TimeProxy 실행");
        long startTime = System.currentTimeMillis();

        Object result = invocation.proceed();

        long endTime = System.currentTimeMillis();
        long resultTime = endTime - startTime;
        log.info("TimeProxy 종료 resultTime={}", resultTime);
        return result;
    }
}

 

 

 

포인트컷 예시

포인트컷은 크게 ClassFilter와 MethodMatcher로 이루어진다. 

이름 그대로 클래스가 맞는지 메서드가 맞는지 확인할 때 사용하며 

둘다 true로 반환해야 어드바이스를 사용할 수 있다. 

 

pointcut 은 스프링에서 인터페이스를 제공한다. 우리는 스프링에서 만든 구현체를 사용한다. 

 

 

스프링에서는 해당 내용 말고도 많은 포인트컷을 제공한다. 

  • NameMatchMethodPointcut : 메서드 이름을 기반으로 매칭하며 내부에서는 PatternMatchUtils를 사용한다. 
    ex) *xxx* 허용
  • JdkRegexpMethodPointcut : JDK 정규 표현식을 기반으로 포인트컷을 매칭한다. 
  • TruePointcut : 항상 참을 반환한다. 
  • AnnotationMatchingPointcut : 어노테이션으로 매칭한다. 
  • AspectJExpressionPointcut : aspectJ표현식으로 매칭한다. 
    실무에서는 해당 포인트컷이 사용이 편리하기도 하고 기능이 많아서 AspectJExpressionPointcut 을 많이 사용한다. 

 

NameMatchMethodPointcut 사용 예시 

    @Test
    void advisorTest() {
        ServiceInterface target = new ServiceImpl();
        ProxyFactory proxyFactory = new ProxyFactory(target);
        NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
        pointcut.setMappedNames("save");
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, new TimeAdvice());
        proxyFactory.addAdvisor(advisor);
        ServiceInterface proxy = (ServiceInterface) proxyFactory.getProxy();

        proxy.save();
        proxy.find();
    }

 

setMappedNames 에 save를 넣음으로 save는 타임프록시가 적용된 것을 볼 수 있는데 find 는 타임 프록시가 적용되지 않았다. 

 

 

하나의 프록시에 여러 어드바이저 적용 예시

 

    @Test
    void multiAdvisorTest() {
        //client -> proxy -> advisor2 -> advisor1 -> target

        DefaultPointcutAdvisor advisor1 = new DefaultPointcutAdvisor(Pointcut.TRUE, new Advice1());
        DefaultPointcutAdvisor advisor2 = new DefaultPointcutAdvisor(Pointcut.TRUE, new Advice2());

        //프록시1 생성
        ServiceInterface target = new ServiceImpl();
        ProxyFactory proxyFactory1 = new ProxyFactory(target);

        proxyFactory1.addAdvisor(advisor2);
        proxyFactory1.addAdvisor(advisor1);
        ServiceInterface proxy = (ServiceInterface) proxyFactory1.getProxy();

        //실행
        proxy.save();
    }

 

포인트 컷이 true 일때만 advice 를 호출하며 

ProxyFactory 하나만 만들고 addAdvisor를 사용한다. 

 

 +

하나의 프록시에서 여러 어드바이저 사용하는 예시 부분을 이해하고 가면 좋은게 

AOP 사용시, AOP를 적용하면 적용하는 수만큼 프록시가 생성된다고 착각하게 되는데 

위의 예시처럼 프록시는 하나만 만들고 하나의 프록시에 여러 어드바이저를 적용해서 사용하는 것이다. 

 

 

프록시 팩토리 방식의 문제점 

1. 너무 많은 설정

실제 빈을 등록해주기 전, 프록시 객체를 등록해주기 위해 동적 프록시 생성 코드를 작성해 빈으로 등록해줘야 하는데

100개의 스프링 빈이 있으면 100개의 동적 프록시 생성 코드를 만들어야 한다. 

 

2. 컴포넌트 스캔 

컴포넌트 스캔으로 등록된 스프링 빈에는 프록시 적용이 불가능하다. 

 

이러한 문제점들로 실무에서는 빈 후처리기 방식이나 @Aspect 어노테이션을 사용해서 동적으로 프록시를 생성해서 사용한다. 

 

 

 

결과적으로 포인트 컷은 다음 두 곳에서 사용된다.

  • 프록시 적용 대상 여부를 체크해서 꼭 필요한 곳에만 프록시를 적용한다.(빈 후처리기 - 자동 프록시 생성) 
  • 프록시의 어떤 메서드가 호출 되었을 때 어드바이스를 적용할지 판단한다. (프록시 내부) 
반응형