4 분 소요

 인프런 스프링 핵심 원리 - 고급편을 학습하고 정리한 내용 입니다.

프록시 팩토리 - 적용 1

지금까지 학습한 프록시 팩토리를 사용해서 애플리케이션에 프록시를 만들어보자.

먼저 인터페이스가 있는 v1 애플리케이션에 LogTrace기능을 프록시 팩토리를 통해서 프록시를 만들어 적용해보자.

먼저 어드바이스를 만들자.

해당 v3_proxyfactory.advice에 만든다.

LogTraceAdvice

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class LogTraceAdvice implements MethodInterceptor {  
  
    private final LogTrace logTrace;  
  
    public LogTraceAdvice(LogTrace logTrace) {  
        this.logTrace = logTrace;  
    }  
  
    @Override  
    public Object invoke(MethodInvocation invocation) throws Throwable {  
  
        TraceStatus status = null;  
        try {  
            Method method = invocation.getMethod();  
            String message = method.getDeclaringClass().getSimpleName() +  
                    "." + method.getName() + "()";  
            status = logTrace.begin(message);  
  
            //로직 호출  
            Object result = invocation.proceed();  
            logTrace.end(status);  
            return result;  
        } catch (Exception e) {  
            logTrace.exception(status, e);  
            throw e;  
        }  
    }  
}

MethodInterceptor 인터페이스 구현해서 invoke메서드 작성하면 된다.

invocation.getMethod()하면 메서드를 꺼낼 수 있기 때문에 매우 쉽다.

그 이외 로직은 v2_dynamicproxy때와 같다.

이제 config를 작성하자

ProxyFactoryConfigV1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@Slf4j  
@Configuration  
public class ProxyFactoryConfigV1 {  
  
  
    @Bean  
    public OrderControllerV1 orderControllerV1(LogTrace logTrace) {  
        OrderControllerV1 orderController = new OrderControllerV1Impl(orderServiceV1(logTrace));  
        ProxyFactory factory = new ProxyFactory(orderController);  
        factory.addAdvisors(getAdvisor(logTrace));  
        OrderControllerV1 proxy = (OrderControllerV1)factory.getProxy();  
  
        log.info("ProxyFactory proxy={}, target={}", proxy.getClass(), orderController.getClass());  
        return proxy;  
    }  
    @Bean  
    public OrderServiceV1 orderServiceV1(LogTrace logTrace) {  
        OrderServiceV1 orderService = new OrderServiceV1Impl(orderRepositoryV1(logTrace));  
        ProxyFactory factory = new ProxyFactory(orderService);  
        factory.addAdvisors(getAdvisor(logTrace));  
        OrderServiceV1 proxy = (OrderServiceV1)factory.getProxy();  
  
        log.info("ProxyFactory proxy={}, target={}", proxy.getClass(), orderService.getClass());  
        return proxy;  
    }  
  
    @Bean  
    public OrderRepositoryV1 orderRepositoryV1(LogTrace logTrace) {  
        OrderRepositoryV1Impl orderRepository = new OrderRepositoryV1Impl();  
  
        ProxyFactory factory = new ProxyFactory(orderRepository);  
        factory.addAdvisor(getAdvisor(logTrace));  
        OrderRepositoryV1 proxy = (OrderRepositoryV1) factory.getProxy();  
  
        log.info("ProxyFactory proxy={}, target={}", proxy.getClass(), orderRepository.getClass());  
        return proxy;  
    }  
  
    private Advisor getAdvisor(LogTrace logTrace) {  
        //pointcut  
        NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();  
        pointcut.setMappedNames("request*", "order*", "save*");  
  
        //advice  
        LogTraceAdvice advice = new LogTraceAdvice(logTrace);  
        return new DefaultPointcutAdvisor(pointcut, advice);  
  
    }  
}
  • 포인트컷은 NameMatchMethodPointcut을 사용한다. 여기에는 심플 매칭 기능이 있어서 * 을 매칭할 수 있다.
    • request*, order*, save* : request로 시작하는 메서드에 포인트컷은 true를 반환한다. 나머지도 같다.
    • 이렇게 설정한 이유는 noLog() 메서드에는 어드바이스를 적용하지 않기 위해서다.
  • 어드바이저는 포인트컷(NameMatchMethodPointcut), 어드바이스(LogTraceAdvice)를 가지고 있다.
  • 프록시 팩토리에 각각의 targetadvisor를 등록해서 프록시를 생성한다. 그리고 생성된 프록시를 스프링 빈으로 등록한다.

이제 마지막으로 ProxyFactoryConfigV1를 등록하고 실행하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//@Import(InterfaceProxyConfig.class)  
//@Import(ConcreteProxyConfig.class)  
//@Import(DynamicProxyBasicConfig.class)  
//@Import(DynamicProxyFilterConfig.class)
@Import(ProxyFactoryConfigV1.class)  
@SpringBootApplication(scanBasePackages = "hello.proxy.app.v3") //주의  
public class ProxyApplication {  
  
