Loading...
태그
    cglibdynamic-proxyjavaspring-aopspring-framework

Spring AOP와 Dynamic Proxy 동작 원리-JDK Proxy와 CGLIB

최종수정일
2026년 03월 26일
5분걸림
작성자: Techy J
Table of Content (목차)

동적 프록시(Dynamic Proxy)는 실행 중에 '가짜 객체(Proxy)'를 만들어 실제 객체 앞뒤에 공통 로직(로그, 트랜잭션 등)을 끼워 넣는 기술이다. Spring에서 @Transactional을 붙이면 자동으로 트랜잭션을 시작하고 커밋 또는 롤백을 처리하는 기능이 동작하는데, 그 핵심 구현 기술이 바로 프록시이다. Spring IoC Container는 Bean Lifecycle 과정에서 필요할 경우 실제 객체 대신 Proxy 객체를 생성하여 Bean을 감싼다. 이 Proxy 객체는 메서드 실행 전후에 공통 기능을 추가하는 역할을 수행한다. 이러한 구조는 AOP(Aspect Oriented Programming)를 구현하기 위한 핵심 기술이다. 대표적인 예가 다음과 같은 기능이다.

  • 트랜잭션 처리
  • 로깅
  • 권한 검사
  • 캐싱
  • 비동기 실행

Spring에서 @Transactional 같은 Annotation을 사용하면 개발자가 직접 트랜잭션 코드를 작성하지 않아도 된다. 이 기능은 내부적으로 Proxy 객체가 실제 서비스 객체를 감싸는 구조로 구현된다.

동적 프록시 (Dynamic Proxy)

동적 프록시는 실행 중에 Proxy 객체를 생성하는 기술이다. 이 Proxy 객체는 실제 객체 앞뒤에 공통 로직을 삽입하는 역할을 수행한다. 예를 들어 다음과 같은 구조가 만들어진다.

Controller → Proxy(Service) → 실제 Service

Controller는 Service를 직접 호출한다고 생각하지만 실제로는 Proxy 객체를 호출한다. Proxy 객체는 메서드 실행 전후에 필요한 부가 기능을 수행한 뒤 실제 Service 메서드를 실행한다. 이 방식 덕분에 비즈니스 로직 코드와 공통 기능을 분리할 수 있다.

JDK 동적 프록시

Java는 표준 라이브러리에서 동적 프록시 기능을 제공한다. 이를 JDK Dynamic Proxy라고 한다. 이 방식은 인터페이스 기반으로 Proxy 객체를 생성한다. 즉 Proxy 대상 객체가 인터페이스를 구현하고 있어야 한다. 다음은 인터페이스 기반 동적 프록시 예제 코드이다.

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
 
// 1. 인터페이스 (POJO의 핵심)
interface Hello {
    void sayHello(String name);
}
 
// 2. 실제 비즈니스 로직 (원본 객체)
class HelloTarget implements Hello {
    public void sayHello(String name) {
        System.out.println("본체 로직 수행: 안녕, " + name);
    }
}
 
// 3. 부가 기능을 담당하는 핸들러 (프록시가 호출할 내용)
class LogHandler implements InvocationHandler {
    private final Object target; // 실제 객체
 
    public LogHandler(Object target) {
        this.target = target;
    }
 
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(">>> [LOG] 메서드 시작: " + method.getName()); // 부가 기능(공통)
        
        Object result = method.invoke(target, args); // 실제 본체 로직 실행 (리플렉션 사용)
        
        System.out.println("<<< [LOG] 메서드 종료"); // 부가 기능(공통)
        return result;
    }
}
 
