Spring에서의 Proxy 패턴 적용해보기

영석·2025년 10월 3일
post-thumbnail

💻 Proxy Pattern 이란?

한국어로는 "대리인"이라는 뜻

대상 객체로의 접근을 중간에서 대신 중개하여 로직에 흐름을 제어하거나
특정 작업을 하도록 설계하는 개발 방법이다.

여기에서 어떤 오브젝트라 함은, 매우 넓은 의미인데
말 그대로 Object, Server, Network 등 다양하다.

이 포스팅에서는 Spring에서의 Proxy 적용 방법과 쓰임을 정리해보고자 한다.

자주 사용해봤을지도??

직접 Proxy 객체를 구현하기보다는,
Library나 Framework에서 제공하는 Proxy 기능을 사용해 본 경험이 더 많을 것이다.

예를 들어 @Transactional 어노테이션이 있습니다.
만약 이를 사용하지 않는다면, 개발자는 모든 비즈니스 로직마다 직접 트랜잭션 코드를 작성해야 한다.

하지만 @Transactional을 적용하면, Spring 컨테이너가 Proxy 객체를 생성하여 트랜잭션을 관리한다.
덕분에 기존 코드를 수정하지 않고도 트랜잭션 기능을 손쉽게 추가할 수 있다.

Proxy를 사용해야 하는 이유

기존 비즈니스 로직을 변경하지 않은 채로 기능을 추가해야 하는 경우

이는 SOLID원칙 중 개방-폐쇄 원칙 과 관련이 있다.

만약 Proxy를 사용하지 않는다면,
기존 비즈니스 로직에 기능을 추가할 때 새로운 기능이 주는 이점기존 코드를 수정함으로써 생기는 리스크
를 모두 고려햐야하는 상황이 발생한다.

그러나 Proxy를 사용하여 기존 비즈니스 로직을 감싸는 Proxy 객체에 기능을 추가한다면
기존 객체를 수정해야 하는 리스크를 피하면서도, 기능을 확장할 수 있다.

따라서 확장에는 열려(Open) 있고, 수정에는 닫혀(Close) 있는 구조를 구현할 수 있게된다.

Proxy 패턴 대표적인 종류 3가지

PaymentService.java

public class PaymentService implements Payment {

    @Override
    public void confirm() {
        System.out.println("[PaymentService.java - confirm()] : 결제완료!");
    }

    @Override
    public void cancel() {
        System.out.println("[PaymentService.java - cancel()] : 결제취소!");
    }
}

Payment.java

package org.practice.proxy;

public interface Payment {

    void confirm();

    void cancel();
}

✅ Base Proxy 패턴

public class PaymentBaseProxy implements Payment {

    private final PaymentService paymentService;

    PaymentBaseProxy(PaymentService paymentService) {
        System.out.println("[PaymentBaseProxy.java - 생성자] : PaymentService 초기화 실행!");
        this.paymentService = paymentService;
    }

    @Override
    public void confirm() {
        System.out.println("[PaymentBaseProxy.java - confirm()] : 프록시 실행!");
        paymentService.confirm();
    }

    @Override
    public void cancel() {
        System.out.println("[PaymentBaseProxy.java - cancel()] : 프록시 실행!");
        paymentService.cancel();
    }
}

Base Proxy 패턴입니다. 호출자와 본 객체 사이에서 요청의 흐름을 조율합니다.
이를 이용해 다양한 기능을 추가 할 수 있습니다.

위 코드에서는 PaymentService를 생성자 주입하고 이를 실행하는 메서드를 설정합니다.
이때 Proxy 객체가 PaymentService가 Implement했던 인터페이스를 동일하게 Implement하여
동일한 메서드를 재정의하여 사용하게 됩니다.

✅ Protection Proxy 패턴

public class PaymentProtectionProxy implements Payment {

    private PaymentService paymentService;
    private User user;

    PaymentProtectionProxy(User user) {
        System.out.println("[PaymentProtectionProxy.java - 생성자] : PaymentService 초기화 실행!");
        paymentService = new PaymentService();
        this.user = user;
    }

    @Override
    public void confirm() {
        System.out.println("[PaymentProtectionProxy.java - confirm()] : 프록시 실행!");

        if (user.hasUserRole("Admin")) { // 프록시에서 권한을 체크한다.
            System.out.println("[PaymentProtectionProxy.java - confirm()] : 어드민 인증 성공!");
            paymentService.confirm();
        } else {
            System.out.println("[PaymentProtectionProxy.java - confirm()] : 어드민 인증 실패!");
        }
    }

    @Override
    public void cancel() {
        System.out.println("[PaymentProtectionProxy.java - cancel()] : 프록시 실행!");
        if (user.hasUserRole("Manager")) { // 프록시에서 권한을 체크한다.
            System.out.println("[PaymentProtectionProxy.java - cancel()] : 매니저 인증 성공!");
            paymentService.cancel();
        } else {
            System.out.println("[PaymentProtectionProxy.java - cancel()] : 매니저 인증 실패!");
        }
    }
}

검증 기능을 수행하는 Protection Proxy 패턴입니다.
본 객체에 메서드를 호출하기전 Proxy 객체에서 권한(검증)을 진행하여 접근을 검증하거나 실행 흐름을 관리합니다.

Proxy 객체에 검증을 위임했기 때문에 본 객체는 변경없이 기능이 추가된 효과를 볼 수 있습니다.

✅ Virtual Proxy 패턴

public class PaymentVirtualProxy implements Payment {

    // PaymentService가 무겁기 때문에 사용될 시점에 초기화 하고 싶을때 - Lazy-Loading
    private PaymentService paymentService;

    PaymentVirtualProxy() {
    }

    @Override
    public void confirm() {
        lazyInitialized();
        System.out.println("[PaymentVirtualProxy.java - confirm()] : 프록시 실행!");
        paymentService.confirm(); // 본 객체에 위임
    }

    @Override
    public void cancel() {
        lazyInitialized();
        System.out.println("[PaymentVirtualProxy.java - confirm()] : 프록시 실행!");
        paymentService.cancel(); // 본 객체에 위임
    }


    private void lazyInitialized() {
        if (paymentService == null) {
            System.out.println(
                "[PaymentVirtualProxy.java - ensureInitialized()] : PaymentService 지연 초기화 실행!");
            paymentService = new PaymentService();
        }
    }
}

대상 서비스나 객체의 생성 비용이 큰 경우, 프록시를 사용하여 지연 초기화(Lazy Initialization) 를 구현할 수 있습니다. 이를 통해 불필요한 리소스 낭비를 줄이고, 실제 사용 시점에만 객체를 생성하여 시스템 효율성을 높일 수 있습니다.

profile
느리게 갱신되는 개발실력 - >_0

0개의 댓글