이번 글은 Spring 면접에서 가장 많이 나오는 주제 중 하나이며,
Dynamic Proxy를 이해했다면 반드시 이어서 알아야 하는 내용이다.
많은 사람들이
"JDK Proxy는 인터페이스용이고 CGLIB는 클래스용"
정도만 외우고 끝난다.
하지만 왜 그렇게 만들어졌는지 이해하면 절대 잊지 않는다.
JDK Proxy vs CGLIB
둘 다 Proxy를 만드는 기술이지만, 만드는 방법이 완전히 다르다.
먼저 Proxy의 목적을 다시 생각해보자.
우리가 원하는 것은
Client
↓
Proxy
↓
Real Object
이다.
그리고 Proxy에서
로그 출력
트랜잭션 시작
캐시 확인
권한 검사
실제 메서드 실행
commit
같은 공통 기능을 수행한다.
문제는 Proxy를 어떻게 만들 것인가?
Java에는 크게 두 가지 방법이 있다.
1. 인터페이스를 구현하는 방법
(JDK Dynamic Proxy)
----------------------------
2. 클래스를 상속하는 방법
(CGLIB)
1. JDK Dynamic Proxy
인터페이스 기반
예를 들어
public interface UserService {
void save();
}
구현체
public class UserServiceImpl
implements UserService {
@Override
public void save() {
System.out.println("save");
}
}
Proxy는
public class UserServiceProxy
implements UserService {
private UserService target;
}
처럼 만들어진다.
그림으로 보면
UserService (interface)
▲
│
┌───────────────┴───────────────┐
│ │
UserServiceImpl Dynamic Proxy
둘 다 같은 인터페이스를 구현한다.
Client는
UserService service = ...
만 알고 있다.
실제로는
UserService
↓
Dynamic Proxy
↓
Real Object
가 된다.
왜 인터페이스가 꼭 필요할까?
Proxy도
UserService
타입이어야 하기 때문이다.
예를 들어
UserService service =
new DynamicProxy();
가 가능해야 한다.
그래서
Dynamic Proxy도 같은 인터페이스를 구현한다.
장점
JDK 기본 기능
추가 라이브러리 없음
구현이 단순
메모리 사용 적음
단점
인터페이스가 반드시 있어야 한다.
UserService (interface)
↓
UserServiceImpl
이 구조가 아니면 사용할 수 없다.
인터페이스가 없는 경우
public class UserService {
public void save() {
}
}
여기서
implements ?
할 것이 없다.
그래서 JDK Proxy는 사용할 수 없다.
2. CGLIB
이번에는 다른 방식을 사용한다.
상속(Inheritance)
기존 클래스
public class UserService {
public void save() {
System.out.println("save");
}
}
Proxy는 내부적으로
이런 클래스를 만든다.
public class UserServiceProxy
extends UserService {
@Override
public void save() {
before();
super.save();
after();
}
}
그림
UserService
▲
│ extends
│
UserServiceProxy
Client는
UserService service =
new UserServiceProxy();
라고 생각한다.
하지만 실제 객체는 Proxy이다.
왜 CGLIB는 인터페이스가 필요 없을까?
상속을 사용하기 때문이다.
extends UserService
만 가능하면 된다.
실행 흐름
Client
↓
Proxy.save()
↓
before()
↓
super.save()
↓
after()
그런데 문제가 있다.
상속에는 제한이 있다.
final 클래스
public final class UserService {
}
Proxy는
class Proxy
extends UserService
를 만들어야 한다.
하지만
final
↓
상속 금지
이다.
그래서
final class
↓
CGLIB Proxy 생성 불가
이다.
final 메서드도 마찬가지
public class UserService {
public final void save() {
}
}
Proxy는
@Override
public void save()
를 해야 한다.
그런데
final
↓
Override 금지
이다.
그래서
final method
↓
AOP 적용 불가
이다.
이것이 실무에서 매우 중요하다.
예를 들어
@Transactional
public final void save() {
}
라고 작성했다.
많은 사람들이
@Transactional
↓
트랜잭션 적용
이라고 생각한다.
실제로는
CGLIB
↓
Override 불가능
↓
Proxy 생성 불가능
↓
@Transactional 동작 안 함
이다.
Spring은 무엇을 사용할까?
Spring은 상황에 따라 다르다.
Spring 4.x 이전
기본
인터페이스 있음
↓
JDK Proxy
----------------
인터페이스 없음
↓
CGLIB
Spring Boot 2.x
기본값
CGLIB
이다.
설정
spring.aop.proxy-target-class=true
기본값이기 때문이다.
즉
인터페이스가 있어도
CGLIB
↓
클래스 상속 방식
을 사용한다.
왜 CGLIB를 기본으로 바꿨을까?
실무에서는
@Service
public class UserService {
}
처럼
인터페이스 없이 작성하는 경우가 매우 많아졌다.
그리고
JDK Proxy
↓
인터페이스 필수
라는 제약이 있었다.
그래서
Spring Boot
↓
CGLIB 기본
↓
인터페이스 없어도 AOP 가능
으로 변경되었다.
둘을 그림으로 비교
JDK Proxy
UserService(interface)
▲
┌───────────┴───────────┐
│ │
Real Object Dynamic Proxy
CGLIB
UserService
▲
│ extends
│
Proxy
성능 차이는?
예전에는
JDK Proxy가 조금 더 빠르다.
라는 이야기가 많았다.
하지만 현대 JVM에서는
JIT 최적화
MethodHandle
Reflection 개선
등으로 인해
실무에서 체감할 수준의 차이는 거의 없다.
그래서 지금은
유지보수성
적용 가능 여부
Spring 기본 전략
이 더 중요하다.
면접 포인트
JDK Dynamic Proxy
- 인터페이스 기반
- 인터페이스를 구현하여 Proxy 생성
- 인터페이스가 반드시 필요
CGLIB
- 클래스 상속 기반
- 인터페이스 없이 사용 가능
- final 클래스, final 메서드는 Proxy 생성 불가
Spring Boot에서 기본 Proxy는?
Spring Boot 2.x 이후 대부분의 환경에서는 CGLIB 기반 Proxy가 기본이며, 인터페이스가 없어도 AOP를 적용할 수 있다.
여기서 꼭 알고 넘어가야 할 한 가지
많은 사람들이
"AOP가 적용 안 된다 = CGLIB 문제"
라고 생각하는데, 실제 실무에서 더 많이 만나는 문제는 Self Invocation(자기 자신 호출) 이다.
@Service
public class UserService {
public void outer() {
inner(); // @Transactional이 있어도 적용되지 않음
}
@Transactional
public void inner() {
...
}
}
왜냐하면 this.inner() 호출은 Proxy를 거치지 않고 실제 객체를 직접 호출하기 때문이다.
다음 글 추천
가장 자연스럽게 이어지는 주제는 Serialization / Deserialization이다.
Reflection, Annotation, Proxy를 이해한 상태에서 보면,
- Jackson은 왜 기본 생성자가 필요한지
- JSON이 어떻게 객체가 되는지
- Spring MVC가 RequestBody를 어떻게 객체로 만드는지
까지 하나의 흐름으로 연결해서 이해할 수 있다.
'language > java' 카테고리의 다른 글
| AutoBoxing / Unboxing (0) | 2026.06.16 |
|---|---|
| Serialization / Deserialization (0) | 2026.06.16 |
| Dynamic Proxy 원리 (0) | 2026.06.16 |
| Meta Annotation (0) | 2026.06.16 |
| Annotation 동작 원리 (0) | 2026.06.16 |
댓글