proxy-start
를 proxy
로 변경
예제는 크게 3가지 상황으로 만든다.
실무에서는 스프링 빈으로 클래스를 등록할 때, 인터페이스가 있는 경우도 있고, 없는 경우도 있다. 또, 빈을 수동으롣 등록하는 경우도 있고, 컴포넌트 스캔으로 자동 등록하는 경우도 있다. 이러한 다양한 경우에 프록시를 어떻게 적용하는지 알아보는 것.
@RequestMapping
: 스프링MVC는 @Controller
또는 @RequestMapping
어노테이션이 타입에 있어야 스프링 컨트롤러로 인식한다. 그리고 스프링 컨트롤러로 인식해야 HTTP URL이 매핑되고 동작한다. 이 어노테이션은 인터페이스에 사용해도 된다.
@ResponseBody
: 인터페이스에 사용해도 된다.
request()
는 LogTrace
를 적용할 대상이고, noLog()
는 적용하지 않을 대상이다.
@Import(AppV1Config.class)
: 클래스를 스프링 빈으로 등록한다.
일반적으로 @Configuration
같은 설정 파일을 등록할 때 사용하지만, 스프링 빈을 등록할 때도 사용할 수 있다.
@SpringBootApplication(scaBasePackages = "hello.proxy.app")
: @ComponentScan
의 기능과 같다. 이렇게 설정하면, hello.proxy.app
패키지를 스캔한다.
이렇게 설정한 이유는 나중에 Config
가 v2, v3
버전들이 나올텐데 컴포넌트 스캔이 v1, v2, v3
를 전부다 스캔하는 것을 방지하기 위해 app
패키지 내부만 스캔하고 config
는 수동으로 등록하는 것이라고 생각하면 될 것 같다.
@Controller
를 굳이 쓰지 않는 이유는 @Component
를 통해 자동으로 등록하지 않도록 하기 위해서이다. 나중에 Config
에서 등록하기 위함.
모두 @Component
를 통해 자동으로 등록되기 때문에 따로 Config
를 만들 필요 없음
이전에 했던 로그 추적기를 보았을때, 문제들이 있었는데
요구사항을 만족하기 위해서 기존 코드를 많이 수정해야 했다. 코드 수정을 최소화 하기 위해 템플릿 메서드 패턴과 콜백 패턴도 사용했지만, 결과적으로 로그를 남기고 싶은 클래스가 수백개라면, 이 클래스를 모두 고쳐야 한다. 로그를 남길 때 기존 원본 코드를 변경해야 한다는 사실 그 자체가 개발자에게는 가장 큰 문제로 남는다.
요구사항 추가
이 요구사항을 만족하기 위해서는 프록시(Proxy
)의 개념을 먼저 이해해야 한다.
프록시에 대해 알아보자
클라이언트와 서버
클라이언트와 서버라 하면 개발자들은 보통 서버 컴퓨터를 생각하지만, 사실 클라이언트와 서버의 개념은 넓은 의미로 사용된다.
클라이언트는 의뢰인,
서버는 '서비스나 상품을 제공하는 사람이나 물건'을 뜻한다.
따라서 클라이언트와 서버의 기본 개념을 정의하면
클라이언트는 서버에 필요한 것을 요청하고, 서버는 클라이언트의 요청을 처리
하는 것이다.
직접 호출과 간점 호출
클라이언트가 서버를 직접 호출하고, 처리 결과를 직접 받는걸 직접 호출.
그런데 어떤 대리자를 통해서 대신 간접적으로 서버에 요청할 수도 있다.
이때 대리자를 영어로 프록시(Proxy
)라고 한다.
예)
대체 가능
객체에서 프록시가 되려면, 클라이언트는 서버에게 요청을 한 것인지, 프록시에게 요청을 한 것인지 조차 몰라야 한다.
쉽게 말해서 서버와 프록시는 같은 인터페이스를 사용해야 한다. 그리고 클라이언트가 사용하는 서버 객체를 프록시 객체로 변경해도 클라이언트 코드를 변경하지 않고 동작할 수 있어야 한다.
서버와 프록시가 같은 인터페이스 사용
런타임(어플리케이션 실행 시점)에 클라이언트 객체에 DI를 사용해서 Client
-> Server
에서 Client
-> Proxy
로 객체 의존관계를 변경해도 클라이언트 코드를 전혀 변경하지 않아도 된다.
DI를 사용하면 클라이언트 코드의 변경 없이 유연하게 프록시를 주입할 수 있다.
프록시의 주요 기능
GOF 디자인 패턴
의도(intent)에 따라서 프록시 패턴과 데코레이터 패턴으로 구분
참고
프록시라는 개념은 클라이언트 서버라는 큰 개념에서 자연스럽게 발생할 수 있다. 프록시는 객체 안에서의 개념도 있고, 웹 서버에서의 프록시도 있다. 객체 안에서 객체로 구현되어 있는가, 웹 서버로 구현되어 있는가처럼 규모의 차이가 있을 뿐, 근본적인 역할은 같다.
둘다 봐보자.
롬복을 사용하기 위해 테스트 의존성을 추가해야함.
코드를 단순하게 만들어보자.
총 3초가 걸린다.
그런데 이 데이터가 한번 조회하면 변하지 않는 데이터라면 어딘가에 보관해두고 이미 조회한 데이터를 사용하는 것이 성능상 좋다. 이런 것을 캐시라고 한다.
프록시 패턴의 주요 기능은 접근제어이고, 캐시도 접근 제어 기능중 하나이다.
프록시 객체를 통해서 캐시를 적용해보자.
해당 객체는 Subject
인터페이스를 참고한다.
private Subject target
: 클라이언트가 프록시를 호출하면 프록시가 최종적으로 실제 객체를 호출해야 한다. 따라서 내부에 실제 객체의 참조를 가지고 있어야 한다. 이렇게 프록시가 호출하는 대상을 target
이라 한다.
operation()
: cacheValue
에 값이 없으면, operation()
을 통해 값을 가져와 cacheValue
에 저장하고 이 값을 반환한다. 다시 접근했을때, cacheValue
가 있으면 바로 반환한다.
처음에는 1초가 걸리고 그 이후부터는 바로 프록시에서 반환되는 것을 확인할 수 있다.
client
-> cacheProxy
-> realSubject
런타임 객체 의존관계가 완성되었다.
정리
프록시 패턴의 핵심은 ProxyPatterClien
가 프록시를 받고 있는지 모른다는 것이다.
이전과 똑같다.
부가 기능을 추가해볼 것이다.
이렇게 부가 기능을 추가하는 것을 데코레이터 패턴이라고 한다.
여기서 부가기능이란.
프록시에서 operation()
에서 값을 가져와 앞 뒤를 *
로 꾸며서 반환한 것을 확인할 수 있다.
이번에는 데코레이터에 데코레이터를 하나 더 넣어보자.
기존 데코레이터에서 실행 시간 측정하는 기능까지 넣어보자.
Client
-> TimeDecorator
-> MessageDecorator
-> RealComponent
-> MessageDecorator
-> TimeDecorator
와 같이 갔다가 돌아온 것을 확인할 수 있다.
꾸며주는 역할을 하는 Decorator
들은 대상이 있어야 한다. 그것이 Component
이다.
의도
프록시 패턴과 데코레이터 패턴은 거의 모양이 같다. 이 둘을 어떻게 구분하는건가?
의도에 따라 패턴을 구분한다.