[4] 디자인패턴: 프록시 패턴 (Proxy)

2025. 12. 29. 01:29·Java/Design Pattern

1. 들어가며

 스프링 프레임워크의 핵심 기술인 AOP(관점 지향 프로그래밍)를 이해하기 위해 반드시 넘어야 할 산이 바로 프록시 패턴이다. 프록시(Proxy)는 사전적 의미로 '대리인'을 뜻하며, 객체지향 세계에서는 실제 객체를 직접 호출하는 대신 그 앞에 대리 객체를 두어 제어 흐름을 가로채거나 접근 권한을 제어하는 역할을 수행한다.

 

 일반적인 객체 호출 구조는 클라이언트가 필요한 서비스를 직접 호출하는 방식이다. 하지만 실무에서는 핵심 비즈니스 로직 외에도 권한 체크, 로그 기록, 캐싱, 트랜잭션 처리와 같은 부가적인 기능(Cross-cutting Concerns)이 필요할 때가 많다.

 

 이러한 부가 기능을 실제 서비스 코드에 직접 작성하면 코드가 복잡해지고 유지보수가 어려워진다. 이때 프록시 패턴을 도입하면 기존 코드를 전혀 수정하지 않고도 부가 기능을 마법처럼 추가할 수 있는 것이다.


2. 프록시 패턴의 구조와 원리

 프록시 패턴의 핵심은 클라이언트가 자신이 호출하는 대상이 실제 객체인지 프록시 객체인지 모르게 하는 것이다. 이를 위해 실제 객체와 프록시는 반드시 동일한 인터페이스를 구현해야 한다.

2.1. 구성 요소

  1. 인터페이스 (Subject): 실제 객체와 프록시가 공통으로 구현하는 인터페이스이다.
  2. 실제 객체 (Real Subject): 실제 비즈니스 로직을 수행하는 본체이다.
  3. 프록시 객체 (Proxy): 실제 객체를 감싸고 있는 대리자이다. 클라이언트의 요청을 가로채어 전처리나 후처리를 수행한 뒤 실제 객체에게 작업을 위임한다.

3. 스프링 부트로 구현하는 프록시 패턴

전통적인 방식의 프록시 구현을 통해 그 원리를 구체적으로 살펴본다.

3.1. 인터페이스와 실제 객체 정의

단순히 데이터를 조회하는 서비스를 가정해본다.

public interface DataService {
    String fetchData();
}

@Service
public class RealDataService implements DataService {
    @Override
    public String fetchData() {
        System.out.println("실제 DB에서 데이터를 조회합니다...");
        return "중요한 데이터";
    }
}

3.2. 프록시 객체의 구현 (캐싱 프록시 예시)

동일한 데이터를 반복 조회할 때, 매번 DB에 접근하지 않도록 프록시가 결과를 캐싱하는 예제인 것이다.

@Component
@Primary // 스프링이 의존성을 주입할 때 프록시를 우선적으로 선택하게 함
public class DataCacheProxy implements DataService {

    private final RealDataService realDataService;
    private String cachedData; // 데이터를 보관할 변수

    public DataCacheProxy(RealDataService realDataService) {
        this.realDataService = realDataService;
    }

    @Override
    public String fetchData() {
        // 전처리: 캐시된 데이터가 있는지 확인
        if (cachedData == null) {
            System.out.println("[Proxy] 캐시된 데이터가 없어 실제 객체를 호출합니다.");
            cachedData = realDataService.fetchData(); // 실제 객체에 위임
        } else {
            System.out.println("[Proxy] 캐시된 데이터를 반환합니다.");
        }
        
        // 후처리: 결과 반환
        return cachedData;
    }
}

4. 스프링 부트 실무에서의 프록시 활용

우리가 작성하는 코드 곳곳에는 스프링이 자동으로 생성한 프록시가 숨어 있다.

4.1. @Transactional의 비밀

스프링에서 @Transactional 어노테이션을 붙이면 해당 빈은 프록시 객체로 생성된다. 클라이언트가 메서드를 호출하면 프록시가 먼저 호출되어 Transaction Start를 수행하고, 실제 메서드가 종료되면 Commit이나 Rollback을 처리하는 방식이다. 이것이 바로 스프링 AOP의 실체인 것이다.

