스프링 핵심 원리 - 고급편 - (22) 스프링 프록시 - 프록시 팩토리 적용
인프런 스프링 핵심 원리 - 고급편을 학습하고 정리한 내용 입니다.
프록시 팩토리 - 적용 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)를 가지고 있다. - 프록시 팩토리에 각각의
target과advisor를 등록해서 프록시를 생성한다. 그리고 생성된 프록시를 스프링 빈으로 등록한다.
이제 마지막으로 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에서 한 것처럼, 부가 기능이 있는 프록시를 실제 객체 대신 스프링 컨테이너에 빈으로 등록해야 한다.
댓글남기기