본문 바로가기

개발

Spring AOP를 이해하기 전 : 프록시 패턴

반응형

 

Spring AOP의 동작 원리를 깊이 있게 이해하려고 공부하던 중, 빠지지 않고 등장하는 개념이 바로 프록시 였습니다. AOP는 내부적으로 프록시 객체를 생성해 핵심 기능 앞뒤에 부가적인 기능을 끼워넣는 방식으로 동작하는데, 이 구조를 명확히 이해하려면 Proxy 패턴에 대한 기본적인 이해가 필수라고 생각해 프록시 패턴에 대해 정리해 보려고 합니다.

 


 

 

프록시 패턴 ( Proxy Pattern )

프록시?

사전적으로 Proxy는 "대리, 대리인, 대용물"등의 뜻을 가지고 있습니다.

프록시 패턴

프록시 패턴은 영어단어 에서 유추할 수 있듯이 어떤 객체에 대한 접근을 제어하기 위해 대리 객체(Proxy)를 사용하는 구조를 말합니다. 즉 실체 객체를 직접 사용하지 않고 그 앞단에 프록시를 두어 접근을 제어하거나 기능을 추가하는 방식입니다.

프록시는 원래 객체처럼 행동하지만, 실제 객체를 감싸고 다양한 목적으로 사용됩니다.

 

프록시 패턴의 구조

 

✅ 주요 구성 요소

  1. Subject (인터페이스 또는 추상 클래스)
    • RealSubject와 Proxy가 모두 구현해야 할 공통 인터페이스
    • 클라이언트는 이 인터페이스를 바라보고 메서드를 호출 합니다.
  2. RealSubject (실제 객체)
    • 클라이언트가 원래 호출하려던 실제 비즈니스 로직을 담당하는 객체입니다.
  3. Proxy (대리 객체)
    • RealSubject와 동일한 인터페이스를 구현합니다.
    • 내부에 RealSubject 인스턴스를 가지고 있으며, 필요 시 객체에게 요청을 위임 합니다.
    • 접근 제어, 로깅, 캐싱, 지연 로딩 등 부가적인 기능을 수행 할 수 있습니다.
  4. Client
    • Subject 인터페이스에 의존합니다.
    • Proxy, RealSubject든 어떤 구현체인지 알 필요없이 동일한 방식으로 사용
    이런 구조를 통해 클라이언트는 실제 객체의 동작은 유지하면서도, 객체 생성/호출 과정에 추가적인 제어나 기능을 넣을 수 있습니다.

 

프록시 패턴 예제 코드 : 문 열기 닫기

이번 예제에서는 Door 인터페이스를 통해 문을 열고 닫는 기능을 정의합니다.
RealDoor는 실제로 문을 제어하는 객체이고, Security는 문을 열기전에 보안을 확인하는 프록시 역할을 합니다.

// 문 - 인터페이스
public interface Door {
    void open();
    void close();
}

Door (인터페이스)

  • 먼저, 문이 수행할 작업을 정의하는 인터페이스를 만듭니다.
// 실제 문 객체
public class RealDoor implements Door {
    @Override
    public void open() {
        System.out.println("RealDoor 문이 열립니다.");
    }

    @Override
    public void close() {
        System.out.println("RealDoor 문이 닫힙니다.");
    }
}

// 보안 문 프록시 객체
public class SecurityDoorProxy implements Door {

    private final RealDoor realDoor;

    public SecurityDoorProxy() {
        this.realDoor = new RealDoor();
    }

    @Override
    public void open() {
        System.out.println("SecurityDoorProxy: 문을 열기 전에 보안 확인 중...");
        realDoor.open();
    }

    @Override
    public void close() {
        System.out.println("SecurityDoorProxy: 문을 닫기 전에 보안 확인 중...");
        realDoor.close();
    }
}

 RealDoor (실제 문 객체)

  • 실제 문을 열고 닫는 로직을 구현하는 클래스로 순수하게 문의 작동 기능만 담당 합니다.

