2014년 2월 18일 화요일

Spring Framework 의 Dynamic Class 파일 저장

스프링 프레임웍에서 @Transactional 이나 @Cachable 과 같은 Annotation을 사용하여 개발하는 경우에 BeanFactory는 동적으로 관련 코드가 삽입된 Bean 클래스를 생성한다. 동적으로 코드를 생성할 때 사용되는 기술은 Java의 Dynamic Proxy 와 CGLIB이며, 별도의 설정이 없는 경우에 Dynamic Proxy를 디폴트로 사용한다.
이처럼 동적으로 생성되는 클래스는 기존 빈에 스프링의 기능들이 추가된 클래스이기 때문에 커스터마이징 할 수는 없다. 하지만 스프링이 어떻게 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 에서 생성한 클래스들은 몇개의 디컴파일 툴로 확인해 본 결과, 디컴파일이 깔끔하게 되지 않는 것 같다.

Resources

http://javahowto.blogspot.kr/2011/12/java-dynamic-proxy-example.html