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();
[참고]
Dynamic Proxies : http://tutorials.jenkov.com/java-reflection/dynamic-proxies.html#proxy
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 |