    public static void main(String[] args) {  
       SpringApplication.run(ProxyApplication.class, args);  
    }  
  
    @Bean  
    public LogTrace logTrace() {  
       return new ThreadLocalLogTrace();  
    }  
}

로그가 잘 남고, no-log는 로그가 남지 않는 걸 확인할 수 있다.

프록시 팩토리 - 적용 2

이번에는 인터페이스가 없고, 구체 클래스만 있는 v2 애플리케이션에 LogTrace기능을 프록시 팩토리를 통해서 프록시를 만들어 적용해보자.

v1에서 v2로만 바꿔주기만 하면 된다.

hello.proxy.config.v3_proxyfactory.ProxyFactoryConfigV2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@Slf4j  
@Configuration  
public class ProxyFactoryConfigV2 {  
  
    @Bean  
    public OrderControllerV2 orderControllerV2(LogTrace logTrace) {  
        OrderControllerV2 orderController = new OrderControllerV2(orderServiceV2(logTrace));  
        ProxyFactory factory = new ProxyFactory(orderController);  
        factory.addAdvisors(getAdvisor(logTrace));  
        OrderControllerV2 proxy = (OrderControllerV2)factory.getProxy();  
  
        log.info("ProxyFactory proxy={}, target={}", proxy.getClass(), orderController.getClass());  
        return proxy;  
    }  
    @Bean  
    public OrderServiceV2 orderServiceV2(LogTrace logTrace) {  
        OrderServiceV2 orderService = new OrderServiceV2(orderRepositoryV2(logTrace));  
        ProxyFactory factory = new ProxyFactory(orderService);  
        factory.addAdvisors(getAdvisor(logTrace));  
        OrderServiceV2 proxy = (OrderServiceV2)factory.getProxy();  
  
        log.info("ProxyFactory proxy={}, target={}", proxy.getClass(), orderService.getClass());  
        return proxy;  
    }  
  
    @Bean  
    public OrderRepositoryV2 orderRepositoryV2(LogTrace logTrace) {  
        OrderRepositoryV2 orderRepository = new OrderRepositoryV2();  
  
        ProxyFactory factory = new ProxyFactory(orderRepository);  
        factory.addAdvisor(getAdvisor(logTrace));  
        OrderRepositoryV2 proxy = (OrderRepositoryV2) factory.getProxy();  
  
        log.info("ProxyFactory proxy={}, target={}", proxy.getClass(), orderRepository.getClass());  
        return proxy;  
    }  
  
    private Advisor getAdvisor(LogTrace logTrace) {  
        //pointcut  
        NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();  
        pointcut.setMappedNames("request*", "order*", "save*");  
  
        //advice  
        LogTraceAdvice advice = new LogTraceAdvice(logTrace);  
        return new DefaultPointcutAdvisor(pointcut, advice);  
  
    }  
}

ProxyFactoryConfigV2도 마찬가지로 import 하고 실행해보자.

보면 CGLIB를 사용해서 프록시를 만들었다.

로그 추적 기능이 잘 작동한다.

정리

프록시 팩토리 덕분에 개발자는 매우 편리하게 프록시를 생성할 수 있게 되었다.

추가로 어드바이저, 어드바이스, 포인트컷 이라는 개념 덕분에 어떤 부가 기능어디에 적용할지 명확하게 이해할 수 있었다.

남은 문제

프록시 팩토리와 어드바이저 같은 개념 덕분에 지금까지 고민했던 문제들을 해결되었다. 프록시도 깔끔하게 적용하고 포인트컷으로 어디에 부가 기능을 적용할지도 명확하게 정의할 수 있다.

원본 코드를 전혀 손대지 않고 프록시를 통해 부가 기능도 적용할 수 있었다.

그런데 아직 해결되지 않는 문제가 있다.

문제 1 - 너무 많은 설정

바로 ProxyFactoryCOnfigV1, ProxyFactoryConfigV2와 같이 설정 파일이 지나치게 많다는 점이다.

예를 들어서 애플리케이션에 스프링 빈이 100개 있다면 여기에 프록시를 통해 부가 기능을 적용하려면 100개의 동적 프록시 생성 코드를 만들어야 한다.

문제 2 - 컴포넌트 스캔

애플리케이션 V3처럼 컴포넌트 스캔을 사용하는 경우는 지금까지 학습한 방법으로는 프록시 설정이 불가능하다.

왜냐하면 실제 객체를 컴포넌트 스캔으로 스프링 컨테이너의 스프링 빈으로 등록을 다 해버린 상태이기 때문이다. 지금까지 학습한 프록시를 적용하려면, 실제 객체를 스프링 컨테이너의 빈으로 등록하는 것이 아니라 ProxyFactoryConfigV1에서 한 것처럼, 부가 기능이 있는 프록시를 실제 객체 대신 스프링 컨테이너에 빈으로 등록해야 한다.

댓글남기기