Java

Java - 프록시 객체와 프록시 패턴

victory-line 2025. 1. 2. 23:33

1. 프록시 객체(Proxy Object)

정의

  • 프록시 객체는 원래 객체를 대신하여 동작하는 대리 객체이며, 원래 객체에 대한 간접적인 접근을 제공하고, 원래 객체의 작업을 지연시키거나 추가적인 처리를 수행할 수 있음
  • 객체의 실제 동작을 가리키는 참조(reference) 역할을 함

특징

  • 프록시 객체는 실제 객체와 동일한 인터페이스(또는 구조)를 가짐
  • 프록시 객체를 통해 실제 객체를 호출하거나, 호출 전에 추가 작업(로깅, 인증, 지연 초기화 등)을 수행할 수 있음

예시: JPA에서의 프록시 객체

JPA에서는 데이터베이스 엔티티를 실제로 가져오기 전에 프록시 객체를 생성하여 사용함

@Entity public class User { @Id @GeneratedValue private Long id; private String name; }

User user = entityManager.getReference(User.class, 1L);
  • getReference() 메서드를 호출하면 User 엔티티의 실제 객체 대신 프록시 객체가 반환됨
  • 프록시 객체는 실제 데이터베이스 쿼리를 실행하지 않고, 필요할 때(예: user.getName()호출 시) 실제 데이터를 가져옴

프록시 객체의 주요 역할

  1. 지연 로딩(Lazy Loading):
    • 프록시 객체는 데이터베이스에서 엔티티를 즉시 가져오지 않고, 엔티티의 속성이 접근되는 순간에 데이터베이스 쿼리를 실행 -> 불필요한 데이터 로드를 방지하고 성능을 최적화
  2. 데이터베이스와의 연결 유지:
    • 프록시 객체는 데이터베이스 세션(EntityManager)과 연결을 유지하여 필요한 시점에 데이터를 로드
  3. 경량 객체 생성:
    • 프록시 객체는 실제 데이터를 포함하지 않고, 최소한의 정보를 유지 -> 메모리 사용을 줄이는 데 도움을 줌

프록시 객체의 단점

  1. LazyInitializationException:
    • 프록시 객체를 사용할 때, 영속성 컨텍스트(EntityManager)가 닫혀 있으면 데이터 로드에 실패함
  2. 타입 문제:
    • 프록시 객체는 실제 객체가 아닌 서브 클래스이므로, instanceof 또는 JSON 직렬화 시 문제가 발생할 수 있음

2. 프록시 패턴(Proxy Pattern)

정의

  • 프록시 패턴디자인 패턴의 하나로, 실제 객체를 대체하거나 보호하기 위해 대리 객체를 제공하는 패턴임
  • 원래 객체에 대한 접근을 제어하거나, 추가적인 로직(예: 캐싱, 로깅)을 삽입할 수 있음

구조

프록시 패턴은 3가지 주요 요소로 구성됨

  1. Subject (인터페이스 또는 추상 클래스):
    • 실제 객체와 프록시 객체가 공통으로 구현하는 인터페이스임
  2. RealSubject (실제 객체):
    • 실제 비즈니스 로직을 수행하는 클래스임
  3. Proxy (프록시 객체):
    • RealSubject와 동일한 인터페이스를 구현함, RealSubject로의 접근을 제어하거나 추가 로직을 처리함

프록시 패턴의 종류

  1. Virtual Proxy:
    • 실제 객체가 무거운 리소스를 사용할 경우, 객체의 생성을 지연함 (예: Lazy Loading)
  2. Protection Proxy:
    • 실제 객체에 대한 접근을 제어함 (예: 권한 검증)
  3. Remote Proxy:
    • 다른 네트워크 상의 객체를 로컬에서 호출할 수 있도록 프록시를 제공함 (예: RMI)
  4. Smart Proxy:
    • 실제 객체 호출 전후에 추가 로직(예: 로깅, 캐싱)을 삽입함

예시: Virtual Proxy

// 공통 인터페이스
public interface Image {
    void display();
}

// 실제 객체
public class RealImage implements Image {
    private String filename;

    public RealImage(String filename) {
        this.filename = filename;
        loadFromDisk();
    }

    private void loadFromDisk() {
        System.out.println("Loading " + filename);
    }

    @Override
    public void display() {
        System.out.println("Displaying " + filename);
    }
}

// 프록시 객체
public class ProxyImage implements Image {
    private RealImage realImage;
    private String filename;

    public ProxyImage(String filename) {
        this.filename = filename;
    }

    @Override
    public void display() {
        if (realImage == null) {
            realImage = new RealImage(filename); // 실제 객체를 초기화
        }
        realImage.display();
    }
}

// 클라이언트 코드
public class Main {
    public static void main(String[] args) {
        Image image = new ProxyImage("photo.jpg");
        image.display(); // 실제 객체를 초기화하고 메서드 실행
        image.display(); // 초기화된 객체를 재사용
    }
}​

출력 결과

 
Loading photo.jpg
Displaying photo.jpg
Displaying photo.jpg​

JPA와의 관계

  • JPA에서 프록시 객체는 Virtual Proxy 패턴에 해당함
  • JPA는 Hibernate와 같은 구현체를 통해 프록시 객체를 생성하고, 데이터베이스에 실제로 접근하는 시점을 제어함

프록시 객체와 프록시 패턴의 관계

  • 프록시 패턴은 디자인 원칙이며, 다양한 용도로 구현될 수 있음
  • 프록시 객체는 프록시 패턴을 기반으로 동작하며, 실제 객체를 대신하여 특정 기능(예: 지연 로딩, 접근 제어 등)을 수행함
  • JPA에서 사용하는 프록시 객체는 프록시 패턴의 한 구체적인 활용 사례임