[Retrofit2] Dynamic Proxy 를 사용하는 이유

sundays·2023년 3월 22일
0

android

목록 보기
13/18

Retrofit2를 파다보니 Dynamic Proxy라는 기술을 사용해서 인터페이스에 대한 구현 부분을 런타임에 만들어주는 기능을 제공한다고 한다. 이부분은 java의 API 중에 java.lang.reflect 내에서 Proxy#newProxyInstance() 에서 맡아서 처리해주고 있다. 그럼 일단 Proxy가 하는 일과 Retrofit의 Dynamic Proxy의 구현에 대해서 찾아보려고 한다

Proxy


일단 프록시를 검색하면 프록시 서버의 필요성 때문에 프록시 라는 단어가 탄생했다. 이것은 클라이언트가 서버의 중계기인 프록시 서버에서 대리로 통신을 수행하는 것을 바로 프록시라고 한다. 이부분은 모두 알고있는 부분이기도 한데 이 프록시가 가지는 장점이 아주많다

  • 캐시를 이용해서 저장해두어서 원격 서버에 접속하여 데이터를 가져올 필요가 없어 전송시간이 절약된다
  • 불필요한 외부와의 접속이 줄어들어 트래픽이 방지되고 병목현상이 방지되는 효과도 있다

클라이언트 호스트에서는 프록시 서버는 원격 서버처럼 동작하는 것 처럼 보이고 원격 서버의 입장에서 보면 프록시 서버는 마치 클라이언트 처럼 동작되는 것 처럼 보인다.

동작 과정

Proxy Server를 사용하면 www.naver.com으로 메인 페이지를 접속할때 네이버에 접속하는 것이 아니라 프록시 서버에 접속하게 된다. 그리고 네이버 서버는 프록시 서버 IP가 저장되어 클라이언트 처럼 동작되고 있다. 그렇기 때문에 wwww.naver.com에서 내 아이피에 차단을 했다고 해도 Proxy Server를 사용해서 접근 가능 하게 된다

  1. www.naver.com 도메인을 입력
  2. Request가 Cache역할을 하는 Proxy Server로 전달
  3. Proxy Server가 현재 www.naver.com의 페이지를 갖고 있는지 확인한다
  4. 만약 없다면 외부 회선으로 www.naver.com이 있는 서버와 연결하여 페이지를 가져온다
  5. 가지고 있다면 현재 가진 페이지가 최신 버전인지 확인한다
  6. 최신 버전이 아닌경우 새로 갱신된 부분을 가져오고 최신버전 일경우 사용자에게 노출한다

Proxy Pattern

마치 프록시 처럼 클라이언트에서는 실제 실행시키는 클래스가 delegate object 인지 메서드의 반환을 받는지 전혀 모르게 처리하는 것을 말한다. 클라이언트는 이런 Pattern을 적용하기 위해 인터페이스 타입으로 접근하여 사용하게 된다. 이것은 OOP에 다형성의 패턴과도 정확히 일치하는 항목이다.

  • 흐름제어 : 사이즈가 큰 객체(이미지) 가 로딩되기 전 프록시를 통해 참조할 수 있다
  • 보안 : interface 사용으로 실제로 운용되는 다른 객체의 접근제어자들의 접근을 방지
  • Caching : 내부 캐시를 유지하여 데이터가 캐시에 존재하지 않는 경우에만 주체 클래스에서 작업이 실행
  • 지연 초기화 : 주체 클래스의 생성 비용이 비싸면 필요로 할때까지 연기할 수 있다
  • logging : 처리 내용(매개변수 및 메서드 호출) 를 intercepter 하여 로그 한다

Dynamic Proxy

프록시란 타겟을 감싸서 타겟에 대한 요청을 받아주는 Warpper Object를 말한다. 이것을 동적으로 Runtime 내에 생성하는것을 말한다

Retrofit2

Retrofit2에서는 HTTP 요청을 보내는 연결을 생성하기 위해 Retrofit#create()라는 함수를 호출해야 한다.

public <T> T create(final Class<T> service) {
    validateServiceInterface(service);
    return (T)
        Proxy.newProxyInstance(
            service.getClassLoader(),
            new Class<?>[] {service},
            new InvocationHandler() {
              private final Platform platform = Platform.get();
              private final Object[] emptyArgs = new Object[0];

              @Override
              public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)
                  throws Throwable {
                // If the method is a method from Object then defer to normal invocation.
                if (method.getDeclaringClass() == Object.class) {
                  return method.invoke(this, args);
                }
                args = args != null ? args : emptyArgs;
                return platform.isDefaultMethod(method)
                    ? platform.invokeDefaultMethod(method, service, proxy, args)
                    : loadServiceMethod(method).invoke(args);
              }
            });
  }

새로운 request를 생성하고 java.lang.reflect 내에서 Proxy#newProxyInstance()를 선언해줌으로서 런타임 내에 Proxy 인스턴스를 생성할 수 있게 된다.

public static Object newProxyInstance(ClassLoader loader,
									  Class<?>[] interfaces,
									  InvocationHandler h)
	throws IllegalArgumentException
{ ... }

Proxy#newProxyInstance() 의 매개변수를 살펴보자

  • loader : 프록시를 만들 클래스 로더
  • interfaces : 어떤 인터페이스에 대해 프록시를 만들 것인지 명시
  • h : InvocationHandler 인터페이스의 구현체

InvocationHandler

InvocationHandlerinvoke() 하나만 가진 인터페이스 인데 어떤 프록시든 호출됬을때 무조건 호출되는 메서드이다. 그래서 어떤 메서드를 호출 하던간에 invoke()에 전달될것이다.

package java.lang.reflect;

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

결론

Retrofit2에서 Dynamic Proxy를 사용하여 프록시 인터페이스를 직접구현하지 않고 런타임내에 동적으로 생성하게 하기 때문에 사용하고 있다. 일일히 인터페이스를 추가하거나 기능을 확장하지 않고 일괄적으로 적용할 수 있게 할 수 있다.

Reference

profile
develop life

0개의 댓글