본문 바로가기

개인공부

인터페이스 프록시 VS 클래스 기반 프록시 패턴

반응형

인터페이스 기반 프록시 패턴

@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);
}

Configuration 설정 입니다.

Controller, Service , Repository는 모두 프록시를 상위로 두게 됩니다.

OrderControllerV1 라는 Interface를 사용하게 될때 그것의 구현체를 바로 반환하는것이아닌. Proxy를 반환하게 됩니다.

 

나무위키에서 보게된다면 다음과 같은 UML패턴을 그리게 됩니다.

 

Bean이렇게 관여함으로써 RealSubject 여기서는 실제 구현체들 XXXImpl의 소스코드를 수정하지 않고 logTrace를 사용하게 됩니다.

 

하나의 Controlelr에서 프록시를 상속하는 방법을 보겠습니다

 

먼저 Controller를 Interface로 만들게 됩니다.

@RequestMapping//스프링은 @Controller 또는 @RequestMapping 이 있어야 스프링 컨트롤러로 인식
@ResponseBody
public interface OrderControllerV1 {

    @GetMapping("/v1/request")
    String request(@RequestParam("itemId") String itemId);

    @GetMapping("/v1/no-log")
    String noLog();
}

 

이러한 Interface의 구현체입니다.

우리는 여기 구현체에 새로운 logTrace를 추가하고 싶습니다. 그렇다면 Proxy객체를 생성하고 OrderConrollerImpl과 똑같은 부모를 상속받도록 새로운 프록시를 구현해야합니다.

public class OrderControllerV1Impl implements OrderControllerV1 {

    private final OrderServiceV1 orderService;

    public OrderControllerV1Impl(OrderServiceV1 orderService) {
        this.orderService = orderService;
    }

    @Override
    public String request(String itemId) {
        orderService.orderItem(itemId);
        return "ok";
    }

    @Override
    public String noLog() {
        return "ok";
    }
}

 

새로운 구현체를 구현했습니다. 우리가 원하는 logTrace에 대한 구현을 한다음 resulte를 호출합니다. 이렇게 되면 원본은 구현체를 건드리지 않고 트레이스만 깔끔하게 구현할 수 있습니다.

@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();
    }
}

 

클래스 기반 프록시 패턴

클래스 기반 프록시 패턴은 인터페이스 프록시패턴과 다를게 없습니다. 오히려 인터페이스 프록시 패턴의 장점이 클래스 프록시 패턴의 장점보다 많습니다. 하지만 실제 작업에서는 interface만으로 구현되어 있는 구조들만 작업하기에는 어려움이 있습니다.

 

이때는 extends로 하나의 클래스를 상속받은 후 다형성을 이용해서 프록시를 구현하는 것입니다.

 

인터페이스 구현의 Bean과 별 다를거는 없습니다. 다른점은 OrderControllerConcreteProxy를 보면 알 수 있습니다.

    @Bean
    public OrderControllerV2 orderControllerV2(LogTrace logTrace) {
        OrderControllerV2 controllerImpl = new OrderControllerV2(orderServiceV2(logTrace));
        return new OrderControllerConcreteProxy(controllerImpl, logTrace);
    }

    @Bean
    public OrderServiceV2 orderServiceV2(LogTrace logTrace) {
        OrderServiceV2 serviceImpl = new OrderServiceV2(orderRepositoryV2(logTrace));
        return new OrderServiceConcreteProxy(serviceImpl, logTrace);
    }

    @Bean
    public OrderRepositoryV2 orderRepositoryV2(LogTrace logTrace) {
        OrderRepositoryV2 repositoryImpl = new OrderRepositoryV2();
        return new OrderRepositoryConcreteProxy(repositoryImpl, logTrace);
    }

OrderConrollerConcreteProxy를 자세히보면은 Interface가 아니라 logTrace를 적용하고 싶은 실제객체의 자식 객체로 생성합니다.

자식 객체로 생성했기에 생성자에 super클래스가 필요하게 되는데 해당 부모객체의 속성을 사용하지 않기 때문에 super(null)을 주입합니다. 이것을 설정하지않으면 super는 자바객체에서 자동으로 생성하는 값이 있기때문에 오류를 유발할 수 있습니다.

 

나머지 역시 Intreface와 같습니다. 하지만 이 프록시패턴은 오직 OrderConrollerV2에만 사용할 수 있도록 국한됩니다. 

Inteface로 사용한다면 Super에 대한 문제 유연성등이 더욱 증가하게 됩니다.

 

하지만 실제 실무에서는 클래스에 대해 하나의 프록시를 구현해야 하는 경우도 있기때문에 알아두면 좋을것 같습니다.

public class OrderControllerConcreteProxy extends OrderControllerV2 {

    private final OrderControllerV2 target;
    private final LogTrace logTrace;

    public OrderControllerConcreteProxy(OrderControllerV2 target, LogTrace logTrace) {
        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;
        }
    }

    @Override
    public String noLog() {
        return target.noLog();
    }
}
반응형

'개인공부' 카테고리의 다른 글

DDD 공부 : 마이크로서비스  (0) 2023.06.28
Reflection 과 Jdk동적 프록시  (0) 2022.05.16
프록시 패턴 / 데코레이터 패턴  (0) 2022.03.28
템플릿 콜백 패턴  (0) 2022.03.28
전략 패턴  (0) 2022.03.28