4.2. JPA 지연 로딩 (Lazy Loading)

JPA에서 연관된 엔티티를 조회할 때 FetchType.LAZY를 설정하면, 실제 DB 쿼리를 날리기 전까지는 가짜 객체가 들어있다. 이 가짜 객체가 바로 프록시이다. 실제 데이터에 접근하는 시점에 프록시가 DB를 조회하여 값을 채워넣는 '가상 프록시' 기법을 사용한다.

4.3. JDK Dynamic Proxy vs CGLIB

스프링 부트는 프록시를 생성할 때 두 가지 방식을 사용한다.

  • JDK Dynamic Proxy: 인터페이스가 있을 때 자바 기본 기능을 사용하여 프록시를 생성한다.
  • CGLIB: 인터페이스가 없고 클래스만 있을 때, 바이트코드를 조작하여 해당 클래스를 상속받은 하위 클래스를 프록시로 만든다. 스프링 부트 2.x 이상부터는 성능상의 이유로 CGLIB를 기본으로 사용하는 경우가 많다.

5. 요약 및 주의사항

5.1. 장점

  • OCP(개방-폐쇄 원칙) 준수: 기존의 비즈니스 로직 코드를 한 줄도 수정하지 않고 기능을 추가할 수 있다.
  • SRP(단일 책임 원칙) 준수: 실제 객체는 본연의 로직에만 집중하고, 부가 기능은 프록시가 전담한다.

5.2. 단점 및 주의점

  • 객체 생성 오버헤드: 프록시 객체를 거쳐야 하므로 아주 미세한 성능 차이가 발생할 수 있다.
  • 내부 호출 문제 (Self-Invocation): 같은 클래스 내에서 프록시가 적용된 메서드를 호출하면 프록시를 타지 않고 실제 객체의 메서드가 직접 호출된다. 이는 @Transactional이 적용되지 않는 흔한 장애 원인이 되므로 각별한 주의가 필요하다.

'Java > Design Pattern' 카테고리의 다른 글

[6] 디자인패턴: 팩토리 패턴 (Factory)  (0) 2025.12.29
[5] 디자인패턴: 데코레이터 패턴 (Decorator)  (0) 2025.12.29
[3] 디자인패턴: 템플릿 패턴 (Template Method/Callback)  (0) 2025.12.29
[2] 디자인패턴: 전략 패턴 (Strategy)  (0) 2025.12.28
[1] 디자인패턴: 왜 스프링 부트 개발자에게 GoF는 필수인가  (0) 2025.12.28
'Java/Design Pattern' 카테고리의 다른 글
  • [6] 디자인패턴: 팩토리 패턴 (Factory)
  • [5] 디자인패턴: 데코레이터 패턴 (Decorator)
  • [3] 디자인패턴: 템플릿 패턴 (Template Method/Callback)
  • [2] 디자인패턴: 전략 패턴 (Strategy)
h6bro
h6bro
백엔드 개발자의 기술 블로그
  • h6bro
    Jun's Tech Blog
    h6bro
  • 전체
    오늘
    어제
    • 분류 전체보기 (250) N
      • Java (18)
        • Core (9)
        • Design Pattern (9)
      • Spring (80)
        • Core (24)
        • MVC (6)
        • DB (10)
        • JPA (26)
        • Monitoring (3)
        • Security (11)
        • WebSocket (0)
      • Database (33)
        • Redis (15)
        • MySQL (18)
      • MSA (25) N
        • MSA 기본 (11)
        • MSA 아키텍처 (14) N
      • Kafka (30)
        • Core (18)
        • Connect (12)
      • ElasticSearch (11)
        • Search (11)
        • Logging (0)
      • Test (4)
        • k6 (4)
      • Docker (9)
      • CI&CD (10)
        • GitHub Actions (6)
        • ArgoCD (4)
      • Kubernetes (18)
        • Core (12)
        • Ops (6)
      • Cloud Engineering (4)
        • AWS Infrastructure (3)
        • AWS EKS (1)
        • Terraform (0)
      • Project (8)
        • LinkFolio (1)
        • Secondhand Market (7)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

    • Cloud Engineering 포스팅 정리
  • 인기 글

  • 태그

    ㅈ
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
h6bro
[4] 디자인패턴: 프록시 패턴 (Proxy)
상단으로

티스토리툴바