나누고 싶은 개발 이야기

Data Engineer로서 기록하고 공유하고 싶은 기술들. 책과 함께 이야기합니다.

Language/Java

Dynamic Proxies

devidea 2017. 8. 29. 17:00
Java의 reflection을 사용하면 런타임시에 동적으로 인터페이스를 구현할 수 있다. java.lang.reflect.Proxy를 사용하게 되는데, 그래서 동적 인터페이스 구현을 동적프록시라고 부르는 이유이기도 하다. 동적 프록시는 다양한 용도로 사용할 수 있다. 예를들면, 데이터베이스 연결 및 트랜잭션 관리, 단위 테스트를 위한 동적 모의 객체 및 인터셉트 목적의 AOP와 유사한 메서드 생성 등에 사용한다.

Proxy의 생성
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
                               throws IllegalArgumentException

Proxy.newProxyInstance() 메서드로 Proxy를 생성하는데 3개의 파라미터를 가진다.
  • loader : 프록시 클래스를 정의하는 클래스 로더
  • interfaces : 프록시 클래스가 구현하는 인터페이스 리스트
  • h : 메서드 호출을 처리하는 핸들러
newProxyInstance로 생성된 Proxy Object는 전달한 interface의 메서드들을 포함하고 있다.

실제 사용은 아래 코드로 살펴보자. 


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;


interface TestIF {
    String hello(String name);
}

class TestImpl implements TestIF {

    @Override
    public String hello(String name) {
        return String.format("Hello %s, this is %s", name, this);
    }
}

class TestInvocationHandler implements InvocationHandler {
    private Object testImpl;

    public TestInvocationHandler(Object testImpl) {
        this.testImpl = testImpl;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        if (Object.class == method.getDeclaringClass()) {
            String name = method.getName();

            if ("equals".equals(name)) {
                return proxy == args[0];
            } else if ("hashCode".equals(name)) {
                return System.identityHashCode(proxy);
            } else if ("toString".equals(name)) {
                return proxy.getClass().getName() + "@" +
                        Integer.toHexString(System.identityHashCode(proxy)) + ", with InvocationHandler " + this;
            } else {
                throw new IllegalStateException(String.valueOf(method));
            }
        }
        return method.invoke(testImpl, args);
    }
}

public class ProxyTest {

    public static void main(String[] args) {

        TestIF t = (TestIF) Proxy.newProxyInstance(TestIF.class.getClassLoader(),
                new Class<?>[]{TestIF.class},
                new TestInvocationHandler(new TestImpl()));
        System.out.printf("t.hello(Duke): %s%n", t.hello("Duke"));
        System.out.printf("t.toString(): %s%n", t);
        System.out.printf("t.hashCode(): %H%n", t);
        System.out.printf("t.equals(t): %B%n", t.equals(t));
        System.out.printf("t.equals(new Object()): %B%n", t.equals(new Object()));
        System.out.printf("t.equals(null): %B%n", t.equals(null));
    }
}

TestInvocationHandler의 invoke 메서드를 보면 3개의 파라미터가 있다.  method 파라미터는 메서드 이름, 파라미터 타입, 리턴 타입 등의 정보를 가져올 수 있다. args 파라미터는 proxy 메서드 호출에 전달한 파라미터를 포함한다.

사용 케이스
적어도 다음과 같은 케이스에 사용한다고 알려져 있다.
  • 데이터베이스 연결과 트랜잭션 관리
  • 유닛 테스트를 위한 동적 모의 객체
  • 사용자 정의 팩토리 인터페이스를 위한 DI Conatiner
  • AOP와 같은 메서드 인터셉션

여러 케이스 중에 한가지만 살펴보자.

데이터베이스 연결과 트랜잭션 관리
spring framework에서는 트랜잭션을 시작하고 커밋/ 롤백을 할 수 있는 트랜잭션 Proxy를 가지고 있다. 그 동작을 간단히 기술하면 아래와 같다.
web controller --> proxy.execute(...);
  proxy --> connection.setAutoCommit(false);
  proxy --> realAction.execute();
    realAction does database work
  proxy --> connection.commit();


[참고]
Proxy.newProxyInstance :  http://bit.ly/2xtfHWl


반응형

'Language > Java' 카테고리의 다른 글

[Java8] sorted groupBy  (0) 2019.01.11
[multi thread] java.util.concurrent Part 1  (0) 2019.01.04
[Java8] 람다란 무엇인가?  (0) 2017.08.09
[Java8] CompletableFuture 정리  (0) 2017.08.09
이름 재사용과 관련된 기술  (0) 2016.10.13