본문 바로가기

안드로이드

Android & Java - Dynamic Proxy

반응형

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에서 지정한 로직대로 값을 내려주는 것을 확인할 수 있다.

반응형