2 분 소요


프록시 패턴

The Proxy Pattern provides a surrogate or placeholder for another object to control access to it. In essence, instead of interacting directly with the original object, a client interacts with a proxy, which then determines whether, how, and when to forward these interactions to the underlying object.

-GoF degisn pattern

프록시 패턴은 실제 대상 객체를 대리하여 로직의 흐름을 제어하는 패턴이다. 프록시 객체에서는 실제 객체에 대한 접근 제어를 할 수 있다.

특징

프록시 패턴을 사용하는 가장 큰 이유, 의도(Intent)는 접근 제어이다. 접근 제어 관점에서 프록시를 통해서 얻을 수 있는 이점들에 대해서 알아보자.

보안

프록시는 클라이언트가 작업을 수행할 수 있는 권한이 있는지 확인하여 권한이 있는 경우에만 타켓 객체에 접근하도록 한다.

캐싱

프로시는 내부에 캐시를 유지하여 프록시에 캐시가 없는 경우에만 target에 접근하여 데이터를 가져온다. 캐시가 있다면 프록시에서 클라이언트로 바로 값을 반환하게 된다. 캐싱도 일종의 접근 제어라고 볼 수 있다.

지연 초기화

target 객체의 생성 비용이 큰 경우 실제로 객체가 필요할 때까지 생성을 미루다가 실제로 필요한 시점에 생성하여 반환한다. JPA의 Lazy loading도 이와같은 방식으로 동작한다.

로깅

프록시가 메소드 호출, 로직 수행 시간과 같은 값을 로깅한다. (사실 이것은 관점에 따라서 데코레이터 패턴이라고 볼 수도 있다.)

=> 디자인 패턴은 패턴을 사용하는 의도(Intent)가 가장 중요하다. 프록시 패턴과 데코레이터 패턴은 모양이 굉장히 유사하지만 사용 의도를 통해서 이 둘을 구분할 수 있다. 프록시 패턴(접근 제어), 데코레이터 패턴(추가 기능)

구조

img1

Client

실제로 프록시 객체를 호출하는 주체이다. Client는 Subject 타입의 인터페이스를 의존하기 때문에 로직을 호출할 때 RealSubject가 호출되는지 Proxy가 호출되는지 알지 못한다.

Subject

프록시 객체와 실제 객체를 묶은 상위 인터페이스이다.

여담으로 위와 같이 인터페이스 기반으로 프록시를 구현할 수도 있지만 실제 객체(RealSubject)를 상속하여 프록시를 만들 수도 있다. (스프링의 경우 인터페이스가 없는 객체에 프록시를 적용할 때 CGLIB을 사용하여 실제 객체를 상속하는 프록시를 생성하고 인터페이스가 있는 객체의 프록시를 생성할 때는 JDK 동적 프록시를 사용하여 프록시를 만든다.)

RealSubject

호출되는 실제 객체(target)이다. 프록시가 있다면 프록시를 거쳐서 실제 객체가 호출되게 된다.

Proxy

프록시 객체로 실제 객체와 동일한 인터페이스를 가지거나 실제 객체를 상속한 형태를 가진다.

예시 코드

지금부터 간단한 코드를 통해 프록시가 어떻게 사용될 수 있는지 알아보자.

클라리언트가 객체(RealSubject)로부터 어떤 데이터를 읽는다고 생각해보자. 그리고 읽기에 걸리는 시간은 5초라고 가정하자.

Client

img1

실제 객체로부터 데이터를 읽어오는 클라이언트 객체이다. execute() 메서드를 통해서 데이터를 읽어온다.

Subject

img1

프록시 객체와 실제 객체를 묶어주는 Subject 인터페이스이다. 데이터를 읽어오는 로직인 operation() 메소드가 정의되어 있다.

RealSubject

img1

실제 객체이다. 데이터를 읽는데 5초의 시간이 걸리도록 sleep(5000)을 설정하였다. 만약 프록시 없이 클라이언트가 operation을 5번 호출하면 25초의 시간이 걸릴것이다.

Proxy

img1

프록시 객체이다. 로직을 보면 알 수 있듯이 현재 캐시에 값이 존재하지 않으면 target객체를 호출하여 값을 읽어와서 리턴하고 값이 있다면 바로 값을 리턴하는 것을 볼 수 있다.

테스트 코드

img1

위의 테스트에서는 클라이언트에 실제 객체를 주입하여 execute()를 호출하고 아래 테스트에서는 클라인어트에 프록시 객체를 주입하여 execute()를 호출하였다. 위의 경우는 데이터를 읽을때마다 5초의 시간이 걸려서 총 15초가 걸릴 것이다. 밑의 테스트는 처음 데이터를 읽을때 5초가 걸리고 나머지 2번의 execute() 호출은 프록시에 저장된 캐시값이 있기 때문에 거의 바로 값이 반환될 것이다.

이와 같이 프록시는 접근 제어 목적으로 사용되는 디자인패턴이다.

참고자료

https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%ED%94%84%EB%A1%9D%EC%8B%9CProxy-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B3%A0%EA%B8%89%ED%8E%B8