Post

[디자인 패턴] 프록시와 프록시 패턴

Spring의 AOP(관점 중심 프로그래밍)에서 프록시와 프록시 패턴이 사용되는데, 프록시와 프록시 패턴이라는 디자인 패턴에 대해 공부하기 위해 기록한다.

프록시란?

프록시(Proxy)를 번역하면, 대체자, 대리자, 위임과 같은 키워드가 등장한다. 그리고 대리자는 누군가의 일을 대신해서 수행하는 사람을 의미한다. 이 부분은 프로그램에서 프록시를 통해 어떠한 로직을 대신해서 수행하도록 하는 것을 나타낸다.

일반적으로 프록시는 다른 무언가와 이어지는 인터페이스의 역할을 하는 클래스를 의미한다.

프록시 패턴이란?

“프록시”라는 개념을 구조화하여 디자인 패턴으로 적용한 것이다. 프록시 객체를 통해 특정 목적을 가진 접근 제어를 구현하기 위해 고안된 디자인 패턴이다.

프록시 객체를 통해 사용되는 실제 객체를 타겟이라고 한다.

클라이언트가 타겟 객체에 직접 접근하지 않고 프록시를 통해 접근하므로, 타겟 객체의 지연 초기화나 접근 제어가 가능하다. 프록시는 타겟이 메모리에 없더라도 기본 정보를 제공하거나, 필요 시점까지 객체 생성을 미룰 수 있다.

프록시 패턴 내의 키워드

proxy pattern

  • Subject : 실제 객체와 프록시 객체의 공통 인터페이스를 정의.
  • RealSubject : 실제 객체(타겟)를 나타내는 클래스로, Subject를 구현.
  • Proxy: RealSubject와 동일한 인터페이스를 가지며, RealSubject를 대신하여 클라이언트 요청 처리.

프록시의 조건

클라이언트의 요청을 대신해서 수행하는 것이 프록시는 아니다. 프록시는 클라이언트가 이용하는 객체가 프록시인지 타겟인지 알 수 없어야 한다. 이는 곧, 프록시와 타겟 객체는 같은 인터페이스를 상속(확장)해야한다는 것이다. 이로 느슨한 연결을 유지하며, OCP 원칙을 지킬 수 있다.

프록시 패턴 장단점

장점

  1. 크기가 큰 객체(ex : 이미지, 영상)가 로딩되기 전에 프록시를 통해 참조할 수 있다.

  2. 실제 객체의 public, protected 메소드의 내용을 숨기고 인터페이스를 통해 노출시킬 수 있다.

  3. 로컬에 있지 않고 분리되어있는 객체를 이용할 수 있다.

  4. 본 객체에 대한 사전처리를 할 수 있다.(부가 기능을 추가할 수 있다.)

단점

  1. 객체를 생성할때 한 단계를 거치게 되므로, 빈번한 객체 생성이 필요한 경우 성능이 저하될 수 있다.

  2. 프록시 내부에서 객체 생성을 위해 스레드가 생성, 동기화가 구현되야 하는 경우 성능이 저하될 수 있다.

  3. 로직이 복잡해져 가독성이 떨어질 수 있다.

프록시 패턴의 종류

프록시 패턴의 대표적인 종류 3가지가 있다.

가상 프록시

실제 객체의 생성을 지연시키는 “지연 로딩”의 역할을 한다.

보호 프록시

특정 객체에 대한 접근 권한을 제어할 때 사용된다. 클라이언트에게 필요한 보안이나 권한에 대한 문제를 해결하는 역할을 한다.

원격 프록시

네트워크를 통해 분산된 객체에 접근하는 역할을 한다. 예를 들어 Google Docs의 경우가 있다.

