프록시 패턴(Proxy Pattern)
프록시 패턴(Proxy Pattern)은 대상 원본 객체를 대리하여 대신 처리하게 함으로써 로직의 흐름을 제어하는 행동 패턴이다.
프록시(Proxy)의 사전적인 의미는 '대리인'이라는 뜻이다.
즉, 클라이언트가 대상 객체(Subject)를 직접 사용하는 것이 아니라 대상 객체에 접근하기 전에 프록시 (Proxy) 객체의 메서드를 접근한 후 추가적인 로직을 처리한 뒤 접근하게 된다.
중계 대리자를 사용하는 이유는 여러가지가 있을 수 있는데, 대리자를 사용함으로써 대상 클래스가 부가 기능에 대한 책임을 분리할 수 있고(SRP) 부가 기능의 대한 로직이 바뀌었을 때 원본 객체를 수정할 수 없는 상황일 때를 극복(OCP) 하기 위해서다.
대체적으로 다음과 같은 효과를 누릴 수 있다.
- 보안(Security) : 프록시는 클라이언트가 작업을 수행할 수 있는 권한이 있는지 확인하고 검사 결과가 긍정적인 경우에만 요청을 대상으로 전달한다.
- 캐싱(Caching) : 프록시가 내부 캐시를 유지하여 데이터가 캐시에 아직 존재하지 않는 경우에만 대상에서 작업이 실행되도록 한다.
- 데이터 유효성 검사(Data validation) : 프록시가 입력을 대상으로 전달하기 전에 유효성을 검사한다.
- 지연 초기화(Lazy Initialization) : 대상의 생성 비용이 비싸다면 프록시는 그것을 필요로 할 때까지 연기할 수 있다.
- 로깅(Ligging) : 프록시는 메소드 호출과 상대 매개 변수를 인터셉트하고 이를 기록한다.
- 원격 객체(Remote objects) : 프록시는 원격 위치에 있는 객체를 가져와서 로컬처럼 보이게 할 수 있다.
프록시 패턴 구조
프록시는 다른 객체에 대한 접근을 제어하는 개체이다.
여기서 다른 객체를 대상(Subject)라고 부른다. 프록시와 대상은 동일한 인터페이스를 가지고 있으며 이를 통해 다른 인터페이스와 완전히 호환되도록 바꿀 수 있다.
- Subject : Proxy와 RealSubject를 하나로 묶는 인터페이스 (다형성)
- 대상 객체와 프록시 역할을 동일하게 하는 추상 메소드 operation()를 정의한다.
- 인터페이스가 있기 때문에 클라이언트는 Proxy 역할과 RealSubject 역할의 차이를 의식할 필요가 없다.
- RealSubject : 원본 대상 객체
- Proxy : 대상 객체(RealSubject)를 중계할 대리자 역할
- 프록시는 대상 객체를 합성(composition)한다.
- 프록시는 대상 객체와 같은 이름의 메소드를 호출하며, 별도의 로직을 수행할 수 있다.
- 프록시는 흐름제어만 할 뿐 결과값을 조작하거나 변경시키면 안 된다.
- Client : Subject 인터페이스를 이용하여 프록시 객체를 생성해 이용
- 클라이언트는 프록시를 중간에 두고 프록시를 통해서 RealSubject와 데이터를 주고 받는다.
프록시 패턴 종류
프록시 패턴은 어떤 목적으로 사용하냐에 따라 프록시의 종류나 나뉘게 된다.
- 기본형 프록시(Normal Proxy)
- 가상 프록시 (Virtual Porxy)
- 보호 프록시 (Protection Proxy)
- 로깅 프록시 (Logging Proxy)
- 원격 프록시 (Remote Proxy)
- 캐싱 프록시 (Caching Proxy)
기본형 프록시 (Normal Proxy)
Subject.java
public interface Subject {
void operation();
}
RealSubject.java
public class RealSubject implements Subject{
@Override
public void operation() {
System.out.println("대상 객체 호출");
}
}
Proxy.java
public class Proxy implements Subject{
private Subject realSubject;
public Proxy(Subject realSubject) {
this.realSubject = realSubject;
}
@Override
public void operation() {
System.out.println("프록시 객체 호출");
realSubject.operation();
}
}
Client.java
public class Client {
public static void main(String[] args) {
Subject realSubject = new RealSubject();
Subject proxy = new Proxy(realSubject);
proxy.operation();
}
}
프록시 객체 호출
대상 객체 호출
가상 프록시 (Virtual Proxy)
- 지연 초기화 방식
- 가끔 필요하지만 항상 메모리에 적재되어 이쓴 무거운 서비스 객체가 있는 경우
- 실제 객체의 생성에 많은 자원이 소모 되지만 사용 빈도는 낮을 때 쓰는 방식이다.
- 서비스가 시작될 때 객체를 생성하는 대신에 객체 초기화가 실제로 필요한 시점에 초기화할 수 있도록 지연할 수 있다.
Proxy.java
public class Proxy implements Subject {
private Subject realSubject;
public Proxy () {
}
@Override
public void operation() {
System.out.println("프록시 객체 호출");
if(realSubject == null) {
System.out.println("객체 생성");
realSubject = new RealSubject();
}
realSubject.operation();
}
}
Client.java
public class Client {
public static void main(String[] args) {
Subject proxy = new Proxy();
proxy.operation();
}
}
프록시 객체 호출
객체 생성
대상 객체 호출
보호 프록시 (Protection Proxy)
- 프록시가 대상 객체에 대한 자원으로의 액세스 제어(접근 권한)
- 특정 클라이언트만 서비스 객체를 사용할 수 있도록 하는 경우
- 프록시 객체를 통해 클라이언트의 자격 증명이 기준과 일치하는 경우에만 서비스 객체에 요청을 전달할 수 있게 한다.
Proxy.java
public class Proxy implements Subject {
private Subject realSubject;
boolean access; // 접근 권한
public Proxy(Subject realSubject, boolean access) {
this.realSubject = realSubject;
this.access = access;
}
@Override
public void operation() {
System.out.println("프록시 객체 호출");
System.out.println("접근 권한 확인");
if(access) {
System.out.println("접근 허용");
realSubject.operation();
}
}
}
Client.java
public class Client {
public static void main(String[] args) {
Subject proxy = new Proxy(new RealSubject(), true);
proxy.operation();
}
}
프록시 객체 호출
접근 권한 확인
접근 허용
대상 객체 호출
로깅 프록시 (Logging Proxy)
- 대상 객체에 대한 로깅을 추가하려는 경우
- 프록시는 서비스 메서드를 실행하고 전달하기 전에 로깅을 하는 기능을 추가하여 재정의한다.
Proxy.java
public class Proxy implements Subject {
private Subject realSubject;
public Proxy(Subject realSubject) {
this.realSubject = realSubject;
}
@Override
public void operation() {
System.out.println("프록시 객체 호출");
System.out.println("----메소드 호출 전----");
realSubject.operation();
System.out.println("---메소드 호출 후----");
}
}
Client.java
public class Client {
public static void main(String[] args) {
Subject proxy = new Proxy(new RealSubject());
proxy.operation();
}
}
프록시 객체 호출
----메소드 호출 전----
대상 객체 호출
---메소드 호출 후----
원격 프록시 (Remote Proxy)
- 프록시 클래스는 로컬에 있고, 대상 객체는 원격 서버에 존재하는 경우
- 프록시 객체는 네트워크를 통해 클라이언트의 요청을 전달하여 네트워크와 관련된 불필요한 작업들을 처리하고 결괏값만 반환
- 클라이언트 입장에선 프록시를 통해 객체를 이용하는 것이니 원격이든 로컬이든 신경 쓸 필요가 없으며, 프록시는 진짜 객체와 통신을 대리하게 된다.
캐싱 프록시 (Caching Proxy)
- 데이터가 큰 경우 캐싱하여 재사용을 유도
- 클라이언트 요청의 결과를 캐시하고 이 캐시의 수명 주기를 관리
예시)
HTTP Proxy
HTTP Proxy는 웹서버와 브라우저 사이에서 웹 페이지의 캐싱을 실행하는 소프트웨어이다.
웹 브라우저가 어떤 웹페이즈를 표시할 때 직접 웹 서버에서 그 페이지를 가져오는 것이 아니고, HTTP Proxy가 캐시 해서 어떤 페이지를 대신해서 취득한다.
만일 최신 정보가 필요하거나 페이지의 유효기간이 지났을 때 웹 서버에 웹 페이지를 가지러 간다.
이를 패턴으로 따져보면, 웹 브라우저가 Client 역할, HTTP Proxy가 Proxy 역할, 그리고 웹 서버가 Real Subject 역할을 한다고 보면 된다.
프록시 패턴 특징
패턴 사용 시기
- 접근을 제어하기 기능을 추가하고 싶은데, 기존의 특정 객체를 수정할 수 없는 상황일 때
- 초기화 지연, 접근 제어, 로깅, 캐싱 등, 기존 객체 동작에 수정 없이 가미하고 싶을 때
패턴 장점
- 개방 폐쇄 원칙(OCP) 준수
- 대상 객체의 코드를 변경하지 않고 부가 기능을 추가할 수 있다.
- 단일 책임 원칙(SRP) 준수
- 대상 객체는 본인의 비즈니스 로직에만 집중을 하고, 이외 부가 기능을 제공하는 역할을 프록시 객체에게 위임할 수 있다.
- 원래 하려던 기능을 수행하며 그 외의 부가적인 작업(로깅, 인증, 네트워크 통신 등)을 수행하는데 유용하다.
- 클라이언트는 객체를 신경 쓰지 않고, 서비스 객체를 제어하거나 생명 주기를 관리할 수 있다.
- 사용자 입장에서는 프록시 객체나 실제 객체나 사용법은 유사하므로 사용성에 문제 되지 않는다.
패턴 단점
- 많은 프록시 클래스를 도입해야 하므로 코드의 복잡도가 증가한다.
- 예를 들어 여러 클래스에 로깅 기능을 가미시키고 싶다면, 동일한 코드를 적용함에도 각각의 클래스에 해당되는 프록시 클래스를 만들어서 적용해야 되기 때문에 코드량이 많아지고 중복이 발생된다.
- 자바에서는 리플렉션에서 제공하는 동적 프록시(Dynamic Proxy) 기법을 이용해서 해결할 수 있다.
- 프록시 클래스 자체에 들어가는 자원이 많다면 서비스로부터의 응답이 늦어질 수 있다.
참고 자료 :
'백엔드 > Java' 카테고리의 다른 글
[Java] 병렬 스트림 (parallel stream) (0) | 2023.06.22 |
---|---|
[Java] 리플렉션(Reflection) (0) | 2023.05.25 |
[Java] JVM의 Class Loader (0) | 2023.05.23 |
[Java] 상속(Inheritance)과 합성(Composition) (1) | 2023.05.19 |
[Java] 옵저버 패턴(Observer Pattern) (1) | 2023.05.17 |