프록시 패턴 — 객체 생성을 지연시키는 구조
프록시 패턴은 어떤 객체에 대한 접근을 다른 객체가 대신 제어하는 구조다.
클라이언트는 실제 객체를 직접 다루지 않고, 대리 객체(Proxy) 를 통해 간접적으로 접근한다.
이 패턴은 다음과 같은 상황에서 자주 사용된다.
- 객체 생성 비용이 매우 클 때
- 실제 객체 사용이 항상 필요한 것이 아닐 때
- 접근 시점에 부가 로직을 끼워 넣고 싶을 때
Hibernate의 지연 로딩(Lazy Loading) 이 대표적인 실사용 사례다.
예제로 볼 프록시 구조
이번 예제는 이미지 로딩을 예로 든 전형적인 프록시 패턴이다.
역할 분리
역할클래스
| 공통 인터페이스 | Image |
| 실제 객체 (Target) | RealImage |
| 프록시 객체 | ProxyImage |
| 클라이언트 | ProxyMain |
공통 인터페이스
package com.example.demo.proxy;
// HibernateProxy
public interface Image {
void display();
}
- 클라이언트는 인터페이스만 알고
- 실제 구현체가 프록시인지, 진짜 객체인지는 모른다
실제 객체 (Target)
package com.example.demo.proxy;
// Target Entity
public class RealImage implements Image {
private String fileName;
public RealImage(String fileName) {
this.fileName = fileName;
loadFromDisk();
}
private void loadFromDisk() {
System.out.println("디스크에서 파일 로딩 중" + fileName);
}
@Override
public void display() {
System.out.println("이미지 출력" + fileName);
}
}
중요한 포인트
- 생성자에서 디스크 로딩 작업 수행
- 즉, new RealImage() 자체가 비싼 연산
이 객체는 필요할 때만 생성되어야 하는 객체다.
프록시 객체
package com.example.demo.proxy;
// Entity$HibernateProxy
public class ProxyImage implements Image {
private String fileName;
private RealImage realImage;
public ProxyImage(String fileName) {
this.fileName = fileName;
}
@Override
public void display() {
if (realImage == null) { // LazyInitializer.initialize()
realImage = new RealImage(fileName);
}
System.out.println("프록시가 요청 가로 챔 -> ");
realImage.display();
}
}
여기서 프록시가 하는 일
- RealImage를 바로 생성하지 않는다
- display() 호출 시점에만
- 실제 객체가 없으면 생성
- 이후부터는 동일 객체 재사용
이 구조가 바로 지연 초기화(Lazy Initialization) 다.
Hibernate의 내부 프록시 클래스도 개념적으로 동일하다.
클라이언트 코드
package com.example.demo.proxy;
public class ProxyMain {
public static void main(String[] args) {
Image image = new ProxyImage("photo.png");
System.out.println("-- 첫번째 호출 --");
image.display();
System.out.println("-- 두번째 호출 --");
image.display();
}
}
중요한 점
- 클라이언트는 Image 타입만 사용
- 프록시인지, 실제 객체인지 전혀 신경 쓰지 않는다
실행 결과 분석
-- 첫번째 호출 --
디스크에서 파일 로딩 중photo.png
프록시가 요청 가로 챔 ->
이미지 출력photo.png
-- 두번째 호출 --
프록시가 요청 가로 챔 ->
이미지 출력photo.png
흐름 해석
첫 번째 display()
- realImage == null
- 프록시가 실제 객체 생성
- 디스크 로딩 발생
- 실제 로직 위임
두 번째 display()
- 이미 생성된 객체 존재
- 디스크 접근 없음
- 바로 실제 객체로 위임
“생성은 한 번, 사용은 여러 번”
Hibernate 지연 로딩과의 정확한 대응
이 예제는 Hibernate의 다음 구조와 1:1로 대응된다.
예제Hibernate
| Image | 엔티티 타입 |
| ProxyImage | Entity$HibernateProxy |
| realImage == null | LazyInitializer |
| display() 호출 | 엔티티 필드 접근 |
Hibernate는 엔티티를 조회할 때 실제 엔티티 대신
프록시 객체를 먼저 반환하고,
- 필드 접근
- 메서드 호출
- 연관 객체 접근
같은 실제 데이터가 필요한 순간에만 SQL을 날린다.
프록시 패턴의 본질 요약
프록시 패턴의 핵심은 기능이 아니라 “시점 제어”다.
- 객체를 미리 만들지 않는다
- 필요해질 때 만든다
- 접근 전/후에 로직을 끼워 넣을 수 있다
- 클라이언트는 이를 인지하지 않는다
그래서 프록시는 단순한 디자인 패턴을 넘어서
ORM, AOP, 보안, 캐시, 원격 호출 전반에서 쓰인다.
프록시 패턴은
객체 자체를 감추는 패턴이 아니라, 객체가 언제 개입할지를 통제하는 패턴이다.
'디자인패턴' 카테고리의 다른 글
| 템플릿 콜백 패턴 (0) | 2025.12.30 |
|---|---|
| 싱글톤 패턴 (0) | 2025.12.21 |
| 템플릿 메소드 패턴 (0) | 2025.12.19 |