이처럼 동적으로 생성되는 클래스는 기존 빈에 스프링의 기능들이 추가된 클래스이기 때문에 커스터마이징 할 수는 없다. 하지만 스프링이 어떻게 Proxy 클래스를 만드는 지 확인하기 위해서 클래스 파일을 로컬 파일 시스템에 저장하고 디컴파일 하여 소스 코드를 살펴보고 싶었다.
엄밀히 말하면 이번 포스트는 스프링의 기능에 관한 이야기가 아니라 Java Dynamic Proxy와 CGLIB에서 동적으로 생성되는 클래스를 로컬 파일 시스템에 저장하는 옵션에 관한 설명이다.
Java Dynamic Proxy
Java 실행 옵션으로 아래의 옵션을 추가해주면 된다.
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
주의 해야 할 점은 JVM 의 실행 디렉터리를 기준으로 Proxy 클래스를 저장하는데, 해당 클래스의 패키지에 해당하는 폴더를 미리 만들어 주어야 한다는 것이다. 실행 시 디렉터리가 없을 때 FileNotFoundException이 발생한다.
org/springframework/core/ 폴더에 $Proxy0 클래스가 생성된다.
com/sun/proxy/ 폴더에 $Proxy1, $Proxy2 ... 클래스들이 생성된다.
그렇기 때문에 이 옵션을 사용하기 위해서는 Java 실행 디렉터리에 위 두 폴더를 미리 생성해 두는게 좋다.
동적으로 생성되는 클래스의 소스 코드를 확인하고자 한다면 jad 같은 디컴파일 도구를 사용해야 한다.
아래 소스 코드는 디컴파일된 소스 코드의 일부분이다.
package com.sun.proxy;
import com.archnal.springcache.service.CacheExampleService;
import java.lang.reflect.*;
import org.aopalliance.aop.Advice;
import org.springframework.aop.*;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.AopConfigException;
public final class $Proxy13 extends Proxy
implements CacheExampleService, SpringProxy, Advised
{
public $Proxy13(InvocationHandler invocationhandler)
{
super(invocationhandler);
}
...
}
이 $Proxy13 클래스의 advisor 를 확인하고 싶으면 아래와 같이 출력해 볼 수 있다.
if(beanClassName.equals("com.sun.proxy.$Proxy13")) {
Advised advised = (Advised) bean;
System.out.println("target class: " + advised.getTargetClass());
for(Advisor advisor: advised.getAdvisors()) {
System.out.println("advisor: " + advisor);
}
}
실행 결과 아래와 같이 출력된다.
target class: class com.archnal.springcache.service.CacheExampleServiceImpl
advisor: org.springframework.cache.interceptor.BeanFactoryCacheOperationSourceAdvisor: advice bean 'org.springframework.cache.interceptor.CacheInterceptor#0'
CGLIB
CGLIB에서 동적으로 생성되는 클래스를 로컬 파일 시스템에 저장하기 위해서는 아래와 같이 Java 실행 옵션에 추가해야 한다.
-Dcglib.debugLocation=/Users/nicholas/cglib-debug
클래스들이 저장될 경로를 지정하면 되고, 각 클래스들의 패키지에 해당하는 디렉터리는 자동생성된다.
아래와 같이 스프링 설정을 변경하면 CGLIB를 사용하여 프록시를 만든다.
<bean id="cacheExampleService"
class="com.archnal.springcache.service.CacheExampleServiceImpl">
<aop:scoped-proxy proxy-target-class="true"/>
</bean>
By Setting proxy-target-class="true" you will be using CGLIB2 for your proxies, instead of jdk proxys.
하지만 CGLIB 에서 생성한 클래스들은 몇개의 디컴파일 툴로 확인해 본 결과, 디컴파일이 깔끔하게 되지 않는 것 같다.