포스트

Spring Proxy: 핵심 로직을 투명하게 감싸는 '기술적 안경'

Spring Proxy: 핵심 로직을 투명하게 감싸는 '기술적 안경'

“한 줄 요약: 프록시는 원본 객체를 수정하지 않고도 런타임에 부가 기능을 동적으로 주입해주는 투명한 비서다.”


1. 프록시의 정의

IT 관점에서 프록시는 타겟 객체(Target Object) 에 대한 호출을 중간에 가로채서(Intercept) 전처리/후처리 로직을 실행한 뒤 타겟에게 요청을 위임하는 대리 객체입니다.

핵심 메커니즘

  1. JoinPoint 가로채기: 클라이언트가 빈(Bean)을 호출할 때 Spring 컨테이너는 실제 객체 대신 프록시 객체를 반환합니다.
  2. Advice 적용: 프록시는 @Transactional이나 로깅 같은 부가 기능을 먼저 실행합니다.
  3. Pointcut 확인: 어떤 메서드에 이 기능을 적용할지 판단한 후 실제 타겟 메서드를 호출합니다.

2. “프록시는 ‘안경’ 같은 거구나!”

위의 복잡한 설명을 실생활에서 예를 들어보면 프록시는 [내 눈과 책 사이에 놓인 안경] 같다고 생각했습니다.

  • 타겟(Target): 읽으려는 책 - 책의 내용(핵심 로직)은 절대로 변하지 않습니다.
  • 프록시(Proxy): 안경 - 내 눈(Client)과 책 사이에서 빛을 굴절시켜 정보를 가공합니다.
  • 부가 기능(Advice): 시력 교정 & 보호
    • 전처리: 빛을 굴절시켜 초점을 맞춥니다. (트랜잭션 시작/보안 체크)
    • 후처리: 블루라이트를 차단해 눈을 보호합니다. (로그 기록/트랜잭션 커밋)

책에 직접 돋보기를 붙이거나 글씨를 크게 해서 다시 인쇄(코드 수정)할 필요가 없습니다. 그저 안경(프록시)을 쓰기만 하면 원본은 그대로인데 내가 보는 경험(기능) 만 업그레이드되는 것입니다.


3. 생성의 차이: JDK Dynamic Proxy vs CGLIB

안경을 만들 때도 사용자의 눈 상태(클래스 구조)에 따라 공법이 달라집니다.

  • JDK Dynamic Proxy (규격 렌즈 방식): - 인터페이스를 구현한 클래스에만 적용 가능합니다.
    • 느낌: “안경테 규격(인터페이스)이 있네요? 그럼 규격에 맞는 표준 렌즈를 깎아 드릴게요.”
  • CGLIB (맞춤형 콘택트 렌즈 방식): - 클래스를 상속받아 바이트코드를 조작합니다.
    • 느낌: “규격서가 없으시군요. 그럼 눈의 형태(클래스)를 그대로 본떠서 밀착형 렌즈를 상속 공법으로 만들어 드릴게요.”

4. 주의사항: 내부 호출(Self-Invocation)의 함정

안경(프록시)을 활용하는 설계에서 가장 빈번하게 발생하는 인프라 로직의 누락 현상입니다.

  • 상황: 안경을 쓰고 책을 읽다가(외부 호출) 중간에 안경을 벗어두고 눈을 감은 채 머릿속으로 내용을 되새기거나 상상합니다(내부 호출).
  • 문제 (Proxy Bypass): 머릿속에서 일어나는 상상은 안경 렌즈를 통과하지 않습니다. 즉 빛이 굴절되어 시야를 교정하는 물리적 상호작용이 생략됩니다.
  • 결과: Spring@Transactional이나 @Async 같은 기능은 AOP 프록시라는 ‘렌즈’를 통과할 때만 가로채기(Intercept)를 통해 부가 기능이 활성화됩니다.

하지만 클래스 내부에서 this.methodB()와 같이 자신의 다른 메서드를 직접 호출하면 호출 주체가 프록시가 아닌 실제 객체(Target) 본인이 됩니다. 결과적으로 호출 흐름이 프록시를 거치지 않고 실제 객체 내부에서만 순환하는 프록시 바이패스(Proxy Bypass) 현상이 발생하며 이 경우 @Transactional 어노테이션이 선언되어 있어도 트랜잭션 매니저와 같은 인프라 로직이 개입할 수 없어 아무런 기능도 작동하지 않습니다.


오늘의 회고: 투명함이 주는 설계의 자유

Insight: “좋은 기술은 안경처럼 투명해야 한다”

프록시의 가장 큰 매력은 비침투성(Non-invasive) 입니다. 내가 안경을 쓰고 있다는 사실을 잊고 책에 몰입하듯 클라이언트 코드는 자신이 프록시를 쓰는지 모른 채 비즈니스 로직에만 집중합니다.

내가 중요하게 생각하는 API 문서화(Swagger)JPA Auditing도 사실 이런 ‘투명한 안경’들이 뒤에서 열심히 일해준 결과이다. “원본의 순수성은 지키되 기능은 확장한다”는 이 철학이 백엔드 설계의 핵심임을 다시금 느낍니다.

다짐

이번 학습을 통해 단순히 @Transactional 어노테이션을 붙이는 것을 넘어 그 이면에서 프록시라는 대리인이 어떻게 인프라 로직을 주입하는지 그 실체를 명확히 이해했습니다.

  • 기술적 사각지대 인지: 안경(프록시)을 통과하지 않는 내부 호출 상황에서는 그 어떤 인프라 기능도 트리거되지 않는다는 점을 배웠습니다. 앞으로 기능이 동작하지 않을 때 무조건적인 설정 변경보다는 호출 주체가 ‘프록시’인지 ‘실제 객체’인지를 먼저 논리적으로 따져보는 습관을 가져봐야겟습니다.
  • 객체지향적 설계의 타당성: 내부 호출 문제를 해결하기 위해 서비스를 분리하는 과정이 단순한 코드 정리가 아니라 프록시 메커니즘을 올바르게 활용하기 위한 공학적 타당성을 갖는 설계임을 깨달았습니다.
  • 투명한 추상화의 이해: 안경 비유처럼 프록시는 원본의 순수성을 지키면서도 기능을 확장하는 고도의 추상화 기술임을 배웠습니다. API 문서 자동화(Swagger)JPA Auditing처럼 당연하게 누려온 편리함 뒤에 프록시라는 투명한 대리인들의 작업이 있음을 기억하겠습니다.

단순히 아무생각없이 습관처럼 쓰던 버릇을 넘어 그 기능이 작동하는 경계(Boundary) 와 조건을 명확히 규정할 수 있는 설계의 근거가 확실한 개발자로 성장하겠습니다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.