FireDrago
프록시 적용하기 (로그 추적기) 본문
프록시를 적용하여 기존 코드를 수정하지 않고, 로그 추적기 기능을 도입해서 사용해보자
원래 코드에 인터페이스가 있는 상황과, 구체 클래스만 있는 상황으로 나누어 2가지 상황 모두 프록시를 사용해보자
1. 인터페이스 기반 프록시 사용
@RequestMapping
@ResponseBody
public interface OrderControllerV1 {
@GetMapping("/v1/request")
String request(@RequestParam("itemId") String itemId);
@GetMapping("/v1/no-log")
String noLog();
}
public class OrderControllerV1Impl implements OrderControllerV1{
private final OrderServiceV1 orderService;
public OrderControllerV1Impl(OrderServiceV1 orderServiceV1) {
this.orderService = orderServiceV1;
}
@Override
public String request(String itemId) {
orderService.orderItem(itemId);
return "ok";
}
@Override
public String noLog() {
return "ok";
}
}
public interface OrderRepositoryV1 {
void save(String itemId);
}
public class OrderRepositoryV1Impl implements OrderRepositoryV1{
@Override
public void save(String itemId) {
// 저장 로직
if (itemId.equals("ex")) {
throw new IllegalStateException("예외 발생!");
}
sleep(1000);
}
private void sleep(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public interface OrderServiceV1 {
void orderItem(String itemId);
}
public class OrderServiceV1Impl implements OrderServiceV1{
private final OrderRepositoryV1 orderRepository;
public OrderServiceV1Impl(OrderRepositoryV1 orderRepository) {
this.orderRepository = orderRepository;
}
@Override
public void orderItem(String itemId) {
orderRepository.save(itemId);
}
}
원본 코드를 작성한다. 모두 인터페이스 기반으로 작성하였다.
이제 각 구현체 마다 사용할 프록시 객체를 만들어 보자
@RequiredArgsConstructor
// 실제 객체와 동일한 OrderControllerV1 인터페이스를 구현한다.
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() {
// 프록시 사용하지 않는 메서드의 경우 target 의 noLog() 를 바로 호출한다.
return target.noLog();
}
}
@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;
}
}
}
@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.save()");
// target 호출
target.save(itemId);
logTrace.end(status);
} catch (Exception e) {
logTrace.exception(status,e);
throw e;
}
}
}
프록시 객체는 내부에 실제 객체를 파라미터로 가진다. 로그 추적 기능을 실행한뒤에,
실제 객체의 로직을 호출한다.
@Configuration
public class InterfaceProxyConfig {
@Bean
public OrderControllerV1 orderController(LogTrace logTrace) {
OrderControllerV1Impl controllerImpl = new OrderControllerV1Impl(orderService(logTrace));
return new OrderControllerInterfaceProxy(controllerImpl, logTrace);
}
@Bean
public OrderServiceV1 orderService(LogTrace logTrace) {
OrderServiceV1Impl serviceImpl = new OrderServiceV1Impl(orderRepository(logTrace));
return new OrderServiceInterfaceProxy(serviceImpl, logTrace);
}
@Bean
public OrderRepositoryV1 orderRepository(LogTrace logTrace) {
OrderRepositoryV1Impl repositoryImpl = new OrderRepositoryV1Impl();
return new OrderRepositoryInterfaceProxy(repositoryImpl, logTrace);
}
}
설정 클래스를 통해, 실제 객체 대신에 프록시 객체를 스프링 빈으로 등록해준다.
프록시 객체는 내부에 실제 객체를 가지고 있다.
이제 사용자가 호출할 경우, 프록시 객체가 로그 추적기능을 실행한뒤, 실제 객체를 내부 호출한다.

2. 구체 클래스 기반 프록시 적용
공통 인터페이스가 없는 구체 클래스에 프록시를 적용할때도 인터페이스가 있을때와 큰 차이는 없다.
다만 상속을 사용하기때문에 몇가지 제약이 추가되는데 컨트롤러 실제객체와 프록시 객체만 살펴보자
@Slf4j
@RequestMapping
@ResponseBody
public class OrderControllerV2 {
private final OrderServiceV2 orderService;
public OrderControllerV2(OrderServiceV2 orderService) {
this.orderService = orderService;
}
@GetMapping("/v2/request")
public String request(String itemId) {
orderService.orderItem(itemId);
return "ok";
}
@GetMapping("/v2/no-log")
public String noLog() {
return "ok";
}
}
인터페이스가 없으므로 실제 객체는 implement 하지 않는다.
public class OrderControllerConcreteProxy extends OrderControllerV2 {
private final OrderControllerV2 target;
private final LogTrace logTrace;
public OrderControllerConcreteProxy(OrderControllerV2 target, LogTrace logTrace) {
// 부모 생성자를 반드시 호출해야 함, 부모기능 사용하지 않으므로 null 넘긴다.
super(null);
this.target = target;
this.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;
}
}
}
반면에 프록시 객체는 공통 인터페이스가 없으므로, 실제 객체인 OrderControllerV2를 상속받는다.
이때문에 구체 클래스 프록시는 부모생성자 호출, final 사용시 에러 발생 등 몇가지 제약 조건이 있다.
super(null) : OrderControllerV2 : 자바 기본 문법에 의해 자식 클래스를 생성할 때는 항상 super() 로 부
모 클래스의 생성자를 호출해야 한다. 이 부분을 생략하면 기본 생성자가 호출된다. 그런데 부모 클래스인 OrderControllerV2 는 기본 생성자가 없고, 생성자에서 파라미터 1개를 필수로 받는다. 따라서 파라미터를 넣어
서 super(..) 를 호출해야 한다.
인터페이스 기반 프록시와 클래스 기반 프록시
- 클래스 기반 프록시는 해당 클래스에만 적용할 수 있다.
인터페이스 기반 프록시는 인터페이스만 같으면 모든 곳에 적용할 수 있다.
- 클래스 기반 프록시는 상속을 사용하기 때문에 몇가지 제약이 있다.
- 부모 클래스의 생성자를 호출해야 한다.(앞서 본 예제)
- 클래스에 final 키워드가 붙으면 상속이 불가능하다.
- 메서드에 final 키워드가 붙으면 해당 메서드를 오버라이딩 할 수 없다.
이론적으로는 모든 객체에 인터페이스를 도입해서 역할과 구현을 나누는 것이 좋다.
이렇게 하면 역할과 구현을 나누어서 구현체를 매우 편리하게 변경할 수 있다.
하지만 실제로는 구현을 거의 변경할 일이 없는 클래스도 많다.
인터페이스를 도입하는 것은 구현을 변경할 가능성이 있을 때 효과적인데,
구현을 변경할 가능성이 거의 없는 코드에 무작정 인터페이스를 사용하는 것은 번거롭고 그렇게 실용적이지 않다.
이런곳에는 실용적인 관점에서 인터페이스를 사용하지 않고 구체 클래스를 바로 사용하는 것이 좋다
'프로그래밍 > 디자인패턴' 카테고리의 다른 글
| [객체지향과 디자인 패턴] 2장 객체지향 (0) | 2024.08.27 |
|---|---|
| 객체를 생성하는 방법 (생성자, 정적메서드, 빌더패턴) (0) | 2024.06.05 |
| 프록시 패턴과 데코레이터 패턴 (0) | 2024.05.13 |
| 전략패턴과 콜백패턴의 적용 (0) | 2024.05.10 |
| 템플릿 메서드 패턴과 적용 (0) | 2024.05.10 |
