DynamicProxy
DynamicProxy
Java 에서 Proxy 인스턴스를 런타임에 만들 수 있는 방법을 제공하는데 이를 DynamicProxy 라 한다. 즉, 애플리케이션 실행 중(runtime)에 인스턴스를 동적으로 만든다는 것이다.
- JDK 동적 프록시를 사용하면 리플렉션을 통해 런타임에 Java 인터페이스 구현을 만들 수 있다.
- JDK 동적 프록시는 인터페이스를 기반으로 프록시를 동적으로 만들어준다. 따라서 인터페이스가 필수이다.
- JDK 동적 프록시에 적용할 로직은 InvocationHandler 인터페이스를 구현하여 작성하면 된다.
Aspect Oriented Programming(AOP) 의 주요 패러다임 중 하나가 트랜잭션 관리, 로깅, 유효성 검사 등과 같은 관심사를 분리하는 것이다. 따라서 AOP 를 많이 사용하는 프레임워크가 Proxy Mechanism 에 의존하는 것은 당연하다.
사용자 정의 프록시(user defined proxy)를 구현하기 위해서는 InvocationHandler 를 구현하면된다.
InvocationHandler:
package java.lang.reflect;
public interface InvocationHandler {
/**
* @params proxy the proxy instance that the method was invoked on(메서드가 호출된 프록시 자신)
* @params method 호출한 메서드
* @params args 메서드를 호출할 때 전달한 인수
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
인터페이스가 꼭 필요하다고했는데 프록시 생성할 때 Concrete Class 를 넣는다면 어떻게 될까?
fun main(args: Array<String>) {
// Create target instance
val targetInstance = TargetClass()
// Create proxy
val proxy = Proxy.newProxyInstance(TargetClass::class.java.classLoader, arrayOf(targetInstance.javaClass), LogExecutionTimeProxy(targetInstance)) as TargetClass
// Invoke the target instance method through the proxy
val result = proxy.action()
println(result)
}
Errors stackTrace:
Exception in thread "main" java.lang.IllegalArgumentException: proxy.TragetClass is not an interface
TargetClass 를 Interface 를 사용해서 구현해보자. 프록시는 Proxy.newProxyInstance() 를 사용해서 생성할 수 있다.
Interface:
interface Target {
}
TargetClass:
class TargetClass: Target {
override fun action() {
println("do action")
}
}
User defined proxy(the one that implements the InvocationHandler interface):
// 사용자 정의 프록시
class LogExecutionTimeProxy(
private val invocationTarget: Any
): InvocationHandler {
override fun invoke(proxy: Any, method: Method, args: Array<out Any>?): Any? {
val startTime = System.nanoTime()
// Invoke the method on the target instance
val result = if (args == null) {
method.invoke(invocationTarget)
} else {
method.invoke(invocationTarget, *args)
}
// Print the execution time
println("Executed method " + method.name + " in "
+ (System.nanoTime() - startTime) + " nanoseconds")
// Return the result to the caller
return result
}
}
method.invoke(invocationTarget) 는 어떤 인스턴스에 있는 메서드를 실행할 것인지를 의미한다. 따라서 인스턴스를 넣어주면 된다.
Main:
fun main(args: Array<String>) {
// Create target instance
val targetInstance = TargetClass()
// Create proxy
val proxy = Proxy.newProxyInstance(
Target::class.java.classLoader,
targetInstance.javaClass.interfaces,
LogExecutionTimeProxy(targetInstance)
) as proxy.Target
// Invoke the target instance method through the proxy
proxy.action()
}
정상적으로 동작하는 것을 알 수 있다. 아래 처럼 람다를 사용할 수도 있다.
Using Lambda:
// Create proxy
val proxy = Proxy.newProxyInstance(
Target::class.java.classLoader,
targetInstance.javaClass.interfaces
) { _, method, args ->
val startTime = System.nanoTime()
// Invoke the method on the target instance
val result = if (args == null) {
method.invoke(targetInstance)
} else {
method.invoke(targetInstance, *args)
}
// Print the execution time
println("Executed method " + method.name + " in " + (System.nanoTime() - startTime) + " nanoseconds")
// Return the result to the caller
result
} as Target
Separation Of Concerns
The mechanism we have just seen is the basis for many Aspect Oriented Programming frameworks.
위에서 TargetClass 가 하나라서 Proxy 의 효과가 별로 없어보이지만 로깅, 검증 등의 로직(공통 관심사)이 여러 메서드들에 대해서 존재한다고 하면 각 메서드마다 공통 관심사를 별도로 구현해야 한다.
따라서 DynamicProxy 를 사용하면 런타임에 공통 관심사 기능을 생성해서 사용할 수 있다.