Spring AOP는 Dynamic Proxy를 사용하여 AOP를 제공합니다 따라 Spring AOP 를 알아보기전에 Dynamic Proxy 를 위한 JDK Dynamic Proxy 와 CGLIB 를 알아보도록 하겠습니다.
JDK Dynamic Proxy는 Reflection를 이용하여 런타임시에 동적으로 만든 프록시 객체를 의미합니다. JDK Dynamic Proxy는 인터페이스를 기반으로 프록시 객체를 생성합니다.
JDK Dynamic Proxy는 프록시 객체 생성를 사용할 때 InvocationHandler 인터페이스를 구현한 구현체를 매핑하여줍니다. InvocationHandler 인터페이스는 invoke 메서드를 오버라이딩하여 프록시 객체가 수행하여할 작업을 위임받습니다.
invoke 메서드는 프록시 객체의 메소드가 호출될 때 호출되어집니다.
InvocationHandler 인터페이스의 장점은 프록시 객체가 수행해야할 작업에 대한 부가기능을 한 곳에 작성함으로서 코드의 중복을 막을 수 있다는 점입니다. 추가로 넘어온 Method 객체를 통하여 프록시 객체의 기능상의 제약을 걸 수도 있습니다.
invoke 메서드 파라미터 설명
proxy
: 생성된 프록시 객체method
: 프록시 객체의 메서드 호출에 대한 정보를 담은 Method 객체args
: 프록시 객체의 메소드 호출에 전달된 인자들반환값 설명
Code Generator Library의 약어로 클래스의 바이트코드를 조작하여 프록시 객체를 생성하여주는 오픈소스 라이브러리입니다. CGLIB의 Enhancer 클래스를 이용하여 프록시 객체를 생성하며, JDK Dyanmic Proxy와는 다르게 기반 인터페이스가 없어도 프록시 객체를 생성할 수 있습니다.
Enhancer 클래스를 통하여 프록시 객체를 생성하기 위해서는 타겟 클래스(SuperClass로 전달)와 JDK Dynamic Proxy 에서의 InvocationHandler 인터페이스와 동일한 역할을 수행하는 MethodInterceptor 인터페이스를 구현한 구현체를 아래와 같이 전달(Callback으로 전달)해줘야합니다.
intercept 메서드는 프록시 객체의 메소드가 호출될 때 호출되어집니다.
invoke 메서드 파라미터 설명
obj
: 원본 객체method
: 원본 객체의 메서드 호출에 대한 정보를 담은 Method 객체args
: 프록시 객체의 메소드 호출에 전달된 인자들proxy
: 원본 객체의 메소드 프록시반환 값 설명
CGLIB는 타겟 클래스를 상속받아 타킷 클래스에 포함된 모든 메소드를 재정의하여 프록시 객체를 생성하여줍니다. 하지만 상속을 기반으로 프록시 객체를 생성하기때문에 상속의 제약사항을 그대로 따르게 된다. final 이나 private와 같이 상속에서 오버라이딩이 불가능한 경우는 프록시 객체를 생성할 수 없습니다.
Enhancer 의존성 추가
디폴트 생성자 필수, 타겟 클래스 생성자 2회 호출
CGLIB는 메서드가 처음 호출될 때 동적으로 타겟 클래스의 바이트 코드를 조작하여 프록시 객체를 생성합니다. 또한 이때 생성된 바이트 코드를 이후 호출에서는 재사용합니다.
JDK Dynamic Proxy와 달리 바이트 코드를 조작하는 방법을 사용한다는 점과, 조작하여 생성된 바이트 코드를 재활용하기때문에 퍼포먼스가 JDK Dynamic Proxy 보다 좋습니다.
Spring은 프록시를 이용하여 기존 프록시의 문제를 해결하기 위하여 런타임 중 동적으로 프록시를 생성하는 Dynamic Proxy 를 사용합니다. Dynamic Proxy 를 구현하기 위한 방법으로는 앞서 살펴본 JDK Dynamic Proxy 와 CGLIB 가 있으며 Spring 의 경우는 두 가지 방법 모두 사용합니다.
AOP의 적용 대상이 인터페이스거나 인터페이스를 구현하고 있는 구현체인지 아닌지에 따라 Spring은 프록시 객체를 JDK Dynamic Proxy 로 생성할지 CGLIB로 생성할지를 결정합니다.
인터페이스이거나 인터페이스를 구현하고 있는 구현체의 경우는 아래의 사진처럼 JDK Dynamic Proxy 를 사용하고 그렇지 않은 경우, 즉 인터페이스가 존재하지 않는 클래스의 경우는 CGLIB를 사용합니다만 앞서 말한 CGLIB는 문제점이 존재하므로 Spring 은 이러한 문제점을 해결하기 위하여 objenesis 라이브러리와 함께 CGLIB를 사용합니다.
이러한 Spring AOP 방식은 동적으로 프록시 객체를 생성하여 구현되므로 런타임 위빙에 해당하며, Spring은 이러한 런타임 위빙뿐만 아니라 컴파일 위빙을 사용하는 AspectJ 라는 AOP 또한 지원합니다.
참고 자료