Java/Spring

[AOP / Spring] 프록시 팩토리 - 포인트컷, 어드바이스, 어드바이저

jaamong 2023. 3. 30. 09:54
 

스프링 핵심 원리 - 고급편 - 인프런 | 강의

스프링의 핵심 원리와 고급 기술들을 깊이있게 학습하고, 스프링을 자신있게 사용할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com

섹션 6. 스프링이 지원하는 프록시

 

 

 

프록시 팩토리를 사용하기 전 동적 프록시부터 알아보자.

 

[Spring]동적 프록시 - JDK 동적 프록시, CGLIB

스프링 핵심 원리 - 고급편 - 인프런 | 강의 스프링의 핵심 원리와 고급 기술들을 깊이있게 학습하고, 스프링을 자신있게 사용할 수 있습니다., - 강의 소개 | 인프런 www.inflearn.com 섹션 5. 동적 프

backend-jaamong.tistory.com

 

프록시 팩토리(ProxyFactory)

프록시팩토리는 스프링이 지원하는 프록시로 편리하게 프록시를 사용할 수 있게 한다. 동적 프록시는 인터페이스가 있을 때와 구체 클래스만 있을 때를 구분하여 사용해야 하지만 프록시 팩토리는 이러한 부분을 자동으로 처리해 준다.

  • 포인트컷(Pointcut) : 어디에 부가 기능을 적용할지, 하지 않을지 판단하는 필터링 로직
  • 어드바이스(Advice) : 프록시가 호출하는 부가 기능(프록시가 수행해야 하는 로직, 동적 프록시의 handler/callback을 떠올려 보자)
  • 어드바이저(Advisor) : 하나의 포인트컷과 하나의 어드바이스를 가지고 있는 것(포인트컷1 + 어드바이스1)

프록시로 부가 기능 로직을 적용해야 할 때 포인트컷으로 어디에 적용할지 선택하고, 어드바이스로 어떤 로직을 적용할지 선택한다. 어디에 어떤 로직을 모두 알고 있는 것이 어드바이저이다.

 

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class XxxAdvice implements MethodInterceptor {

    ...
    
    @Override
    public Object invoke(MethodInvocation invocation) {
       
       	...
		
        //로직 호출
        Object result = invocation.proceed(); //target을 찾아서 target 인스턴스의 메서드 호출
			
        ...
    }
}

어드바이저를 사용하기 위해서는 포인트컷과 어드바이스가 필요하다. 직접 만드는 어드바이스는 MethodInterceptor(org.aopaliiance.intercept.MethodInterceptor)를 구현하여 invoke()를 오버라이드 해야 한다. 이전에 동적프록시를 사용했던 것 처럼 인자가 많지 않고, MethodInvocation만 존재한다. 여기에 target의 정보와 메서드의 정보가 모두 포함되어 있다. target의 메서드를 호출할 때는 MethodInvocation.proceed()를 사용한다.

 

@Configuration
public class ProxyFactoryConfig { //인터페이스가 있는 버전

    ...
  

    @Bean
    public OrderRepository orderRepository() {
        OrderRepository orderRepository = new OrderRepositoryImpl(); //구체클래스만 있어도 여기만 고치면 된다.

        ProxyFactory factory = new ProxyFactory(orderRepository);
        factory.addAdvisor(getAdvisor());
        OrderRepositoryV1 proxy = (OrderRepositoryV1) factory.getProxy();

        return proxy;
    }

    private Advisor getAdvisor() {
        //pointcut
        NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
        pointcut.setMappedNames("request*", "order*", "save*");
        //advice
        LogTraceAdvice advice = new LogTraceAdvice();

        return new DefaultPointcutAdvisor(pointcut, advice);
    }
}

위 코드는 인터페이스가 있을 때 프록시 팩토리를 사용하여 프록시를 만든다. 우선 프록시 팩토리를 만들고 여기에 target을 넣어준다. 여기에서는 orderRepository가 target이 된다. 그다음 ProxyFactory.addAdvisor()를 사용하여 만든 프록시를 팩토리에 어드바이저를 넣어준다. 

 

포인트컷은 여러 버전이 존재하는데 여기서는 메서드의 이름이 매칭되는지 확인하는 포인트컷을 사용한다.  setMappedNames()에 통과시킬 메서드 이름을 넣는다. 어드바이스는 MethodInterceptor를 구현한 클래스를 사용한다. 마지막으로 DefaultPointcutAdvisor를 사용하여 여기에 생성한 포인트컷과 어드바이스를 넣는다. (어드바이저에 포인트컷 없이 어드바이스만 넣어줘도 Pointcut.TRUE가 디폴트로 넘어간다). 어드바이저의 종류는 다양하니 상황에 따라 적절한 것으로 사용한다.

 

이렇게 프록시 팩토리에 어드바이저를 넣어주고 그다음 ProxyFactory.getProxy()로 프록시를 획득한다. 프록시는 target의 타입을 기반으로 생성되므로 타입컨버팅으로 타입을 변경한다. 마지막으로 생성한 프록시를 리턴하면, 프록시가 빈으로 등록된다.

 

!중요! 스프링은 하나의 프록시에 여러 어드바이저 적용이 가능하다. 즉, 어드바이저 수만큼 프록시를 만드는 게 아니라는 의미이다.