1 Proxy Pattern
- 클라이언트는 의뢰인이라는 뜻이고, 서버는 '서비스나 상품을 제공하는 사람이나 물건'을 뜻하는 서버-클라이언트 구조를 생각해보자
- 이 개념을 네트워크에 도입하면 클라이언트는 웹 브라우저가 되고, 요청을 처리하는 서버는 웹 서버가 된다.
- 이 개념을 객체에 도입하면, 요청하는 객체는 클라이언트가 되고, 요청을 처리하는 객체는 서버가 된다.
직접 호출
- 일반적으로 클라이언트가 서버를 직접 호출하고, 처리 결과를 직접 받는다.
- 이것을 직접 호출이라 한다.
프록시를 통한 간접 호출
- 클라이언트가 요청한 결과를 서버에 직접 요청하는 것이 아니라 어떤 대리자를 통해서 대신 간접적으로 서버에 요청할 수 있다
- 여기서 대리자를 프록시라 한다
2 Proxy의 역할
- 클라이언트는 서버에게 요청을 한 것인지, 프록시에게 요청을 한 것인지 조차 몰라야 한다.
- 따라서 서버와 프록시는 같은 인터페이스를 사용해야 한다.
- 같은 인터페이스를 사용하기 때문에 클라이언트가 사용하는 서버 객체를 프록시 객체로 변경해도 클라이언트 코드를 변경하지 않고 동작할 수 있다
- 프록시는 클라이언트와 서버 중간에 위치하며 아래와 같은 기능을 한다.
클래스 의존 관계

- 클라이언트는 ServerInterface에 의존하고 있으며 Server와 Proxy는 같은 인터페이스를 구현하고 있다
런타임 객체 의존 관계


- 런타임 객체 의존 관계가 클라이언트 -> Server인 상태에서 DI를 사용해서 Server 대신 Proxy를 주입해도 클라이언트의 코드를 변경하지 않아도 된다
- 같은 인터페이스를 사용하기 때문에 Server를 Proxy로 교체해도 예전처 럼 그대로 사용가능
2.1 Proxy의 주요 기능
- 프록시에의 주요 기능에는 크게 2가지로 구분됩니다.
- 접근 제어
- 권한에 따른 접근 차단
- 캐싱
- 지연 로딩
- 부가 기능 추가
- 서버가 제공하는 기능에 더해 부가 기능을 수행합니다.
- 로그 서비스
- 요청이나 응답 값 중간 수정
- 부가 기능 추가의 목적을 가진 프록시 패턴을 데코레이터 패턴이라 부릅니다.
2.2 GOF의 Proxy 정의
- 프록시 패턴과 데코레이터 패턴은 둘다 프록시를 사용하지만 의도에 따라서 구분합니다.
- 프록시 패턴: 접근 제어가 목적
- 데코레이터 패턴: 새로운 기능 추가가 목적
- Decorator.md 참고
정보
프록시라는 개념은 클라이언트 서버라는 큰 개념 안에서 자연스럽게 발생할 수 있습니다. 프록시는 객체 안에서의 개념도 있고, 웹 서버에서의 프록시도 있습니다. 객체 안에서 객체로 구현되어 있는가, 웹 서버로 구현되어 있는가처럼 규모의 차이가 있을 뿐 근본적인 역할은 같습니다.
3 Proxy Pattern 예제 코드
- 서버의 응답을 캐시에 클라이언트의 접근을 제어하는 프록시를 만들어보겠습니다.
3.1 Client 클래스
- 클라이언트 클래스로 Subject 인터페이스를 의존하고 있습니다.
public class Client {
private Subject subject;
public Client(Subject subject) {
this.subject = subject;
}
public void execute() {
subject.operation();
}
}
3.2 Subject 인터페이스
- 클라이언트가 의존하는 인터페이스
- Subject 인터페이스는 단순히 operation() 메서드 하나만 가지고 있습니다.
public interface Subject {
String operation();
}
3.3 RealSubject 클래스
- RealSubject는 Server 인터페이스를 구현한 구현체입니다.
- operation() 메서드는 호출할 때 마다 시스템에 큰 부하를 주는 데이터 조회를 시뮬레이션한 것으로 1초가 소요됩니다.
@Slf4j
public class RealSubject implements Subject {
@Override
public String operation() {
log.info("RealSubject 호출");
sleep(1000);
return "data";
}
private void sleep(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}