이 글은 김영한님의 스프링 고급편 강의중 제목과 관련된 부분을 블로그장의 취향대로 요약한 것이며 강의 자료 및 출처는 가장 아래에서 확인할 수 있습니다.
프록시 패턴(Proxy Pattern)
1.정의
위는 클라이언트에서 서버를 직접 호출하는 간단한 예시이다.
클라이언트가 요청한 결과를 서버에 직접 요청하는 것이 아닌 어떤 대리자를 통해 간접적으로 호출하는 예시이다.
예를 들어 내가 직접 마트에서 장을 볼 수도 있지만, 누군가에게 대신 장을 봐달라고 부탁할 수도 있는데 여기서 장을 보는 대리자를 프록시(proxy)라 한다.
2.구조
객체에서 프록시가 되려면, 클라이언트는 서버에서 요청한 것인지, 프록시에게 요청한 것인지 몰라야한다.
즉, 서버와 프록시는 같은 인터페이스를 사용해야하고 클라이언트가 사용하는 서버 객체를 프록시 객체로 변경해도 클라이언트 코드를 변경하지 않고 동작할 수 있어야 한다.
3.기능
프록시가 하는 일은 크게 두가지로 구분할수 있는데 접근 제어와 부가 기능 추가이다.
접근 제어 : 권한에 따른 접근 차단 , 캐싱 , 지연 로딩
부가 기능 추가 : 부가 기능 수행 , 예) 실행 시간을 측정하여 추가 로그를 남김
4.구현
public interface Subject {
String operation();
}
Subject 인터페이스는 단순히 operation()하나의 메서드를 가지고 있다.
public class RealSubject implements Subject{
// 호출할 때 마다 시스템에 큰 부하를 주는 데이터 조회
@Override
public String operation() {
log.info("실제 객체 호출");
sleep(1000); // 1초
return "data";
}
private void sleep(int millis){
try{
Thread.sleep(millis);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
Subject를 구현한 RealSubject는 실제 로직이 담겨있는 코드이며, 호출할 때 마다 시스템에 큰 부하를 주는 데이터를 조회한다고 가정하였다.
public class ProxyPatternClient {
private Subject subject;
public ProxyPatternClient(Subject subject){
this.subject = subject;
}
public void execute(){
subject.operation();
}
}
클라이언트는 Subject 인터페이스를 필드로 가지고있고, 생성시 Subject를 주입받도록 하였다.
execute()는 주입받은 Subject 인터페이스의 구현체에서 operation()을 실행하도록 하였다.
// 캐시 프록시 도입
public class CacheProxy implements Subject{
private Subject target; // 프록시가 호출하는 대상을 target이라 한다.
private String cacheValue;
public CacheProxy(Subject target){
this.target = target;
}
@Override
public String operation() {
log.info("프록시 호출");
if (cacheValue == null){
cacheValue = target.operation();
}
return cacheValue;
}
}
캐시프록시를 도입하였다.
프록시는 이미 언급하였듯 서버와 같은 인터페이스를 사용해야 하며 클라이언트가 사용하는 서버 객체를 프록시 객체로 변경해도 문제가 없어야한다.
따라서 프록시 필드엔 target이라는 실제(또는 추가되는 프록시) 대상 객체가 있으며 operation()내부에 공통 로직을 추가한뒤, 실제 대상 객체의 operation()을 또 한번 실행한다.
한번 대상 객체를 실행했다면 cacheValue에 값이 존재하므로 실제 대상 객체에서 sleep()을 반복하지 않고, cacheValue를 바로 리턴하도록 하였다.
@Test
void cacheProxyTest(){
Subject realSubject = new RealSubject();
Subject cacheProxy = new CacheProxy(realSubject);
ProxyPatternClient client = new ProxyPatternClient(cacheProxy);
client.execute();
client.execute();
client.execute();
}
캐시 프록시를 도입하기 전에는 3초가 걸렸겠지만, 도입 이후에는 최초 한번만 걸리고 이후에 즉시 반환하게 된다.
실제 객체는 한번만 호출되고 이후 프록시를 호출한것을 확인할 수 있다.
데코레이터 패턴(Decorator Pattern)
1.정의
앞서 설명한 프록시의 기능은 크게 접근 제어와 부가 기능 추가 두 가지가 있다고 언급하였다. 이번엔 프록시로 부가 기능을 추가할것인데, 이렇게 프록시로 부가 기능을 추가하는것을 데코레이터 패턴이라고 한다.
2.구조
프록시 패턴과 동일하다.
3.기능
원래 서버가 제공하는 기능에 더해서 부가 기능을 수행한다.
예) 요청 값이나 응답 값을 중간에 변형, 실행 시간을 측정해서 추가 로그를 남김
4.구현
public interface Component {
String operation();
}
Component 인터페이스는 하나의 operation()메서드를 가진다.
public class RealComponent implements Component{
@Override
public String operation() {
log.info("RealComponent 실행");
return "data";
}
}
Realcomponent는 Component 인터페이스를 구현한 실제 객체이며 operation()은 단순히 로그를 남기고 "data"를 반환한다.
public class DecoratorPatternClient {
private Component component;
public DecoratorPatternClient(Component component){
this.component = component;
}
public void execute(){
component.operation();
}
}
클라이언트는 프록시와 동일하게 Component필드를 가지고, 생성자에서 주입받는다.
execute()에서 Component 구현체의 operation()을 실행한다.
// 데코레이터 적용
public class MessageDecorator implements Component{
private Component component;
public MessageDecorator(Component component){
this.component = component;
}
@Override
public String operation() {
log.info("MessageDecorator 실행");
String result = component.operation();
String decoResult = "*****" + result + "*****";
log.info("MessageDecorator 꾸미기 적용 전={}, 적용 후={}", result , decoResult);
return decoResult;
}
}
데코레이터 클래스이다.
Component를 필드로 가지고, 생성시 실제 대상 객체(또는 다른 데코레이터)를 주입받는다.
operation()에서는 주입받은 객체의 operation()을 실행한후, 반환받은 문자열에 단순히 "*****"문자열을 더해준다.
@Test
void decorator1(){
Component realComponent = new RealComponent();
Component messageDecorator = new MessageDecorator(realComponent);
DecoratorPatternClient client = new DecoratorPatternClient(messageDecorator);
client.execute();
}
클라이언트는 데코레이터를 의존하고, 데코레이터는 실제 컴포넌트를 의존하는 모습을 확인할 수 있다.
데코레이터가 적용된 것을 확인할 수 있다.
5.GOF 데코레이터 패턴
잘 생각해보면 데코레이터 클래스들은 모두 대상 객체가 있어야하기 때문에, 호출 대상인 Component를 가지고 있어야 하며, 항상 component를 호출해야 한다.
따라서 이런 중복을 제거하기 위해 component를 속성으로 가진 Decorator 추상 클래스를 만드는 방법을 고민할 수 있는데 이렇게 하면 클래스 다이어그램에서 실제 컴포넌트와 데코레이터를 명확하게 구분할 수 있다.
여기까지가 GOF에서 설명하는 데코레이터 패턴의 기본 예제이다.
프록시 패턴 VS 데코레이터 패턴
두 패턴은 모양이 거의 같고, 상황에 따라 정말 똑같을 때가 있기때문에 의도에 따라 패턴을 구분할뿐이다.
프록시를 사용하는것은 둘다 동일하다.
프록시의 사용 목적이 접근 제어라면 프록시 패턴이고, 새로운 기능을 추가하면 데코레이터 패턴이다.
* 출처 자료
'Spring' 카테고리의 다른 글
Spring Boot 정리 1) 외부 설정과 프로필 (1) | 2024.08.12 |
---|---|
Spring Advanced 정리 4) 스프링의 프록시 등록 방식 (0) | 2024.03.28 |
Spring Advanced 정리 2) 템플릿 메서드 패턴과 템플릿 콜백 패턴 (0) | 2024.03.25 |
Spring Advanced 정리 1) 쓰레드 로컬 (0) | 2024.03.14 |
Spring DB 정리 4) 트랜잭션 전파 (2) | 2024.03.13 |