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);
}





덧글