CGLIB 를 이용해서 메소드의 로그를 자동으로 남기기 Programmer's notes

예전에 자바의 Proxy 기능을 이용한 AOP 구현이라는 글을 보고 이것 참 괜찮다는 생각이 들었었다.
2001년에 한 프로젝트를 시작하면서 과장님이 요구했던 기능 중 하나가 로깅을 체계적으로 하는 것이 좋겠다며 특정 레벨의 메소드(그 당시는 무상태 세션빈)가 실행할 때 그 파라미터들을 자동으로 남겨주는 공통함수 같은 것을 만들라는 것이었다.
reflect 를 암만 뒤져봐도 그러한 기능의 함수를 만들 길이 없어서 포기하고 그냥 메소드를 선언할 때 수동으로 뿌려주자고 하며 결론을 내고 실제로 그런 코드를 넣지는 않았던 기억이 있다. Proxy 가 JDK 1.3 부터 지원하는 기능이었으니...
그러다가 위의 글을 보고 한 번 써먹어봐야겠다 생각을 하고 최근 작은 프로젝트를 시작하면서 Delegate 에서 이런 기능을 해주도록 해야겠다 마음을 먹고 설계를 했다.

클래스 다이어그램을 그리면서 Command bean 에서 인터페이스로 된 Delegate 를 호출하고 Delegate 의 구현체에서 Session Facade 를 호출하도록 했다.
설계단계가 끝나고 구현을 하다보니 Delegate 를 인터페이스를 만들고 구현체를 따로 만들기가 번거롭게 여겨지고 쓸데없이 복잡하게 하는 것 같아 그냥 일반 클래스로 바꿔서 구현을 했다.
이제 개발이 대충 끝나고 Proxy 로 로깅을 남기는 클래스를 만들려고 봤더니 웬걸~ Proxy 는 인터페이스와 구현체로 되어있는 구조에서만 가능했던 것...
아뿔쏴.. 내가 그래서 인터페이스로 설계를 했었나?

그러나 인터페이스로 다시 바꾸자니 클래스다이어그램도 또 바꿔야 하는 번거로움이...

그래서 떠오른 것이 Hibernate 에서 썼다는 CGLIB.

인테넷에서 검색을 해보니 최범균님이 정리한 CGLIB를 이용한 프록시 객체 만들기라는 글이 검색되어서 이 것을 보며 Delegate 에서 로그를 남기는 클래스를 다음과 같이 만들었다.

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class MethodCallLogInterceptor implements MethodInterceptor{
    private Log log;
    public void setLog(Log log) {
        this.log = log;
    }
   
    public MethodCallLogInterceptor(Log log) {
        this.log = log;
    }
   
    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        if(log == null) {
            this.log = LogFactory.getLog(object.getClass());
        }
        if(log.isInfoEnabled()) {
            log.info(object.getClass().getName()+"."+method.getName()+" invoked.");
        }
        if(log.isDebugEnabled()) {
            if(args != null && args.length > 0) {
                for(int i=0; i< args.length; i++) {
                    try {
                        log.debug("param "+i+"> "+describe(args[i],null,null));
                    } catch(Throwable t) {
                        log.debug("param "+i+" failed toString : "+t.toString());
                    }
                }
            }
        }
        long start = System.currentTimeMillis();
        Object returnValue = null;
        try {
            returnValue = methodProxy.invokeSuper(object, args);
        } catch(Throwable t) {
            log.error(method.getName()+" failed.", t);
            throw t;
        } finally {
            if(log.isInfoEnabled()) {
                log.info(method.getName()+" elapsed "+(System.currentTimeMillis()-start)+" msec.");
            }
        }
        if(log.isDebugEnabled()) {
            try {
                log.debug("return value> "+describe(returnValue,null,null));
            } catch(Throwable t) {
                log.debug("return value failed toString : "+t.toString());
            }
        }
        return returnValue;
    }
   
    private StringBuffer describe(Object obj, StringBuffer sb, Set printedObjects) {
        if(sb == null) {
            sb = new StringBuffer();
        }
        if(printedObjects == null) {
            printedObjects = new HashSet();
        }
        // null 은 패스
        if(obj == null) {
            return sb.append("null");
        }
        // 출력했던 오브젝트도 패스
        if(printedObjects.contains(obj)) {
            return sb.append("printed object");
        } else { // 출력안했던 오브젝트는 재귀호출.
            printedObjects.add(obj);
        }
        try {
            if (obj instanceof Map) { // Map, Collection, Array 는 {} 으로 싸서 출력
                Map map = (Map)obj;
                sb.append(map.getClass().getName()).append("{");
                Iterator iter = map.keySet().iterator();
                String comma = "";
                while (iter.hasNext()) {
                    Object key = (Object)iter.next();
                    sb.append(comma).append(key).append("=");
                    describe(map.get(key), sb, printedObjects);
                    comma=", ";
                }
                sb.append("}");
            } else if (obj instanceof Collection) {
                Collection cols = (Collection)obj;
                sb.append(cols.getClass().getName()).append("{");
                Iterator iter = cols.iterator();
                String comma = "";
                while (iter.hasNext()) {
                    Object value = (Object)iter.next();
                    sb.append(comma);
                    describe(value, sb, printedObjects);
                    comma=", ";
                }
                sb.append("}");
            } else if (obj instanceof Object[]) {
                Object[] array = (Object[])obj;
                sb.append(array.getClass().getName()).append("{");
                String comma = "";
                for(int i=0; i< array.length; i++) {
                    Object value = array[i];
                    sb.append(comma).append(i).append("=");
                    describe(value, sb, printedObjects);
                    comma=", ";
                }
                sb.append("}");
            // java. 으로 시작하는 패키지의 오브젝트는 Object.toString()
            } else if(obj.getClass().getName().startsWith("java.")) {
                sb.append(obj);
            } else { //
                String comma = "";
                Class clazz = obj.getClass();
                Class[] paramTypes = null;
                Object[] params = new Object[0];
                Method getter = null;
                Method[] methods = clazz.getMethods();
                String name = null;
                int numGetter = 0;
                sb.append(obj.getClass().getName()).append("[");
                if(methods != null && methods.length > 0) {
                    for(int i=0; i< methods.length; i++) {
                        getter = methods[i];
                        name = getter.getName();
                        paramTypes = getter.getParameterTypes();

                        //getter 찾기
                        if(name.startsWith("get") &&(paramTypes == null || paramTypes.length == 0) ) {
                            sb.append(comma).append(Character.toLowerCase(name.charAt(3))).append(name.substring(4)).append("=");
                            describe(getter.invoke(obj, params), sb, printedObjects);
                            comma = ", ";
                            numGetter++;
                        }
                    }
                }
                if(numGetter == 0) {
                    sb.append(obj);
                }
                sb.append("]");
            }
        } catch (Exception e) {
            sb.append(e.toString());
        }
        return sb;
    }
}


위의 클래스는 어떻게 쓸까? 다음과 같은 메쏘드를 만들어서 쓰면 된다.



    public static Object getLoggingObject(Class clazz) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(new MethodCallLogInterceptor(LogFactory.getLog(clazz)));
        Object obj = enhancer.create();
        return obj;
    }


그러면 DelegateFactory 에서는 다음과 같이 호출하면 끝



    public static MyDelegate getMyDelegate() {
        return (MyDelegate)getLoggingObject(MyDelegate.class);
    }


트랙백

이 글과 관련된 글 쓰기 (트랙백 보내기)
TrackbackURL : http://sayjava.egloos.com/tb/2774910 [도움말]

덧글

덧글 입력 영역