본문 바로가기
language/java

JDK Proxy vs CGLIB in Spring

by 죄니안죄니 2026. 6. 17.
반응형

이번 글은 Spring 면접에서 단골 질문이며, AOP를 제대로 이해했는지 확인하는 질문이기도 하다.

면접에서 자주 나오는 질문이 있다.

"Spring은 JDK Dynamic Proxy와 CGLIB 중 어떤 것을 사용하나요?"

많은 사람들이

"인터페이스 있으면 JDK Proxy, 없으면 CGLIB입니다."

라고만 답한다.

틀린 답은 아니지만, 왜 그렇게 설계되었는지까지 이해해야 Spring Proxy를 제대로 이해한 것이다.


JDK Proxy vs CGLIB in Spring

Spring은 어떻게 Proxy를 만들까?

이전 글에서 우리는

Client

↓

Proxy

↓

Real Object
 

구조를 배웠다.

그리고

 
@Transactional
public void save() {

}
 

가 실제로는

Transaction Proxy

↓

Real Service
 

를 통해 실행된다는 것도 살펴봤다.

그렇다면 새로운 질문이 생긴다.

Spring은 Proxy를 어떤 방식으로 만들까?


Proxy를 만드는 방법은 두 가지

Spring은 크게 두 가지 방법으로 Proxy를 생성한다.

JDK Dynamic Proxy

CGLIB Proxy
 

둘 다 Proxy를 만든다는 점은 같지만,

만드는 방식이 완전히 다르다.


JDK Dynamic Proxy

JDK가 기본으로 제공하는 기능이다.


다음 구조를 보자.

 
public interface UserService {

    void save();

}
 

 
@Service
public class UserServiceImpl
        implements UserService {

    @Override
    public void save() {

        System.out.println("save");

    }

}
 

여기서 JDK Proxy는

UserService (Interface)

        ▲

        │

JDK Proxy

        │

        ▼

UserServiceImpl
 

처럼 동작한다.


즉,

Proxy도

인터페이스를 구현한다.


호출 흐름

 
UserService service = proxy;

service.save();
 

실제로는

Client

↓

JDK Proxy

↓

InvocationHandler.invoke()

↓

Real Object.save()
 

순서로 실행된다.


핵심은 인터페이스

JDK Proxy는

인터페이스를 구현하는 방식으로 Proxy를 만든다.

그래서

인터페이스가 반드시 필요하다.


인터페이스가 없다면?

 
@Service
public class UserService {

    public void save() {

    }

}
 

구현할 Interface가 없다.


이 경우

JDK Proxy는 사용할 수 없다.


CGLIB 등장

CGLIB는

인터페이스가 아니라

상속(Inheritance) 을 이용한다.


원본 클래스

 
public class UserService {

    public void save() {

        System.out.println("save");

    }

}
 

CGLIB

UserService

        ▲

        │ extends

CGLIB Proxy
 

즉,

Proxy가

UserService를 상속받는다.


호출 흐름

 
userService.save();
 

Client

↓

CGLIB Proxy.save()

↓

Transaction

↓

super.save()
 

상속을 이용해서

기존 메서드 앞뒤에 기능을 추가한다.


그림으로 비교

JDK Proxy

Interface

        ▲

        │ implements

Proxy

        │

        ▼

Real Object
 

CGLIB

Proxy

        │ extends

        ▼

Real Class
 

왜 Spring은 두 가지를 모두 지원할까?

Spring은 오래전부터 존재했다.


초기 Java 개발은

 
interface UserService

class UserServiceImpl
 

구조가 매우 일반적이었다.


그래서

JDK Proxy만으로도 충분했다.


하지만

최근에는

 
@Service
public class UserService {

}
 

처럼

인터페이스 없이 클래스를 직접 사용하는 경우가 매우 많아졌다.


그래서

CGLIB 지원이 필수가 되었다.


Spring Boot의 기본값

Spring Framework는

원래

인터페이스 있음

↓

JDK Proxy

------------

인터페이스 없음

↓

CGLIB
 

전략을 사용했다.


하지만

Spring Boot 2.x 이후부터는

기본적으로

CGLIB Proxy
 

를 사용하는 방향으로 변경되었다.


이유는

프로그래밍 모델을 단순하게 만들기 위해서이다.


final이 왜 문제일까?

다음 코드를 보자.

 
public final class UserService {

}
 

CGLIB는

