참고 : [CGLib 홈페이지, Java+Web Can Do IT]
CGLib 은 Code Generator Library의 약자로, 런타임시에 동적으로 클래스의 Proxy를 생성해줍니다.
JDKDynamicProxy도 동적으로 Proxy를 생성해주지만, JDKDynamicProxy은 인터페이스 기반의 Proxy만 생성해준다는 점이 CGLib과의 차이점입니다.
CGLib은 자바 개발에서 매우 광범위하게 사용됩니다. 오픈 소스 중에서는 Spring, Hibernate, iBatis 등에서 쓰였습니다. Hibernate에서는 lazy-loading을 위해서 CGLib을 확장 구현해서 코어로 사용하고 있으며, Spring에서는 두 가지 방식 모두 지원하고 있습니다.
위 홈페이에 가면 CGLib library를 받을 수 있습니다.
CGLib은 Enhancer 클래스를 중심으로 Proxy를 만들어 줍니다. 가장 간단히 원본과 동일한 Proxy를 생성하는 코드는 다음과 같습니다.
@Test
public void basicProxyCrete() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(TargetDomain.class);
//Proxy 객체를 원본 객체와 동일한 단순 복사본으로 사용.
enhancer.setCallback(NoOp.INSTANCE);
Object object = enhancer.create();
TargetDomain targetDomain = (TargetDomain) object;
System.out.println(targetDomain.getName());
}
public void basicProxyCrete() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(TargetDomain.class);
//Proxy 객체를 원본 객체와 동일한 단순 복사본으로 사용.
enhancer.setCallback(NoOp.INSTANCE);
Object object = enhancer.create();
TargetDomain targetDomain = (TargetDomain) object;
System.out.println(targetDomain.getName());
}
NoOp 을 사용하면 만들어지는 Proxy는 원본 클래스와 동일하게 생성됩니다. 여기서 Proxy는 단지 호출을 전달해주는 역할만 하게 됩니다.
그럼 이번에는 Interceptor를 추가해 보겠습니다. Interceptor는 net.sf.cglib.proxy.MethodInterceptor 를 구현하면 됩니다.
public class NameInterceptor implements MethodInterceptor {
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy)
throws Throwable {
Object returnValue = methodProxy.invokeSuper(object, args);
return returnValue;
}
}
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy)
throws Throwable {
Object returnValue = methodProxy.invokeSuper(object, args);
return returnValue;
}
}
그럼 이제 구현한 Interceptor를 Enhancer에 추가해야 합니다.
@Test
public void usingMethodInterceptor() {
...
enhancer.setSuperclass(TargetDomain.class);
enhancer.setCallback(new MethodCallbackInterceptor());
Object object = enhancer.create();
...
}
public void usingMethodInterceptor() {
...
enhancer.setSuperclass(TargetDomain.class);
enhancer.setCallback(new MethodCallbackInterceptor());
Object object = enhancer.create();
...
}
setCallback() 메소드를 통해서 구현한 Interceptor를 등록해줍니다. 이 방법을 사용하면 모든 메소드에 하나의 Interceptor가 등록이 됩니다. 특정 메소드에 특화된 Interceptor를 구현하기 위해서 net.sf.cglib.proxy.CallbackFilter를 사용할 수 있습니다.
public class TargetDomainCallbackFilter implements CallbackFilter {
public int accept(Method method) {
if(method.getName().equals("getName")){
return 0;
}else if(method.getName().equals("getDescription")){
return 1;
}
return 0;
}
}
public int accept(Method method) {
if(method.getName().equals("getName")){
return 0;
}else if(method.getName().equals("getDescription")){
return 1;
}
return 0;
}
}
CallbackFilter 인터페이스를 구현하고, 배열로 등록한 CallbackInterceptor에 대한 키값(배열의 인덱스)을 메소드 별로 지정해줍니다. 아래 코드는 배열로 callbackInterceptor를 등록하고, filter를 등록하는 코드입니다.
@Test
public void usingCallbackFilter() {
...
Callback[] callbacks = new Callback[]{
new NameInterceptor(),
new DescriptionInterceptor()
};
enhancer.setCallbacks(callbacks);
enhancer.setCallbackFilter(new TargetDomainCallbackFilter());
...
}
public void usingCallbackFilter() {
...
Callback[] callbacks = new Callback[]{
new NameInterceptor(),
new DescriptionInterceptor()
};
enhancer.setCallbacks(callbacks);
enhancer.setCallbackFilter(new TargetDomainCallbackFilter());
...
}
[개선점]
# 위 코드대로라면 Interceptor 추가시 CallbackFilter에 추가된 내용을 지속적으로 등록해야 합니다. 이 부분을 동적으로 처리하거나, 설정 파일로 처리할 수 있도록 개선해봐야 할 것 같습니다.
# 메소드에 대한 Interceptor는 해결했는데 속성에 대한 접근과 속성의 상태 변화에 따른 Interceptor는 없는지 알아봐야 겠습니다.
# 위 코드대로라면 Interceptor 추가시 CallbackFilter에 추가된 내용을 지속적으로 등록해야 합니다. 이 부분을 동적으로 처리하거나, 설정 파일로 처리할 수 있도록 개선해봐야 할 것 같습니다.
# 메소드에 대한 Interceptor는 해결했는데 속성에 대한 접근과 속성의 상태 변화에 따른 Interceptor는 없는지 알아봐야 겠습니다.

Prev

Rss Feed