이번 글에서는 java.lang.reflect.Proxy, java.lang.reflect.InvocationHandler에 대해 다루겠습니다. 안드로이드 개발에서 Retrofit을 사용하여 Restful API 사용 로직을 구현하려면 아래와 같이 코드를 작성합니다.


<출처 : http://square.github.io/retrofit/>

public interface GitHubService {
    @GET("users/{user}/repos")
    Call<List<Repo>> listRepos(@Path("user") String user);
}
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .build();

GitHubService service = retrofit.create(GitHubService.class);
Call<List<Repo>> repos = service.listRepos("octocat");

위 코드를 보면 개발자는 HTTP 요청을 보내기 위해 Interface를 정의(여기서는 GitHubService)해서 Retrofit.create() 메소드에 전달합니다. 그러면 Retrofit.create()는 GitHubService 인터페이스를 구현한 Proxy class를 만들어 반환하고 개발자는 interface에 선언한 메소드 GitHubService.listRepos() 메소드를 호출하면 "https://api.github.com/users/{user}/repos" 리소스에 GET 요청을 보내고 응답을 List<Repo>로 얻습니다.


Retrofit 사용자는 서버 URL을 설정하고, 어떤 리소스에 무슨 요청을 보낼지(GET, PUT, POST, DELETE 등의 메소드 설정), 응답은 어떤 형식으로 받을지만 정의하면 됩니다. 사용자가 상기처럼 GitHubService와 같은 Interface를 정의하면 Retrofit은 내부적으로 Dynamic Proxy란 기술을 써서 인터페이스에 대한 구현 부분(Proxy class)을 런타임에 만들어줍니다. HTTP 통신을 어떻게 해야하고 에러 처리는 또 어떻게 해야할지 등에 대한 고민은 Proxy 클래스가 처리하고 있습니다.  우선 Dynamic Proxy를 사용하는 샘플을 보면서 Proxy, InvocationHandler가 어떻게 쓰이는지 이해를 해봅시다.

public class TestDynamicProxy1 {
    public static void main(String[] args) {
        TestInterface proxyInstance = (TestInterface)
                Proxy.newProxyInstance(TestInterface.class.getClassLoader(),
                new Class[]{TestInterface.class},
                new CustomInvocationHandler());

        System.out.println(proxyInstance.padSomeSymbolInTail("aaaaa"));
    }

    public interface TestInterface {
        String padSomeSymbolInTail(String text);
    }

    public static class CustomInvocationHandler implements InvocationHandler {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            return args[0].toString() + " FaNtAsTiC TaIlS";
        }
    }
}
위의 코드를 실행 시키면 아래와 같이 표준 출력에 문자열이 출력됩니다.

aaaaa FaNtAsTiC TaIlS


Interface에 정의된 메소드를 호출하면 실제 코드 실행은 InvocationHandler.invoke()에서 수행하는 구조입니다. 사실 이러한 방식은 Spring Data 에서도 제공하는 것으로 DB에서 CRUD 연산을 간편하게 하기 위해 개발자가 아래와 같이 인터페이스를 선언하면 DBMS에 접근하여 커넥션을 맺고 CRUD 동작을 수행하여 원하는 데이터를 반환해 주는 일은 Proxy class가 합니다.


<출처 : Spring in Action 4th edition. p320>

public interface SpitterRepository extends JpaRepository<Spitter, Long> {
    Spitter findByUsername(String username);
}

위와 같이 Dynamic proxy 기술은 자바 라이브러리나 프레임워크에서 자주 사용되고 있습니다. 안드로이드 개발자로서 모바일 환경에서 Dynamic proxy를 사용하는 것에 대해 계속 괜찮은 걸까?라는 의문을 가지고 있어서 다음 포스트에서는  Dynamic proxy 사용으로 어떤 장점과 단점이 있는지 살펴보고 다른 대안이 있으면 그 대안을 구현해 보고 dynamic proxy와 비교해 보겠습니다.


<참고자료>

1. http://docs.oracle.com/javase/1.5.0/docs/guide/reflection/proxy.html

2. https://docs.oracle.com/javase/7/docs/api/java/lang/reflect/Proxy.html#getProxyClass(java.lang.ClassLoader,%20java.lang.Class...)

3. https://docs.oracle.com/javase/7/docs/api/java/lang/reflect/InvocationHandler.html

4. Spring in Action 4th edition by Walls, Craig (2014) Paperback[ISBN-13 : 978-1617291203]


Posted by 제이제이랩
,