상속으로 Proxy를 만든다.


그런데

final

↓

상속 금지
 

이다.


즉,

CGLIB Proxy

↓

extends UserService

↓

컴파일 불가
 

그래서

final 클래스는

CGLIB Proxy를 만들 수 없다.


final 메서드도 문제

 
public class UserService {

    public final void save() {

    }

}
 

CGLIB는

메서드를 Override해서

기능을 추가한다.


그런데

final

↓

Override 금지
 

이다.


결국

Transaction

Log

Cache
 

를 추가할 수 없다.


그래서 @Transactional이 안 되는 경우가 있다.

 
@Transactional
public final void save() {

}
 

CGLIB

Override 불가

Proxy 적용 불가

Transaction 동작 안 함


실무에서 의외로 자주 발생하는 문제이다.


성능 차이는?

예전에는

JDK Proxy

>

CGLIB
 

라고 알려져 있었다.


하지만

최근 JVM에서는

거의 의미 없는 수준이다.


실무에서는

성능

X

사용 가능 여부

O
 

가 더 중요하다.


Proxy인지 확인하는 방법

디버깅할 때

 
System.out.println(
        userService.getClass()
);
 

출력

JDK Proxy

com.sun.proxy.$Proxy123
 

CGLIB

UserService$$SpringCGLIB$$0
 

이렇게

실제 Proxy 타입을 확인할 수 있다.


Spring이 내부에서 선택하는 과정

Bean 생성

↓

AOP 필요 확인

↓

Interface 존재?

        │

   YES      NO

    │        │

JDK Proxy  CGLIB

    │        │

Proxy 생성

↓

Container 저장
 

실무에서 인터페이스를 꼭 만들어야 할까?

예전에는

 
UserService

↓

UserServiceImpl
 

구조를 많이 사용했다.


하지만 최근에는

 
@Service
public class UserService {

}
 

처럼

구현 클래스만 사용하는 프로젝트도 매우 많다.


Spring Boot가 기본적으로 CGLIB를 사용하는 이유도

이러한 개발 스타일을 지원하기 위해서이다.


지금까지의 흐름

Reflection

↓

Bean 생성

↓

DI

↓

BeanPostProcessor

↓

Dynamic Proxy

↓

JDK Proxy 또는 CGLIB 선택

↓

Container 저장

↓

Client 호출

↓

Transaction / Log / Cache 수행
 

면접 포인트

JDK Dynamic Proxy란?

인터페이스를 구현하는 방식으로 Proxy를 생성하는 JDK 기본 기능이다.


CGLIB란?

상속(extends)을 이용하여 Proxy를 생성하는 라이브러리이다.


Spring은 언제 JDK Proxy를 사용하고 언제 CGLIB를 사용하는가?

전통적으로는

  • 인터페이스가 있으면 JDK Proxy
  • 인터페이스가 없으면 CGLIB

를 사용했다.

다만 Spring Boot에서는 클래스 기반 개발을 지원하기 위해 CGLIB를 기본 Proxy 방식으로 사용하는 경우가 많다.


왜 final 클래스와 final 메서드가 문제가 되는가?

CGLIB는 상속과 메서드 Override를 이용하여 Proxy를 생성하기 때문에,

  • final 클래스는 상속할 수 없고
  • final 메서드는 Override할 수 없어

Proxy 기반 기능(@Transactional, AOP 등)을 적용할 수 없다.


다음 글

다음 글은 Java Proxy 기반 트랜잭션 처리이다.

여기서는 지금까지 배운

Reflection
        ↓
Bean 생성
        ↓
BeanPostProcessor
        ↓
Proxy 생성
        ↓
ThreadLocal
        ↓
Transaction
 

을 하나의 요청 흐름으로 연결하여,

@Transactional 한 줄이 내부에서 어떤 과정을 거쳐 Commit/Rollback까지 수행되는지를 그림과 코드로 깊이 있게 설명하면 Spring 챕터의 이해도가 크게 올라간다.

반응형

'language > java' 카테고리의 다른 글

ThreadLocal과 TransactionSynchronizationManager  (0) 2026.06.24
Java Proxy 기반 트랜잭션 처리  (0) 2026.06.17
Dynamic Proxy와 Spring AOP  (0) 2026.06.17
객체 생명주기와 Spring Bean  (0) 2026.06.17
Bean 생성 과정  (0) 2026.06.17

댓글