2015년 5월 20일 수요일

Java Dynamic Proxy vs CGLib Proxy vs AspectJ Weaving #2

이번 포스트에서는 Proxy 패턴을 구현하는 방법에 대해서 얘기해 보겠습니다.

Proxy 패턴을 구현할 때, 개발자가 직접 패턴을 구현할 수도 있겠지만 이미 만들어져 있는 도구를 잘 사용하는 것도 좋을 것 같습니다.

흔희 많이 사용하는 방법은 Java API에서 제공하는 dynamic proxy를 이용하는 방법과, cglib를 사용해서 바이트 코드를 핸들링해서 만드는 방법이 있습니다.
그리고 위의 두가지 방법과는 조금 다르긴 하지만, 기존 코드를 수정하지 않고 기능을 추가하는 방법인 AOP의 구현체인 AspectJ 라이브러리를 사용하는 방법이 있습니다.

1. Java Dynamic Proxy
Java Dynamic Proxy는 런타임시에 interface를 구현하는 java.lang.reflect.Proxy 클래스의 서브클래스 객체를 생성합니다.
Java Dynamic Proxy는 interface를 구현하는 것이기 때문에, Proxy를 사용하려면 interface 정의를 반드시 해야합니다. 즉 Client에서 사용하려는 inteface를 정의해야 합니다.

Proxy 객체가 어떤일을 할 것인지를 결정하는 것은 java.lang.reflect.InvocationHandler의 구현체에서 합니다.
InvocationHandler에는 한 개의 메쏘드가 정의되어 있습니다.

public Object invoke(Object proxy, Method method, Object[] args)

proxy: 메쏘드 호출이 일어났던 proxy 인스턴스
method: 메쏘드 호출 당시의 interface 정의에 있는 java.lang.reflect.Method의 인스턴스
args: 위 method의 매개변수들

예제를 한번 살펴 보겠습니다.
package dynamic_proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.List;

public class NoOpAddInvocationHandler implements InvocationHandler {
 private final List<E> proxied;
 
 public NoOpAddInvocationHandler(List<E> proxied) {
  this.proxied = proxied;
 }
 
 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  if(method.getName().startsWith("add")) {
   return false;
  }
  
  return method.invoke(proxied, args);
 }

}

위의 예제는 java.util.List에 대한 Proxy를 생성하는 코드입니다. List가 interface라는 것을 주의하시기 바랍니다. List의 메쏘드 중에서 메쏘드 이름이 "add"로 시작하는 메쏘드가 호출되면 무시합니다. 예를 들어, List의 add()나 addAll()을 호출하게 되면, 아무런 일을 하지 않게 됩니다. 그 외의 메쏘드 호출은 method.invoke()를 통해서 target object (여기서는 proxied)의 메쏘드를 그대로 호출합니다.
다음은 테스트 코드입니다.
package dynamic_proxy;

import static org.junit.Assert.assertTrue;

import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;

import org.junit.Before;
import org.junit.Test;

public class NoOpAddInvocationHandlerTest {
 private List<Object> list;
 
 private List<Object> proxy;
 
 @Before
 public void setUp() {
  list = new ArrayList<Object>(0);
 }
 
 @SuppressWarnings("unchecked")
 @Test
 public void testInvoke() {
  
  proxy = (List<Object>) Proxy.newProxyInstance(NoOpAddInvocationHandlerTest.class.getClassLoader(),
    new Class[] {List.class}, new NoOpAddInvocationHandler<Object>(list));
  
  assertTrue(proxy instanceof Proxy);
 }
 
 public void testAdd() {
  proxy.add(new Object());
  
  assertTrue(list.isEmpty());
 }
}

위 테스트 코드에서는 List의 구현체인 ArrayList를 사용했습니다. NoOpAddInvocationHandler의 Proxy를 java.lang.reflect.Proxy를 통해서 생성합니다.
Proxy 객체를 생성할 때, 3개의 매개변수가 필요합니다.

  1. 클래스 로더
  2. 인터페이스 리스트
  3. InvocationHandler 구현체

클래스 로더는 테스트코드의 클래스로더와 동일한 것을 사용합니다.
인터페이스 리스트는 Proxy로 만들려는 모든 interface를 나열합니다. 여기서는  List 하나입니다.
InvocationHandler 구현체는 위에서 작성한 NoOpAddInvocationHandler 객체를 사용합니다.

위의 테스트를 실행하면, testAdd()에서 Object 객체를 추가하였음에도 List의 사이즈가 늘어나지 않았음을 알 수 있습니다.

전체 소스 코드는 https://github.com/jinwooe/dynamic-proxy-cglib-aspectj-example 에서 받을 수 있습니다

참고 사이트
http://java.dzone.com/articles/power-proxies-java
http://denis-zhdanov.blogspot.kr/2009/08/weaving-with-aspectj.html

댓글 없음:

댓글 쓰기