public class ProxyExample {
    public static void main(String[] args) {
        Hello target = new HelloTarget(); // 원본
 
        // 4. 런타임에 가짜(Proxy) 객체 생성
        Hello proxyInstance = (Hello) Proxy.newProxyInstance(
                Hello.class.getClassLoader(), // 프록시 클래스를 로딩할 로더
                new Class[]{Hello.class}, // 프록시 객체가 구현할 인터페이스 목록
                new LogHandler(target) // 프록시는 LogHandler 기능 수행
        );
 
        // 5. 프록시 호출 (사용자는 원본인지 프록시인지 모름)
        // 프록시 객체는 invoke를 호출
        proxyInstance.sayHello("Spring");
    }
}
  • Proxy.newProxyInstance(): 자바 언어 차원에서 제공하는 기능으로, 런타임에 Hello 인터페이스를 구현한 Proxy 객체를 생성
  • LogHandler (InvocationHandler): 프록시 객체의 메서드가 호출되면 무조건 이 핸들러의 invoke 메서드가 실행. Spring 는 이 위치에서 트랜잭션 시작(begin), 로그 기록, 권한 체크 같은 공통 작업을 처리
  • method.invoke(target, args): 부가 기능을 다 수행한 후, 실제 비즈니스 로직(HelloTarget)을 실행. 원본 코드(HelloTarget)를 수정하지 않고도 기능을 추가할 수 있음

핵심 flow를 정리하면 아래와 같다.

proxy.sayHello()  
→ InvocationHandler.invoke()  
→ method.invoke(target)  
→ 실제 메서드 실행

Spring AOP와 Proxy

Spring은 이러한 Proxy 구조를 이용해 AOP 기능을 구현한다. 예를 들어 다음과 같은 코드가 있다고 가정해 보자.

@Transactional  
@Service  
public class OrderService {  
  
    public void order() {  
        System.out.println("주문 처리");  
    }  
  
}

이 경우 Spring IoC Container는 다음과 같은 구조로 Bean을 생성할 수 있다.

Controller → Transaction Proxy → OrderService

Controller는 OrderService를 직접 호출한다고 생각하지만 실제로는 Proxy 객체를 호출한다. Proxy 객체는 다음과 같은 작업을 수행한다.

  • 트랜잭션 시작
  • 실제 서비스 메서드 실행
  • 트랜잭션 커밋 또는 롤백

이 모든 과정이 Proxy 내부에서 자동으로 처리된다.

Spring에서 사용되는 Proxy 종류

Spring에서 사용하는 Proxy 방식은 두 가지가 있다. JDK Dynamic Proxy는 인터페이스 기반 프록시와 CGLIB Proxy는 클래스 상속 기반 프록시를 사용한다. Spring은 기본적으로 인터페이스가 존재하는 경우 JDK Dynamic Proxy를 사용하고, 인터페이스가 없는 경우 CGLIB Proxy를 사용한다. Spring Boot 환경에서는 기본 설정에 따라 CGLIB Proxy가 사용되는 경우도 많다.

JDK Dynamic Proxy

  • 인터페이스 기반 Proxy 생성
  • java.lang.reflect.Proxy 사용
  • 인터페이스가 반드시 필요

CGLIB Proxy

  • 클래스 상속 기반 Proxy 생성
  • 인터페이스가 없어도 사용 가능
  • 메서드 오버라이딩 방식으로 동작

CGLIB Proxy 예시 코드

JDK 동적 프록시는 인터페이스 기반으로 Proxy 객체를 생성한다. 하지만 실제 개발에서는 인터페이스가 없는 클래스도 존재한다. 이 경우 사용할 수 있는 방식이 CGLIB Proxy이다. CGLIB는 클래스를 상속받아 Proxy 객체를 생성하는 방식이다. 즉 인터페이스가 없어도 Proxy를 만들 수 있다. 다음은 CGLIB를 이용해 메서드 실행 전후에 로그를 출력하는 예제 코드이다.

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
 
// 실제 비즈니스 로직 클래스 (원본 객체)
class HelloService {
    public void sayHello(String name) {
        System.out.println("본체 로직 수행: 안녕, " + name);
    }
}
 
