인터페이스와 그 구현제가 있는 Controller, Service, Repository에 프록시를 도입하여 로그 추적기 기능을 추가해보자.
프록시를 사용하면 기존의 코드는 전혀 수정하지 않고 로그 추적기 기능을 사용할 수 있다.
현재 V1 App의 클래스 의존관계는 다음과 같다. (각 계층이 다음 계층의 인터페이스에 의존한다.)
런타임 시에는 다음과 같은 의존관계를 가지게 된다.
프록시를 사용하여 로그 추적 기능을 적용하기 위해서는 아래와 같이 해야한다.
위의 관계는 런타임 시에는 아래와 같이 될 것이다.
또한 어플리케이션 실행시점에 프록시를 사용하도록 의존 관계를 설정해주어야 한다.
이제 프록시 코드를 만들어보자.
각각의 Controller, Service, Repository의 프록시들을 만들어야 한다.
ControllerProxy
@RequiredArgsConstructor
public class OrderControllerInterfaceProxy implements OrderControllerV1 {
private final OrderControllerV1 target;
private final LogTrace logTrace;
@Override
public String request(String itemId) {
TraceStatus status = null;
try{
status = logTrace.begin("OrderController.request()");
//target 호출
String result = target.request(itemId);
logTrace.end(status);
return result;
}catch(Exception e){
logTrace.exception(status, e);
throw e;
}
}
@Override
public String noLog() {
return target.noLog();
}
}
ServiceProxy
@RequiredArgsConstructor
public class OrderServiceInterfaceProxy implements OrderServiceV1 {
private final OrderServiceV1 target;
private final LogTrace logTrace;
@Override
public void orderItem(String itemId) {
TraceStatus status = null;
try{
status = logTrace.begin("OrderService.orderItem()");
//target 호출
target.orderItem(itemId);
logTrace.end(status);
}catch(Exception e){
logTrace.exception(status, e);
throw e;
}
}
}
RepositoryProxy
@RequiredArgsConstructor
public class OrderRepositoryInterfaceProxy implements OrderRepositoryV1 {
private final OrderRepositoryV1 target;
private final LogTrace logTrace;
@Override
public void save(String itemId) {
TraceStatus status = null;
try{
status = logTrace.begin("OrderRepository.request()");
//target 호출
target.save(itemId);
logTrace.end(status);
}catch(Exception e){
logTrace.exception(status, e);
throw e;
}
}
}
프록시 코드들을 완성했다. 이제 Controller, Service, Repository들의 의존관계들을 수정해주어야 한다. 프록시를 도입하기 이전의 연관관계들은 아래와 같다.
@Configuration
public class AppV1Config {
@Bean
public OrderControllerV1 orderControllerV1(){
return new OrderControllerV1Impl(orderServiceV1());
}
@Bean
public OrderServiceV1 orderServiceV1() {
return new OrderServiceV1Impl(orderRepositoryV1());
}
@Bean
public OrderRepositoryV1 orderRepositoryV1() {
return new OrderRepositoryV1Impl();
}
}
하지만 이제 아래와 같이 바꿔 줄 것이다.
@Configuration
public class InterfaceProxyConfig {
@Bean
public OrderControllerV1 orderController(LogTrace logTrace){
OrderControllerV1Impl controllerV1Impl = new OrderControllerV1Impl(orderService(logTrace));
return new OrderControllerInterfaceProxy(controllerV1Impl, logTrace);
}
@Bean
public OrderServiceV1 orderService(LogTrace logTrace) {
OrderServiceV1Impl serviceV1Impl = new OrderServiceV1Impl(orderRepository(logTrace));
return new OrderServiceInterfaceProxy(serviceV1Impl, logTrace);
}
@Bean
public OrderRepositoryV1 orderRepository(LogTrace logTrace) {
OrderRepositoryV1Impl repositoryV1Impl = new OrderRepositoryV1Impl();
return new OrderRepositoryInterfaceProxy(repositoryV1Impl, logTrace);
}
}
• 스프링 컨테이너에 프록시 객체가 등록된다. 스프링 컨테이너는 이제 실제 객체가 아니라 프록시 객체를 스프링 빈으로 관리한다.
• 이제 실제 객체는 스프링 컨테이너와는 상관이 없다. 실제 객체는 프록시 객체를 통해서 참조될 뿐이다.
• 프록시 객체는 스프링 컨테이너가 관리하고 자바 힙 메모리에도 올라간다. 반면에 실제 객체는 자바 힙 메모리에는 올라가지만 스프링 컨테이너가 관리하지는 않는다.
이제 아래와 같이 @Import를 수정하고, LogTrace를 빈 객체로 등록해준뒤, 어플레케이션을 실행해보자.
@Import(InterfaceProxyConfig.class)
//@Import({AppV1Config.class, AppV2Config.class})
//@Import(AppV1Config.class)
@SpringBootApplication(scanBasePackages = "hello.proxy.app") //주의
public class ProxyApplication {
public static void main(String[] args) {
SpringApplication.run(ProxyApplication.class, args);
}
@Bean
public LogTrace logTrace(){
return new ThreadLocalLogTrace();
}
}
아래와 같은 요청을 날려보면
로그가 잘 출력되는 것을 볼 수 있다.
2022-01-08 16:28:41.994 INFO 3717 --- [nio-8080-exec-7] h.p.trace.logtrace.ThreadLocalLogTrace : [dbdf6463] OrderController.request()
2022-01-08 16:28:41.994 INFO 3717 --- [nio-8080-exec-7] h.p.trace.logtrace.ThreadLocalLogTrace : [dbdf6463] |-->OrderService.orderItem()
2022-01-08 16:28:41.994 INFO 3717 --- [nio-8080-exec-7] h.p.trace.logtrace.ThreadLocalLogTrace : [dbdf6463] | |-->OrderRepository.request()
2022-01-08 16:28:42.997 INFO 3717 --- [nio-8080-exec-7] h.p.trace.logtrace.ThreadLocalLogTrace : [dbdf6463] | |<--OrderRepository.request() time=1003ms
2022-01-08 16:28:42.998 INFO 3717 --- [nio-8080-exec-7] h.p.trace.logtrace.ThreadLocalLogTrace : [dbdf6463] |<--OrderService.orderItem() time=1004ms
2022-01-08 16:28:42.998 INFO 3717 --- [nio-8080-exec-7] h.p.trace.logtrace.ThreadLocalLogTrace : [dbdf6463] OrderController.request() time=1004ms
김영한님의 인프런 강의와 PDF를 바탕으로 정리하였습니다.
'Spring > spring AOP' 카테고리의 다른 글
스프링 코어2 - 리플렉션 (0) | 2022.01.09 |
---|---|
스프링 코어2 - 구체 클래스 기반 프록시 (0) | 2022.01.08 |
스프링 코어2 - 프록시 패턴과 데코레이터 패턴 (0) | 2022.01.06 |
스프링 코어2 - 프록시 학습을 위한 프로젝트 만들기 (0) | 2022.01.06 |
스프링 코어2 - 템플릿 콜백 패턴 (0) | 2022.01.05 |