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

프록시 패턴
프록시 패턴은 영어단어 에서 유추할 수 있듯이 어떤 객체에 대한 접근을 제어하기 위해 대리 객체(Proxy)를 사용하는 구조를 말합니다. 즉 실체 객체를 직접 사용하지 않고 그 앞단에 프록시를 두어 접근을 제어하거나 기능을 추가하는 방식입니다.
프록시는 원래 객체처럼 행동하지만, 실제 객체를 감싸고 다양한 목적으로 사용됩니다.
프록시 패턴의 구조

✅ 주요 구성 요소
- Subject (인터페이스 또는 추상 클래스)
- RealSubject와 Proxy가 모두 구현해야 할 공통 인터페이스
- 클라이언트는 이 인터페이스를 바라보고 메서드를 호출 합니다.
- RealSubject (실제 객체)
- 클라이언트가 원래 호출하려던 실제 비즈니스 로직을 담당하는 객체입니다.
- Proxy (대리 객체)
- RealSubject와 동일한 인터페이스를 구현합니다.
- 내부에 RealSubject 인스턴스를 가지고 있으며, 필요 시 객체에게 요청을 위임 합니다.
- 접근 제어, 로깅, 캐싱, 지연 로딩 등 부가적인 기능을 수행 할 수 있습니다.
- 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를 통해 프록시를 효과적으로 활용할 수 있기에 개발자가 직접 프록시를 구현할 일이 적지만 프록시에 대한 동작 원리를 알고 있어야 효율적으로 적용할 수 있을 뿐더러 이슈 상황에서도 잘 대응할 수 있기에 잘 알아두도록 하자
'개발' 카테고리의 다른 글
| 객체에 기능을 동적으로 추가하는 방법: 데코레이터 패턴 (0) | 2025.05.22 |
|---|---|
| 전략 패턴을 더 간결하게: 템플릿 콜백 패턴 (0) | 2025.05.19 |
| 변하는 알고리즘을 분리하는 디자인 패턴: 전략 패턴 (0) | 2025.05.18 |