Spring Framework을 사용하면서, IoC 기반의 코딩을 하기 위해 Interface를 앞단에 두고 이를 구현하는 방식으로 구현한 소스를 좀 뒤집었다.
추가적인 이종의 구현의 필요가 없어 개발의 편의성을 위해 앞단의 Interface를 모두 제거하는 식으로 변경을 하였다. Interface를 둘 경우 Eclipse 소스에서 Ctrl 키를 눌러 참조 구현부로 이동하는 기능이 Interface로 연결되면서 개발상 불편한 것도 변경의 이유가 되었다.
문제는 앞단의 Interface를 지우고 직접 Class를 올리게 되니 Spring에서 Bean을 생성하고 Proxy AOP를 거는 중 다음과 같은 오류가 발생하였다.
Initialization of bean failed; nested exception is org.springframework.aop.framework.AopConfigException: Cannot proxy target class because CGLIB2 is not available. Add CGLIB to the class path or specify proxy interfaces.
인터넷상에 나와있는 “JDK Dynamic Proxy와 CGLIB Proxy에 대한 이해“를 보면 2가지 종류의 Proxy가 이용되는 것으로 확인된다. 아마도 이전에 Interface 기반에서 JDK Dynamic Proxy가 사용된 것으로 보인다.
직접 Class에 대한 Proxy를 이용하기 위해서 Spring Framework의 “lib/cglib” 위치에 있는 “cglib-nodep-2.1_3.jar” 라이브러리를 Class Path에 추가해 주면 된다.
Proxy에 대하여..
Spring AOP는 두가지 Type의 Proxy를 지원하고 있다. 그 첫번째는 JDK의 Proxy 기능을 이용하는 것이고, 두번째 방법은 CGLIB의 Enhancer 클래스를 이용하는 것이다. 이 두가지 Proxy의 차이점을 이해하고 사용하는 Spring AOP를 제대로 사용하는 것이 될 것이다.
Proxy의 핵심적인 기능은 원하는 메써드가 호출(Invocation)될 때 이 메써드를 가로채어 우리가 원하는 특정 기능들을 추가할 수 있도록 지원하는 것이다.
JDK Proxy
JDK Proxy는 인터페이스에 대한 Proxy만을 지원하며, 클래스에 대한 Proxy를 지원할 수 없다는 것이 큰 단점이다. 자바에서 인터페이스를 사용하는 것이 좋은 설계임에는 틀림없지만 모든 애플리케이션에서 인터페이스를 사용한다는 제약을 두는 것은 좋지 않다. 특히 국내와 같이 인터페이스를 많이 사용하지 않고, 이에 대한 인식이 없는 상태에서 Proxy 기능을 사용하기 위하여 인터페이스를 사용해야 한다고 강요할 경우 개발자들에게 상당한 반발을 가져올 수 밖에 없다.
또한 이미 구현되어 있는 애플리케이션에 Proxy기능을 추가할 때 JDK Proxy를 사용한다면 클래스로 구현되어 있는 소스에서 인터페이스를 추출한 다음 Proxy를 적용할 수 밖에 없다는 단점이 있다. 이와 같이 클래스에 Proxy를 적용하고자 할 경우에는 CGLIB Proxy만을 사용해야 한다.
JDK Proxy가 가지는 또 하나의 단점은 Target 클래스에 Proxy를 적용할 때 PointCut에 정보에 따라 Advice되는 메써드와 그렇지 않은 메써드가 존재한다. 그러나 JDK Proxy를 사용할 경우 Target 클래스에 대한 모든 메써드 호출이 일단 JVM에 Intercept한 다음 Advice의 invoke 메써드를 호출하게 된다. 그 후에 이 메써드가 Advice되는 메써드인지 그렇지 않은지를 판단하게 된다. 이 과정에서 JVM에 의하여 Intercept한 다음 invoke 메써드를 호출할 때 JDK의 reflection을 이용하여 호출하게 되는것이다. 이는 Proxy를 사용할 때 실행속도를 상당히 저하시키는 원인이 된다.
Spring 프레임워크에서 JDK Proxy를 사용하고자 한다면 ProxyFactory의 setProxyInterfaces() 메써드에 사용할 인터페이스를 전달하면 JDK Proxy를 이용할 수 있다. 그러나 이 메써드를 통하여 인터페이스를 전달하지 않을 경우 기본적인 Proxy는 CGLIB Proxy가 된다.
CGLIB Proxy
CGLIB Proxy 또한 JDK Proxy처럼 Runtime시에 Target 메써드가 호출될 때 해당 메써드의 Advice적용 여부를 결정하게 된다. 그러나 CGLIB는 메써드가 처음 호출 되었을때 동적으로 bytecode를 생성하여 이후 호출에서는 재사용하는 과정을 거치게 된다. 이 같은 과정을 통하여 두번째 호출이후부터는 실행속도의 향상을 가져올 수 있는 방법을 사용하고 있다.
또한 CGLIB Proxy는 클래스에 대한 Proxy가 가능하다.