SecurityDoorProxy (문 프록시 객체)

  • Door 인터페이스를 구현하고, 내부적으로 RealDoor 인스턴스를 가집니다.
  • open(), close() 메소드를 호출하기전에 보안을 확인합니다.

 

System.out.println("===== 실제 문 객체 호출 =====");
Door realDoor = new RealDoor();
realDoor.open();
realDoor.close();
// ===== 실제 문 객체 호출 =====
// RealDoor 문이 열립니다.
// RealDoor 문이 닫힙니다.

System.out.println("===== 프록시 문 객체 호출 =====");
Door securityDoorProxy = new SecurityDoorProxy();
securityDoorProxy.open();
securityDoorProxy.close();
// ===== 프록시 문 객체 호출 =====
//SecurityDoorProxy: 문을 열기 전에 보안 확인 중...
//RealDoor 문이 열립니다.
//SecurityDoorProxy: 문을 닫기 전에 보안 확인 중...
//RealDoor 문이 닫힙니다.

Client

  • 실제 문 객체와 프록시 문 객체를 생성해서 호출하는 코드입니다.
  • 클라이언트는 Door 인터페이스를 통해 문을 제어하고 보안기능이 프록시에 의해 제공되는지는 알 필요가 없습니다.

SecurityDoorProxy는 open() 메소드 호출 전에 보안을 확인하고 실제 RealDoor의 open() 메소드가 호출 됩니다.

핵심 비즈니스 로직 문 열기(open())는 변경 없이 보안 확인 이라는 부가적인 기능을 추가했습니다. RealDoor는 보안에 대해 전혀 알지 못합니다.

클라이언트는 Door의 인터페이스만 알고 내부적으로 RealDoor를 사용하는지 SecurityDoorProxy를 사용하는지 신경 쓸 필요가 없습니다.

 

 

프록시 패턴의 특징, 장단점

 

🌟 장점

  • 접근 제어 및 보안 강화
    실제 객체에 대한 접근을 중간에서 제어하여 권한 검사, 인증 보안 기능을 구현 할 수 있습니다.
  • 추가 기능 삽입
    실제 객체의 핵심 로직을 변경하지 않고, 메소드 호출 전후에 로깅, 캐싱, 트랜잭션 관리 등 부가 기능을 추가할 수 있음
  • 클라이언트의 투명성
    프록시와 실제 객체가 동일한 인터페이스를 구현하므로, 클라이언트는 자신이 실제 객체를 사용하는지 프록시 객체를 사용하는지 알 필요가 없고, 이는 코드에 유연성을 높임
  • 지연 초기화 / 로딩
    무거운 객체의 생성을 실제로 필요할 때까지 지연 시킬 수 있고, 불필요한 자원 낭비를 줄여 성능을 최적화 할 수 있다

 

⚠️ 단점

  • 클래스 수 증가
    하나의 기능을 위해 인터페이스, 실제 객체, 프록시 객체 등 여러개의 클래스를 생성해야 함 전체 시스템의 구조가 복잡해 질 수 있음
  • 성능 저하 가능성
    프록시를 통한 간접적은 호출은 직접 호출보다 성능 오버헤드를 발생 시킬 수 있다. 특히 프록시가 많은 부가 기능을 수행하거나 여러 단계의 프록시가 체인 형태로 연결될 경우 더 두드러질 수 있음
  • 디버깅의 어려윰
    프록시 계층이 추가되면서 실제 로직의 흐름을 추적하기 어려울 수 있음

 

 

📌 정리

프록시 패턴은 특정 상황에서 많은 이점을 제공하지만, 불필요하게 사용될 경우 시스템의 복잡성을 증가시키고 성능 저하를 가져올 수 있기에 신중하게 적용해야 한다.

스프링의 경우는 AOP를 통해 프록시를 효과적으로 활용할 수 있기에 개발자가 직접 프록시를 구현할 일이 적지만 프록시에 대한 동작 원리를 알고 있어야 효율적으로 적용할 수 있을 뿐더러 이슈 상황에서도 잘 대응할 수 있기에 잘 알아두도록 하자

반응형