// CGLIB 인터셉터
// 프록시 객체에서 메서드가 호출될 때 실행되는 로직
class LogInterceptor implements MethodInterceptor {
 
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
 
        // 부가 기능 실행 (AOP 공통 로직)
        System.out.println("LOG 시작: " + method.getName());
 
        // 실제 메서드 호출
        // invokeSuper는 프록시가 상속한 부모 클래스의 메서드를 실행한다
        Object result = proxy.invokeSuper(obj, args);
 
        // 부가 기능 실행
        System.out.println("LOG 종료");
 
        return result;
    }
}
 
public class CglibProxyExample {
    public static void main(String[] args) {
 
        // CGLIB 프록시 생성을 위한 Enhancer 객체
        Enhancer enhancer = new Enhancer();
 
        // 프록시가 상속할 대상 클래스 지정
        enhancer.setSuperclass(HelloService.class);
 
        // 메서드 호출을 가로챌 인터셉터 등록
        enhancer.setCallback(new LogInterceptor());
 
        // 런타임에 HelloService를 상속한 Proxy 클래스 생성
        HelloService proxy = (HelloService) enhancer.create();
 
        // 프록시 객체의 메서드 호출
        proxy.sayHello("Spring");
    }
}
  • Enhancer: CGLIB에서 프록시 객체를 생성하는 핵심 클래스. 대상 클래스를 상속한 Proxy 클래스를 런타임에 생성
  • setSuperclass(HelloService.class): 프록시가 상속할 대상 클래스를 지정. CGLIB는 이 클래스를 기반으로 Proxy 클래스를 생성
  • setCallback(new LogInterceptor()): 메서드 호출을 가로챌 인터셉터를 등록. 프록시 객체의 메서드가 호출되면 intercept()가 실행
  • intercept(): 프록시 메서드 호출 시 가장 먼저 실행되는 메서드. 실제 메서드 실행 전후에 공통 기능(로그, 트랜잭션 등)을 수행
  • proxy.invokeSuper(obj, args): 실제 부모 클래스의 메서드를 호출. 실제 비즈니스 로직이 실행되는 지점
  • enhancer.create(): 대상 클래스를 상속한 Proxy 객체를 런타임에 생성

핵심 flow를 정리하면 아래와 같다.

proxy.sayHello()
intercept()
invokeSuper()
→ HelloService.sayHello()

이 구조 덕분에 원본 클래스를 수정하지 않고도 메서드 실행 전후에 원하는 기능을 추가할 수 있다.

정리하면 Proxy는 실제 객체 앞에서 메서드 호출을 가로채고 공통 기능을 수행하는 객체이다. Spring은 Proxy 기술을 이용해 다음과 같은 기능을 구현한다.

  • 트랜잭션 관리
  • 로깅
  • 보안 검사
  • 캐싱
  • 비동기 실행

이러한 구조 덕분에 개발자는 비즈니스 로직 코드만 작성하고 공통 기능은 Spring이 자동으로 처리한다. 즉 Spring AOP의 핵심 구현 기술이 Proxy이다.

(끝)

이 글은 ' 출처: 변호사 전정숙 '과 ' 원본링크: https://www.korean-lawer.com/articles/spring-framework/spring-aop-dynamic-proxy-jdk-cglib-explained'를 명시하는 조건으로 인용가능 합니다.
무단 복제, AI 학습 목적으로의 사용과 Google, Naver의 Indexing 외 크롤링 금지합니다
About
전정숙 변호사
법무법인 정맥 파트너 변호사부산파산법원 파산관재인전) 부산변호사회 부회장전) 전국여성변호사회 부회장전) 부산 가정법원 조정위원
Contact

(82) 051-916-8582 , 051-916-8583

부산광역시 연제구 법원로 12 (거제동)

로윈타워빌딩 2층 법무법인정맥

변호사 전정숙

© 2005-2026 전정숙 변호사.

All Rights Reserved.