프록시 패턴은 어떤 경우에 사용될 수 있을까?

  • 원격지에 존재하는 객체에 대한 접근 : 객체가 원격 서버에 있을 경우 직접적으로 접근하기보다는 프록시 객체를 통해 접근하는 것이 더 안전하고 효율적일 수 있다.

  • 객체에 대한 접근 제어 : 객체에 대한 접근을 허용하거나 제한하기 위해 프록시 패턴을 사용할 수 있다. 예를 들어 객체에 대한 접근 권한이 있는지 확인하고 권한이 없으면 접근을 거부하는 것이 가능하다.

  • 객체 생성 및 소멸에 대한 제어 : 객체의 생성 및 소멸 시점을 제어하기 위해 프록시 패턴을 사용할 수 있다. 예를 들어 객체 생성 시점에 캐시에 저장하거나 객체 소멸 시점에 자원 해제 등의 작업을 수행할 수 있다.

  • 객체에 대한 부가적인 기능 제공 : 객체에 대해 부가적인 기능을 제공하고자 할 때 프록시 패턴을 사용할 수 있다. 예를 들어 객체의 메소드 호출 시점에 로깅, 성능 측정 등의 작업을 수행할 수 있다.

  • 객체에 대한 복잡한 접근 방식 제공 : 객체에 대한 복잡한 접근 방식을 제공하고자 할 때 프록시 패턴을 사용할 수 있다. 예를 들어 객체에 대한 복잡한 쿼리를 수행하거나 객체를 필터링하거나 객체를 변환하는 등의 작업을 수행할 수 있다.

실생활에서 사용가능한 예로는 웹의 경우 이전 방문한 웹페이지의 복사본을 저장하고 이후에 같은 페이지를 요청할 때 로컬에서 바로 제공이 가능하도록 구현할 수 있다. 게임의 경우로 예를 들면 플레이어나 봇이 게임에서 필요한 리소스를 미리 로드하고 캐시 할 때 프록시 패턴이 사용될 수 있다.

Java 프록시 패턴 예시

예로, 용량이 큰 이미지와 텍스트를 화면에 동시에 보여야하는 기능을 구현해야한다면 텍스트는 용량이 작아서 로딩되는 데에 얼마 걸리지 않지만 이미지는 용량이 크기 때문에 로딩이 오래 걸릴 것이다. 텍스트와 이미지의 로딩을 별도로 처리해야 로딩 소요 시간이 적은 텍스트가 먼저 로딩 되고, 이미지의 로딩이 마친 뒤에야 로딩되도록 구현해야할 것이다.

프록시 패턴을 통해 텍스트 로딩 프로세스, 이미지 로딩 프로세스를 별도로 적용할 수 있다.

Image 인터페이스

1
2
3
public interface Image{
  void display();
}

RealImage 구현체 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class RealImage implements Image{
  private String fileName;
    
  public RealImage(String fileName) {
        this.fileName = fileName;
        loadFromDisk(fileName);
  }
    
  private void loadFromDisk(String fileName) {
        System.out.println("Loading " + fileName);
  }
    
  @Override
  public void display() {
    System.out.println("Displaying " + fileName);
  }
}

Proxy 패턴이 적용된 ProxyImage 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ProxyImage implements Image {
  private Image realImage;
  private String fileName;
    
  public Proxy_Image(String fileName) {
        this.fileName = fileName;
  }
    
  @Override
  public void display() {
    if (realImage == null) {
      realImage = new RealImage(fileName);
    }
    realImage.display(); //proxy 패턴의 display에서 위임된 realImage의 display를 수행한다.
  }
}

테스트 실행 main 메소드

1
2
3
4
5
6
7
public static void main(String[] args) {
  Image image1 = new ProxyImage("a.png");
  Image image2 = new ProxyImage("b.png");

  image1.display();
  image2.display();
}

실행 결과

proxy pattern test image


프록시 패턴의 공부를 통해 프록시와 프록시 패턴이 다른 의미라는 것을 알 수 있었고, 이후에는 이 프록시 패턴을 통해 Spring AOP가 어떻게 구성되는 지, 그리고 Spring에서 관리하는 Bean에 이용되는 프록시(CGLIB, JDK 프록시)에 대해 다뤄볼 것이다.

출처

This post is licensed under CC BY 4.0 by the author.