인터페이스 기반 프록시 vs 클래스 기반 프록시
- 인터페이스가 없어도 클래스 기반으로 프록시를 생성할 수 있다.
- 클래스 기반 프록시는 해당 클래스에만 적용할 수 있다.
- 인터페이스 기반 프록시는 인터페이스만 같으면 모든곳에 적용할 수 있다.
- 클래스 기반 프록시는 상송을 사용하기 때문에 몇가지 제약이 있다.
- 부모 클래스의 생성자를 호출해야한다.(super())
- 클래스에 final 키워드가 붙으면 상속이 불가능하다.
- 메서드에 final 키워드가 붙으면 오버라이딩 할 수 없다.
예제 소스
각 인터페이스 코드는 아래와 같다.
@RequestMapping//스프링은 @Controller 또는 @RequestMapping이 있어야 스프링 컨트롤러로 인식할 수 있다.
@ResponseBody
public interface OrderControllerV1 {
@GetMapping("/v1/request")
String request(@RequestParam("itemId") String itemId);
@GetMapping("/v1/no-log")
String noLog();
}
public interface OrderRepositoryV1 {
void save(String itemId);
}
public interface OrderServiceV1 {
void orderItem(String itemName);
}
그리고 해당 인터페이스를 구현한 코드는 아래와 같다
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";
}
}
public class OrderRepositoryV1Impl implements OrderRepositoryV1 {
@Override
public void save(String itemId) {
//저장 로직
if (itemId.equals("ex")) {
throw new IllegalArgumentException("예외 발생");
}
sleep(1000);
}
private void sleep(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
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);
}
}
이 구조를 보면 현재 의존관계는 다음과 같다고 볼수 있다.
그리고 이 구조를 proxy패턴을 통하여 다음과 같이 변경하고자 한다.
Controller부분에 대한 Proxy코드 부분
- OrderControllerV1 인터페이스를 구현하고, 내부적으로 이미 구현되어있는 OrderControllerV1을 추가하였다.
- 실제 OrderControllerV1Impl을 실행시키되, 앞뒤로 추가 작업에 대한 코드를 작성한다.
- 나머지 interface도 동일하다.
@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()");
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();
}
}
그리고 설정 프록시가 동작할 수 있도록 의존관계 설정을 해줘야 하는데,
아래와 같이 작성한다.
실제 구현한 Impl을 생성하고, 그 생성한 Impl객체를 Proxy객체에 넣어주고, 해당 Proxy객체를 반환하도록 작성한다.
@Configuration
public class InterfaceProxyConfig {
@Bean
public OrderControllerV1 orderController(LogTrace logTrace) {
OrderControllerV1Impl orderControllerV1 = new OrderControllerV1Impl(orderService(logTrace));
return new OrderControllerInterfaceProxy(orderControllerV1, 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);
}
}
클래스 기반의 프록시
-인터페이스 기반의 프록시와 큰 틀에서 비슷하지만 , 클래스 기반의 프록시는 인터페이스가 아니기때문에, 해당 프록시를 상속받아서 만들어야 한다는 차이점이 있다.
- 아래와 같은 형태의 구조를 가진다.
중요한점은 implements가 아닌 extends를 한다는점이 중요!(interface가 아니기 때문)
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()");
String result = target.request(itemId);
logTrace.end(status);
return result;
} catch (Exception e) {
logTrace.exception(status, e);
throw e;
}
}
}
public class OrderRepositoryConcreteProxy extends OrderRepositoryV2 {
private final OrderRepositoryV2 target;
private final LogTrace logTrace;
public OrderRepositoryConcreteProxy(OrderRepositoryV2 orderRepositoryV2, LogTrace logTrace) {
this.target = orderRepositoryV2;
this.logTrace = logTrace;
}
@Override
public void save(String itemId) {
TraceStatus status = null;
try {
status = logTrace.begin("OrderRepository.request()");
target.save(itemId);
logTrace.end(status);
} catch (Exception e) {
logTrace.exception(status, e);
throw e;
}
}
}
public class OrderServiceConcreteProxy extends OrderServiceV2 {
private final OrderServiceV2 target;
private final LogTrace logTrace;
public OrderServiceConcreteProxy(OrderServiceV2 target, LogTrace logTrace) {
super(null);
this.target = target;
this.logTrace = logTrace;
}
}
그리고 아래와 같이 빈등록을 해준다.
인터페이스 프록시와 동일하다. 기존 구현체를 생성하여, 프록시에 DI해준다.
@Configuration
public class ConcreteProxyConfig {
@Bean
public OrderControllerV2 orderControllerV2(LogTrace logTrace) {
OrderControllerV2 orderController = new OrderControllerV2(orderServiceV2(logTrace));
return new OrderControllerConcreteProxy(orderController, 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 orderRepositoryV2 = new OrderRepositoryV2();
return new OrderRepositoryConcreteProxy(orderRepositoryV2, logTrace);
}
}
'SPRING > 스프링' 카테고리의 다른 글
[스프링 핵심 원리 - 고급편] 프록시, 프록시 패턴, 데코레이터 패턴 (0) | 2022.02.16 |
---|---|
[스프링 핵심 원리-고급] 템플릿 콜백 패턴 (0) | 2022.01.27 |
[스프링 핵심 원리 -고급] 전략패턴 (0) | 2022.01.27 |
[스프링 핵심 원리-고급] 템플릿 메서드 패턴 (0) | 2022.01.25 |
[스프링 핵심 원리-고급] 쓰레드 로컬 주의사항 (0) | 2022.01.22 |