전 포스트에서는 인터페이스에 의존하는 클라이언트를 대상으로 프록시를 적용하였다. 그런데 아래와 같이 인터페이스가 없는 경우에는 어떻게 프록시를 적용할 수 있을까?
@Slf4j
public class ConcreteLogic {
public String operation(){
log.info("Concrete Logic 실행");
return "data";
}
}
이를 사용하는 Client는 아래와 같다.
public class ConcreteClient {
private ConcreteLogic concreteLogic;
public ConcreteClient(ConcreteLogic concreteLogic) {
this.concreteLogic = concreteLogic;
}
public void execute(){
concreteLogic.operation();
}
}
이와 같은 상황에서 자바의 다형성을 사용하여 프록시를 도입할 수 있다.
자바의 다형성은 인터페이스를 구현하든, 클래스를 상속하든 상위 타입만 맞다면 다형성이 적용된다. 즉 굳이 인터페이스를 만들지 않아도 프록시를 적용할 수 있다는 것이다. 하여 이번에는 인터페이스가 아닌 클래스를 기반으로 상속을 받아 프록시를 만들어 보자.
로직 실행 시간을 측정을 하는 프록시를 만들 것이다. 위에서 상속을 사용하여 프록시를 가능하게 할 것이기 때문에 프록시는 ConcreteLogic을 extends하면 된다.
@Slf4j
public class TimeProxy extends ConcreteLogic{
private ConcreteLogic target;
public TimeProxy(ConcreteLogic target) {
this.target = target;
}
@Override
public String operation() {
log.info("TimeDecorator 실행");
long startTime = System.currentTimeMillis();
String result = target.operation(); // ConcreteLogic의 operation 호출
long endTime = System.currentTimeMillis();
log.info("TimeDecorator 종료, result time = {}", (endTime - startTime));
return result;
}
}
이제 클라이언트는 ConcreteLogic은 물론 TimeProxy도 주입받을 수 있게 되었다. 이를 테스트해보자.
@Test
void yesProxy(){ // Clien --> Proxy --> ConcreteLogic 의 의존관계를 가진다.
ConcreteClient concreteClient = new ConcreteClient(new TimeProxy(new ConcreteLogic()));
concreteClient.execute();
}
실행결과:
21:14:06.221 [Test worker] INFO hello.proxy.pureproxy.concreteproxy.code.TimeProxy - TimeDecorator 실행
21:14:06.222 [Test worker] INFO hello.proxy.pureproxy.concreteproxy.code.ConcreteLogic - Concrete Logic 실행
21:14:06.223 [Test worker] INFO hello.proxy.pureproxy.concreteproxy.code.TimeProxy - TimeDecorator 종료, result time = 1
잘 적용된 것을 알 수 있다.
이제 위에서 본 상속을 이용한 프록시를 V2 App에 적용해보자.
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
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;
}
@Override
public void orderItem(String itemId) {
TraceStatus status = null;
try{
status = logTrace.begin("OrderService.orderItem()");
target.orderItem(itemId);//target 호출
logTrace.end(status);
}catch(Exception e){
logTrace.exception(status, e);
throw e;
}
}
}
RepositoryProxy
public class OrderRepositoryConcreteProxy extends OrderRepositoryV2 {
private final OrderRepositoryV2 target;
private final LogTrace logTrace;
public OrderRepositoryConcreteProxy(OrderRepositoryV2 target, LogTrace logTrace) {
this.target = target;
this.logTrace = logTrace;
}
@Override
public void save(String itemId) {
TraceStatus status = null;
try{
status = logTrace.begin("OrderRepository.request()");
target.save(itemId);//target 호출
logTrace.end(status);
}catch(Exception e){
logTrace.exception(status, e);
throw e;
}
}
}
이제 의존 주입을 해주자
package hello.proxy.config.v1_proxy;
import hello.proxy.app.v2.OrderControllerV2;
import hello.proxy.app.v2.OrderRepositoryV2;
import hello.proxy.app.v2.OrderServiceV2;
import hello.proxy.config.v1_proxy.concrete_proxy.OrderControllerConcreteProxy;
import hello.proxy.config.v1_proxy.concrete_proxy.OrderRepositoryConcreteProxy;
import hello.proxy.config.v1_proxy.concrete_proxy.OrderServiceConcreteProxy;
import hello.proxy.trace.logtrace.LogTrace;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ConcreteProxyConfig {
@Bean
public OrderControllerV2 orderControllerV2(LogTrace logTrace){
OrderControllerV2 orderControllerImpl = new OrderControllerV2(orderServiceV2(logTrace));
return new OrderControllerConcreteProxy(orderControllerImpl, logTrace);
}
@Bean
public OrderServiceV2 orderServiceV2(LogTrace logTrace){
OrderServiceV2 orderServiceImpl = new OrderServiceV2(orderRepositoryV2(logTrace));
return new OrderServiceConcreteProxy(orderServiceImpl, logTrace);
}
@Bean
public OrderRepositoryV2 orderRepositoryV2(LogTrace logTrace){
OrderRepositoryV2 repositoryImpl = new OrderRepositoryV2();
return new OrderRepositoryConcreteProxy(repositoryImpl, logTrace);
}
}
그리고 아래와 같이 메인 클래스에 @Import를 수정해준다.
@Import(ConcreteProxyConfig.class)
//@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 21:40:24.744 INFO 4966 --- [nio-8080-exec-1] h.p.trace.logtrace.ThreadLocalLogTrace : [76541b54] OrderController.request()
2022-01-08 21:40:24.747 INFO 4966 --- [nio-8080-exec-1] h.p.trace.logtrace.ThreadLocalLogTrace : [76541b54] |-->OrderService.orderItem()
2022-01-08 21:40:24.747 INFO 4966 --- [nio-8080-exec-1] h.p.trace.logtrace.ThreadLocalLogTrace : [76541b54] | |-->OrderRepository.request()
2022-01-08 21:40:25.751 INFO 4966 --- [nio-8080-exec-1] h.p.trace.logtrace.ThreadLocalLogTrace : [76541b54] | |<--OrderRepository.request() time=1004ms
2022-01-08 21:40:25.752 INFO 4966 --- [nio-8080-exec-1] h.p.trace.logtrace.ThreadLocalLogTrace : [76541b54] |<--OrderService.orderItem() time=1005ms
2022-01-08 21:40:25.752 INFO 4966 --- [nio-8080-exec-1] h.p.trace.logtrace.ThreadLocalLogTrace : [76541b54] OrderController.request() time=1008ms
지금까지 프록시를 적용하여 기존 코드를 변경하지 않고, 로그 추적기를 도입할 수 있었다. 하지만 각 Controller, Service, Repository 마다 프록시 클래스들이 생성되어 너무 많은 프록시 코드들이 존재하게 되었다. 즉 대상 클래스가 100개라면 100개의 프록시 클래스를 만들어야 한다! 하여 동적 프록시 기술을 사용하여 이와 같은 문제를 해결할 수 있다.
김영한님의 인프런 강의와 PDF를 바탕으로 정리하였습니다.
'Spring > spring AOP' 카테고리의 다른 글
스프링 코어2 - JDK 동적 프록시 (0) | 2022.01.09 |
---|---|
스프링 코어2 - 리플렉션 (0) | 2022.01.09 |
스프링 코어2 - 인터페이스 기반 프록시 (0) | 2022.01.08 |
스프링 코어2 - 프록시 패턴과 데코레이터 패턴 (0) | 2022.01.06 |
스프링 코어2 - 프록시 학습을 위한 프로젝트 만들기 (0) | 2022.01.06 |