spring

[Spring] 인터페이스냐 클래스냐? 다이나믹 프록시와 CGLIB의 올바른 선택 방법

사이버나그네 2024. 8. 27. 13:39

[배경]

 JDK 동적 프록시와 CGLIB 프록시가 나오게 된 배경은 Java 애플리케이션에서 런타임 시 객체의 동작을 동적으로 변경하거나 확장할 필요성과 관련이 있습니다. 이러한 프록시 기법은 특히 "관점 지향 프로그래밍(AOP)" 와 데코레이터 패턴을 지원하는 데 중요한 역할을 하며, 더 나은 설계와 유지보수성을 가능하게 합니다.다이나믹 프록시는 자바 표준 라이브러리를 이용하여 인터페이스 기반의 프록시를 생성하는 반면, CGLIB는 바이트코드 조작을 통해 클래스 기반의 프록시를 생성합니다. 이 두 가지 방법론의 특징과 장단점을 비교하고, 실무에서의 올바른 선택 방법을 제시합니다. 

 


 

[내용]

 

 먼저 프록시란?

프록시(Proxy)란 대리의 의미를 갖고 있으며 서버와 클라이언트 사이에서 통신을 대신 처리해주는 역할을 합니다. 프록시는 객체안에서의 개념(디자인 패턴), 웹 서버에서의 개념 (포워드 프록시, 리버스 프록시) 등으로 사용되며 근본적인 역할은 같습니다. 객체안에서의 프록시에 대해서 알아보도록 하겠습니다.

 

출처 : https://lsh2016.tistory.com/m/171

위는 클라이언트에서 서버를 직접 호출하는 간단한 예시입니다.

 

출처 : https://lsh2016.tistory.com/m/171

 

클라이언트가 요청한 결과를 서버에 직접 요청하는 것이 아닌 어떤 대리자를 통해 간접적으로 호출하는 예시입니다. 예를 들어 내가 직접 마트에서 장을 볼 수도 있지만, 누군가에게 대신 장을 봐달라고 부탁할 수도 있는데 여기서 장을 보는 대리자를 프록시(proxy)라 합니다.

 

 

프록시 객체

프록시 객체는 서버와 클라이언트 사이에서 크게 2가지의 역할을 수행하고 있습니다.
GoF 디자인 패턴에는 이 둘을 의도(intent)에 따라 프록시 패턴, 데코레이터 패턴으로 구분됩니다.

  • 접근 제어 (권한, 캐싱, 지연 로딩 등) -> 프록시 패턴
  • 부가기능 (추가 기능 수행, 값을 변형, 로그 등) -> 데코레이터 패턴
     

프록시 패턴 적용 전

출처 : https://mirr-coding.tistory.com/42

 

 

프록시 패턴 적용 후

출처 : https://mirr-coding.tistory.com/42

 

 

 

데코레이터 패턴 적용 전

출처 : https://mirr-coding.tistory.com/42

 

데코레이터 패턴 적용 후

출처 : https://mirr-coding.tistory.com/42

 

그림으로 보면 상당히 유사해 보이지만 생성의 목적에서 본다면 차이가 있습니다. 데코레이터 패턴은 객체의 기능을 동적으로 확장하고자 할 때 사용됩니다. 객체에 직접 추가하지 않고도 기능을 다양하게 조합할 수 있습니다. 프록시 패턴은 객체에 대한 접근을 제어하거나 특정 상황에서 객체의 동작을 조정하고자 할 때 사용됩니다. 객체의 실제 사용을 제어하고 성능을 최적화하거나, 보안 및 접근 제어를 구현하는 데 유용합니다.


 

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의 유형

  1. Compile-time Weaving:
    • 소스 코드가 컴파일될 때, Aspect를 코드에 결합합니다.
    • 이를 통해 생성된 바이트코드는 이미 Aspect가 적용된 상태로 존재합니다.
    • AspectJ 같은 AOP 프레임워크에서 자주 사용됩니다.
  2. Load-time Weaving:
    • 클래스 로더가 클래스를 메모리에 로드할 때 Aspect를 결합합니다.
    • JVM의 클래스로더를 활용해 Weaving이 이루어집니다.
    • 이 방식은 런타임 성능에 영향을 덜 주면서도 유연한 AOP 적용이 가능합니다.
  3. Runtime Weaving:
    • 프로그램 실행 중, 동적으로 Aspect를 결합합니다.
    • 프록시 패턴을 활용하여 런타임 시 Join Point에서 실제 비즈니스 로직을 실행하기 전에 Advice가 실행됩니다.
    • Spring AOP는 주로 이 방법을 사용합니다.

 

 

요약

  • 다이나믹 프록시는 인터페이스를 기반으로 동적으로 프록시 객체를 생성하는 데 유리하며, 간단한 사용성과 안정성을 제공합니다. 하지만 인터페이스가 필요하고 성능 오버헤드가 있을 수 있습니다.
  • CGLIB는 클래스 수준에서 프록시를 생성할 수 있어 구체적인 클래스에도 적용 가능하며 성능이 더 좋을 수 있지만, final 제한이 있고 추가 라이브러리 의존성과 메모리 오버헤드가 발생할 수 있습니다.

 

 

 

 


[결론]

 다이나믹 프록시와 CGLIB은 프록시 기반 디자인 패턴을 구현하는 데 필수적인 도구입니다. 이러한 프록시 기술을 활용하면 코드의 재사용성을 높이고, 복잡한 비즈니스 로직을 단순화할 수 있습니다. 다이나믹 프록시는 성능이 좋고 메모리 사용량이 적지만 인터페이스 기반이라는 제한이 있고, CGLIB은 좀 더 많은 메모리를 사용하지만 구체적인 클래스를 프록시할 수 있어 더 높은 유연성을 제공합니다. 따라서 애플리케이션의 성능 요구사항과 설계 구조에 따라 적절한 기술을 선택하는 것이 좋습니다.

 


[출처 및 참조]