동적 프록시는 런타임에 특정 인터페이스를 구현한 프록시 객체를 동적으로 만들어서 사용하는 방식이다. Java의 Proxy 클래스를 사용하거나, Spring에서는 AOP(Aspect-Oriented Programming)를 구현할 때 동적 프록시를 사용한다.
CGLIB는 인터페이스가 없는 클래스에도 프록시를 적용할 수 있게 도와주는 라이브러리이다. 동적 프록시가 인터페이스가 필요하다면, CGLIB는 클래스를 상속받아 프록시 객체를 생성한다.
AspectJ는 AOP의 한 종류로, 컴파일 시점에 프록시를 생성하는 방식이다. Spring AOP가 동적 프록시를 주로 사용하지만, AspectJ는 애스펙트(Aspect)를 코드에 직접 주입하는 방식이다.
Spring AOP는 동적 프록시와 CGLIB을 사용해 런타임에 Aspect(공통 기능)를 적용하는 방식이다. 인터페이스가 있는 경우는 동적 프록시를 사용하고, 없는 경우는 CGLIB을 사용해 프록시를 생성한다.
방식 | 사용법 | 적용대상 | 프록시 생성 시점 | 장단점 |
---|---|---|---|---|
동적 프록시 | JDK 동적 프록시를 사용 | 인터페이스 | 런타임 | 간단하지만 인터페이스가 필수, 구체 클래스에는 적용 불가능 |
CGLIB | 바이트코드 조작을 통한 클래스 상속 | 구체 클래스 | 런타임 | 인터페이스가 필요 없음, final 메서드에 적용 불가능 |
AspectJ | 바이트코드 조작을 통한 AOP 적용 | 클래스, 필드, 메서드 등 | 컴파일, 로딩, 런타임 | 강력한 기능 제공(메서드 호출 외에도 다양한 지점에 적용이 가능) |
내부 호출 문제란 Spring AOP에서 발생하는 한계 중 하나로, 프록시 기반 AOP가 동작하는 방식에서 기인한다. 같은 클래스 내의 메서드 간 호출이 있을 때 AOP가 적용되지 않는 상황을 말한다.
왜 이런 문제가 발생할까?
이러한 문제를 해결하려면 self-invocation(자기 호출)을 피해야 한다. 보통은 내부 호출을 외부에서 호출하도록 구조를 변경하거나, AOP 기능을 강제로 적용할 수 있는 방법을 사용한다고 한다.
내부 호출을 피하고, 외부에서 해당 메서드를 호출하게 설계한다.
메서드를 각기 다른 Bean으로 분리한다.
프록시 방식이 아닌 바이트코드를 수정하는 방식의 AOP로, 내부 호출에서도 제대로 동작한다.
동적 프록시는 간단하고 효과적이지만 인터페이스가 필요한 반면, CGLIB는 더 많은 유연성을 제공해 인터페이스가 없는 구체 클래스에도 적용할 수 있다.
AspectJ는 이러한 모든 한계를 넘어선 AOP 구현 방식으로, 컴파일 시점부터 AOP 기능을 적용할 수 있어 더 많은 제어와 성능 최적화를 가능하게 한다.
내부 호출 문제는 AOP 적용 시 자주 발생하는 문제점으로 프록시 객체를 통해 AOP가 적용되기 때문에 자기 자신을 호출하는 경우 AOP가 적용되지 않는 한계가 발생한다. 이를 해결하기 위해 설계 변경이나 AspectJ 같은 더 강력한 AOP 구현 방식을 고려해야 한다.
Spring AOP는 런타임 시점에 가벼운 로깅, 트랜잭션 관리 등과 같은 부가적인 기능을 쉽게 적용할 수 있도록 해주지만, 그만큼 사용 환경에 따라 적절한 프록시 방식(CGLIB 또는 동적 프록시)을 선택해야 한다.
AspectJ는 더 강력하고 다양한 기능을 제공하지만, 사용 설정이 복잡하다는 단점이 있다.
따라서 Spring AOP와 프록시를 사용할 때는 다음을 고려하는 것이 중요하다.
AOP는 코드의 간결성을 유지하면서도 중복되는 부가 기능을 추상화하여 재사용할 수 있도록 해주지만, 요구 사항에 맞는 적절한 방식을 선택하는 것이 핵심이 될 것이다.