Dynamic Proxy?
간단하게 함수의 실행 전에 특정 인터페이스를 거치도록 만들 수 있는 자바 기능 중 하나이며,
컴파일 타임이 아닌 런타임에 만들 수 있는 프락시이다.
안드로이드에선 대표적으로 Retrofit 이 다이나믹 프록시를 사용하여 구현되어있다.
간단한 예제와 함께 설명하겠다.
Dynamic Proxy 생성과 사용
interface ProxyInterface {
fun doSomething()
}
val MyProxy = Proxy.newProxyInstance(
ProxyInterface::class.java.classLoader,
arrayOf(ProxyInterface::class.java),
InvocationHandler { proxy, method, args ->
doSomeThing
}
) as ProxyInterface
MyProxy.doSomething()
Dynamic Proxy을 생성하고 사용하는 것은 간단합니다.
Proxy.newProxyInstance
함수 아래 3가지 인자를 통해 만들 수 있다.
첫 번째 인자는 클래스 로더가 들어와야 한다.
인터페이스나, 인터페이스의 구현체의 클래스 로더여야 한다.
두 번째 인자는 인터페이스"들"이다.
인자로 포함된 인터페이스들을 모두 포함하는 Dynamic Proxy가 만들어진다.
세 번째 인자는 InvocationHandler이다.
proxy( 자기 자신 ), method( 실행된 함수 정보 ), args( 함수 실행에 사용된 인자들 )를 인자로 받는 함수 하나만 구현하면 된다.
생성된 Dynamic Proxy는 Any타입이다.
두 번째 인자에 포함된 인터페이스들로 타입 변환하여 사용하면 된다.
위의 예제에서 MyProxy의 함수 중에 ProxyInterface에 포함되는 함수를
실행할 때마다 InvocationHandler의 코드가 동작하게 된다.
( toString
, equals
, hashCode
도 포함된다. )
Dynamic Proxy를 생성하고 사용하는 것은 위의 코드로 끝이다.
그럼, 초 간이 버전의 Retrofit을 만들면서 Dynamic Proxy를 사용해보자.
사전 작업
data class Result(val code: Int, val data: Any?)
@kotlin.annotation.Target(AnnotationTarget.FUNCTION)
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
annotation class GET
@kotlin.annotation.Target(AnnotationTarget.FUNCTION)
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
annotation class POST
Retofit에서 사용하는 어노테이션인 GET
, POST
를 만들었고,
결과 값을 대신할 Result
클래스도 선언했다.
인터페이스 선언
interface PostInterface {
@POST
fun postMethod(data: String): Result
}
interface GetInterface {
@GET
fun getMethod(): Result
}
interface ServiceInterface : PostInterface, GetInterface
Proxy.newProxyInstance
에서 인자로 인터페이스"들"을 받기 때문에
Get 만 처리하는 GetInterface
Post 만 처리하는 PostInterface
둘 다 처리하는 ServiceInterface
3가지를 선언해 주었다.
DynamicProxy 생성하기
class ClassCreator {
private val invocationHandler = InvocationHandler { proxy, method, arrayOfAnys ->
method.annotations.getOrNull(0)?.let { annotation ->
return@InvocationHandler when (annotation) {
is GET -> {
Result(200, "SUCCESS GET")
}
is POST -> {
Result(200, arrayOfAnys[0])
}
else -> this
}
}
}
fun <T> create(clazz: Class<T>): ServiceInterface {
return Proxy.newProxyInstance(clazz.classLoader, arrayOf(ServiceInterface::class.java), invocationHandler) as ServiceInterface
}
fun <T> createOnlyGet(clazz: Class<T>): GetInterface {
return Proxy.newProxyInstance(clazz.classLoader, arrayOf(GetInterface::class.java), invocationHandler) as GetInterface
}
fun <T> createGetAndPost(clazz: Class<T>): Any {
return Proxy.newProxyInstance(clazz.classLoader, arrayOf(GetInterface::class.java, PostInterface::class.java), invocationHandler)
}
}
Retofit 클래스와 비슷한 ClassCreator
클래스를 만들었다.
우선,GET
이 어노테이션인 함수가 오면 Result(200, "SUCCESS GET")
를 반환하고POST
이 어노테이션인 함수가 오면 Result(200, arrayOfAnys[0])
를 반환하는
동작을 하는 InvocationHandler를 만들어 주었다.
그리고,
Dynamic Proxy 생성을 위해 create
, createOnlyGet
, createGetAndPost
세 가지 함수를 만들었다.
create
는 ServiceInterface을 포함
createOnlyGet
는 GetInterface을 포함
createGetAndPost
는 GetInterface, PostInterface 둘 다 포함하고 있는 Dynamic Proxy가 만들어진다.
그렇기에 반환할 때 해당 인터페이스 타입으로 캐스팅하여 반환해주고 있다.
예제에서는 실 구현체에서 구현한 함수는 사용하지 않지만,
class MyInvocationHandler(val service: ServiceInterface) : InvocationHandler{
override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any {
//doSomething
val result = method?.invoke(service,args) ?: something
//doSomething
return result
}
}
InvocationHandler
클래스를 상속받아서 위와 같은 형식으로 코드를 작성하면
구현체의 함수 실행 이전 이후 다른 작업을 추가할 수 있다. ( ex. 로그 찍기 )
사용
val myService: ServiceInterface = ClassCreator().create(ServiceInterface::class.java)
println(myService.getMethod())
println(myService.postMethod("Dataaaaaaaaaaaaa"))
val myGetService : GetInterface = ClassCreator().createOnlyGet(ServiceInterface::class.java)
println(myGetService.getMethod())
val myGetAndPostService = ClassCreator().createGetAndPost(ServiceInterface::class.java)
println((myGetAndPostService as GetInterface).getMethod())
println((myGetAndPostService as PostInterface).postMethod("Dataaaaaaaaaaaaa"))
//myService
Result(code=200, data=SUCCESS GET)
Result(code=200, data=Dataaaaaaaaaaaaa)
//myGetService
Result(code=200, data=SUCCESS GET)
//myGetAndPostService
Result(code=200, data=SUCCESS GET)
Result(code=200, data=Dataaaaaaaaaaaaa)
결과를 보면 구현이 안된 ServiceInterface
를 넘겼는데도 Dynamic Proxy에서
InvocationHandler에서 지정한 로직대로 값을 내려주는 것을 확인할 수 있다.
'안드로이드' 카테고리의 다른 글
Android Hilt - WorkManager (0) | 2020.07.03 |
---|---|
Android & Java - Reflection (0) | 2020.07.02 |
Android Espresso #4 - ActivityRules (0) | 2020.06.29 |
Android Espresso #3 - RecyclerView (0) | 2020.06.26 |
Android Espresso #2 - ViewMatcher, ViewAction, ViewAssertion (0) | 2020.06.25 |