[Spring] 인터페이스냐 클래스냐? 다이나믹 프록시와 CGLIB의 올바른 선택 방법
[배경]
JDK 동적 프록시와 CGLIB 프록시가 나오게 된 배경은 Java 애플리케이션에서 런타임 시 객체의 동작을 동적으로 변경하거나 확장할 필요성과 관련이 있습니다. 이러한 프록시 기법은 특히 "관점 지향 프로그래밍(AOP)" 와 데코레이터 패턴을 지원하는 데 중요한 역할을 하며, 더 나은 설계와 유지보수성을 가능하게 합니다.다이나믹 프록시는 자바 표준 라이브러리를 이용하여 인터페이스 기반의 프록시를 생성하는 반면, CGLIB는 바이트코드 조작을 통해 클래스 기반의 프록시를 생성합니다. 이 두 가지 방법론의 특징과 장단점을 비교하고, 실무에서의 올바른 선택 방법을 제시합니다.
[내용]
먼저 프록시란?
프록시(Proxy)란 대리의 의미를 갖고 있으며 서버와 클라이언트 사이에서 통신을 대신 처리해주는 역할을 합니다. 프록시는 객체안에서의 개념(디자인 패턴), 웹 서버에서의 개념 (포워드 프록시, 리버스 프록시) 등으로 사용되며 근본적인 역할은 같습니다. 객체안에서의 프록시에 대해서 알아보도록 하겠습니다.
위는 클라이언트에서 서버를 직접 호출하는 간단한 예시입니다.
클라이언트가 요청한 결과를 서버에 직접 요청하는 것이 아닌 어떤 대리자를 통해 간접적으로 호출하는 예시입니다. 예를 들어 내가 직접 마트에서 장을 볼 수도 있지만, 누군가에게 대신 장을 봐달라고 부탁할 수도 있는데 여기서 장을 보는 대리자를 프록시(proxy)라 합니다.
프록시 객체
프록시 객체는 서버와 클라이언트 사이에서 크게 2가지의 역할을 수행하고 있습니다.
GoF 디자인 패턴에는 이 둘을 의도(intent)에 따라 프록시 패턴, 데코레이터 패턴으로 구분됩니다.
- 접근 제어 (권한, 캐싱, 지연 로딩 등) -> 프록시 패턴
- 부가기능 (추가 기능 수행, 값을 변형, 로그 등) -> 데코레이터 패턴
프록시 패턴 적용 전
프록시 패턴 적용 후
데코레이터 패턴 적용 전
데코레이터 패턴 적용 후
그림으로 보면 상당히 유사해 보이지만 생성의 목적에서 본다면 차이가 있습니다. 데코레이터 패턴은 객체의 기능을 동적으로 확장하고자 할 때 사용됩니다. 객체에 직접 추가하지 않고도 기능을 다양하게 조합할 수 있습니다. 프록시 패턴은 객체에 대한 접근을 제어하거나 특정 상황에서 객체의 동작을 조정하고자 할 때 사용됩니다. 객체의 실제 사용을 제어하고 성능을 최적화하거나, 보안 및 접근 제어를 구현하는 데 유용합니다.
1. 다이나믹 프록시 (Java Dynamic Proxy)
정의: Java의 다이나믹 프록시는 java.lang.reflect.Proxy 클래스를 사용하여 인터페이스 기반의 프록시 객체를 동적으로 생성하는 기술입니다. 이 방식에서는 프록시가 구현할 인터페이스를 지정하고, InvocationHandler 인터페이스를 구현하여 메서드 호출을 처리합니다. InvocationHandler의 invoke() 메서드에서 실제 메서드 호출 전후의 로직을 추가할 수 있습니다.
장점:
- 간단하고 가벼움: 다이나믹 프록시는 자바 표준 라이브러리에서 제공되므로 추가적인 라이브러리를 사용할 필요가 없고, 비교적 간단하게 사용할 수 있습니다. 여기서 가볍다는 의미는 자원의 소비가 적고 설치, 설정이 간단하며 빠른실행속도를 의미합니다.
- 인터페이스 기반의 프록시 생성: 특정 인터페이스를 구현하는 클래스들에 대해 매우 유연하게 적용할 수 있으며, 런타임 시점에 새로운 프록시 객체를 동적으로 생성할 수 있습니다.
- 안전성: Java의 리플렉션 API와 라이브러리를 사용하기 때문에 코드의 안정성과 호환성이 보장됩니다.
단점:
- 인터페이스 필수: 다이나믹 프록시는 반드시 인터페이스 기반으로 동작해야 합니다. 즉, 프록시를 생성하려는 객체가 인터페이스를 구현하고 있어야만 합니다.
- 성능 오버헤드: 런타임 시점의 리플렉션과 메서드 호출의 추가 레이어로 인해 성능 오버헤드가 발생할 수 있습니다.
- 기능 제한: 클래스 자체를 프록시로 생성할 수 없으므로, 메서드 오버라이딩 같은 더 복잡한 기능에는 제한적입니다.
2. CGLIB (Code Generation Library)
정의: CGLIB는 클래스 수준에서 프록시를 생성할 수 있는 라이브러리입니다. CGLIB는 바이트코드 조작 기술을 사용하여 런타임에 새로운 서브 클래스를 생성하고, 메서드를 오버라이드하여 프록시를 생성합니다. 이 방식은 구체적인 클래스에 대해서도 프록시를 만들 수 있습니다.
장점:
- 클래스 기반 프록시 생성: CGLIB는 인터페이스를 구현하지 않는 클래스에 대해서도 프록시를 생성할 수 있습니다. 따라서 구체적인 클래스를 대상으로 할 때 매우 유용합니다.
- 성능: 다이나믹 프록시와 비교해 상대적으로 더 빠른 성능을 제공할 수 있습니다. 바이트코드 조작을 통해 메서드 호출을 직접 오버라이드하기 때문에 리플렉션 기반 호출보다 빠릅니다.
- 더 다양한 기능: 인터페이스의 유무에 관계없이 거의 모든 클래스에 대해 프록시를 생성할 수 있으며, 구체적인 클래스의 메서드 오버라이딩도 가능합니다.
단점:
- 복잡성: CGLIB는 자바의 표준 라이브러리가 아니기 때문에 별도의 라이브러리를 추가해야 하며, 사용법이 상대적으로 복잡할 수 있습니다.
- Final 클래스 제한: CGLIB는 바이트코드 조작을 통해 서브 클래스를 생성하는 방식이므로, final로 선언된 클래스나 메서드에 대해서는 프록시를 생성할 수 없습니다.
- 메모리 오버헤드: 바이트코드 조작 및 클래스 로더 사용으로 인해 메모리 사용량이 늘어날 수 있으며, 큰 규모의 애플리케이션에서는 메모리 관리에 주의가 필요합니다.
3. Weaving
Weaving 이란 Aspect를 실제 코드에 결합하는 과정입니다. Weaving은 컴파일 타임, 클래스 로드 타임, 또는 런타임에 수행될 수 있습니다.
- Weaving의 유형
- Compile-time Weaving:
- 소스 코드가 컴파일될 때, Aspect를 코드에 결합합니다.
- 이를 통해 생성된 바이트코드는 이미 Aspect가 적용된 상태로 존재합니다.
- AspectJ 같은 AOP 프레임워크에서 자주 사용됩니다.
- Load-time Weaving:
- 클래스 로더가 클래스를 메모리에 로드할 때 Aspect를 결합합니다.
- JVM의 클래스로더를 활용해 Weaving이 이루어집니다.
- 이 방식은 런타임 성능에 영향을 덜 주면서도 유연한 AOP 적용이 가능합니다.
- Runtime Weaving:
- 프로그램 실행 중, 동적으로 Aspect를 결합합니다.
- 프록시 패턴을 활용하여 런타임 시 Join Point에서 실제 비즈니스 로직을 실행하기 전에 Advice가 실행됩니다.
- Spring AOP는 주로 이 방법을 사용합니다.
요약
- 다이나믹 프록시는 인터페이스를 기반으로 동적으로 프록시 객체를 생성하는 데 유리하며, 간단한 사용성과 안정성을 제공합니다. 하지만 인터페이스가 필요하고 성능 오버헤드가 있을 수 있습니다.
- CGLIB는 클래스 수준에서 프록시를 생성할 수 있어 구체적인 클래스에도 적용 가능하며 성능이 더 좋을 수 있지만, final 제한이 있고 추가 라이브러리 의존성과 메모리 오버헤드가 발생할 수 있습니다.
[결론]
다이나믹 프록시와 CGLIB은 프록시 기반 디자인 패턴을 구현하는 데 필수적인 도구입니다. 이러한 프록시 기술을 활용하면 코드의 재사용성을 높이고, 복잡한 비즈니스 로직을 단순화할 수 있습니다. 다이나믹 프록시는 성능이 좋고 메모리 사용량이 적지만 인터페이스 기반이라는 제한이 있고, CGLIB은 좀 더 많은 메모리를 사용하지만 구체적인 클래스를 프록시할 수 있어 더 높은 유연성을 제공합니다. 따라서 애플리케이션의 성능 요구사항과 설계 구조에 따라 적절한 기술을 선택하는 것이 좋습니다.
[출처 및 참조]
- 토비의 스프링 3.1
- https://sasca37.tistory.com/278
- https://gmoon92.github.io/spring/aop/2019/04/20/jdk-dynamic-proxy-and-cglib.html
- https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EB%88%84%EA%B5%AC%EB%82%98-%EC%89%BD%EA%B2%8C-%EB%B0%B0%EC%9A%B0%EB%8A%94-Dynamic-Proxy-%EB%8B%A4%EB%A3%A8%EA%B8%B0
- https://kghworks.tistory.com/161