Hanbit the Developer

Retrofit2 Implementation(1): Top-Down from Retrofit.create() 본문

Mobile/Android

Retrofit2 Implementation(1): Top-Down from Retrofit.create()

hanbikan 2024. 2. 26. 17:30

배경

지금껏 Retrofit2는 단순히 ‘OkHttp3를 쓰기 편하게 해주는 wrapping library’ 정도로 인지하고 있었습니다. 하지만 구현이 어떻게 되어 있는지 궁금하였고 특히 인터페이스에 어노테이션만 붙였을 뿐인데 이것이 어떻게 구현체가 되는지 궁금했습니다.

오늘은 Retrofit2가 어떻게 구현되어 있는지를 create() 함수에서 시작하여 top-down으로 알아보겠습니다.

Usage

지금까지 Retrofit2를 어떻게 사용했는지를 돌이켜보겠습니다. 먼저 인터페이스로 API가 어떻게 호출되어야 하는지를 정의해주곤 하였습니다.

interface PhotoService {
    @GET("api/v1/albums/{id}/photos")
    suspend fun readPhotoList(
        @Header("Authorization") token: String,
        @Path("id") id: Long
    ): PhotoListResponse
}

다음으로 위 함수를 사용하기 위해서 Retrofit 객체가 필요한데 이것을 정의해야 합니다.

val logger = HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY }
val okHttpClient = OkHttpClient.Builder()
    .addInterceptor(logger)
    .build()
val service = Retrofit.Builder()
    .baseUrl(baseUrl)
    .client(okHttpClient)
    .addConverterFactory(GsonConverterFactory.create())
    .build()
    .create(PhotoService::class.java)

// API 호출
service.readPhotoList(
    // ...
)

Retrofit.java

create()

본격적으로 Retrofit2의 구현을 살펴보겠습니다. 먼저 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);
              }
            });
  }
  1. validate 함수를 통해 전달된 클래스가 인터페이스인지 체크한다.
  2. Proxy.newProxyInstance() 함수를 통해 인터페이스로 정의한 서비스 부분을 해석하여 ‘적절한 행동’을 취한다.

Proxy.newProxyInstance()

newProxyInstance()는 인터페이스의 구현체를 런타임에 동적으로 생성해주는 함수입니다. 해당 함수의 이해를 위해 먼저 간단한 사용 사례를 살펴보겠습니다.

import java.lang.reflect.*;

// 1단계: 인터페이스 정의
public interface HelloInterface {
    void sayHello();
}

public class ProxyExample {
    public static void main(String[] args) {
        // 2단계: InvocationHandler 구현
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if (method.getName().equals("sayHello")) {
                    System.out.println("Hello from dynamic proxy!");
                    return null;
                }
                return method.invoke(this, args);
            }
        };

        // 3단계: 프록시 인스턴스 생성
        HelloInterface proxyInstance = (HelloInterface) Proxy.newProxyInstance(
                HelloInterface.class.getClassLoader(),
                new Class[] { HelloInterface.class },
                handler
        );

        // 프록시를 통해 메서드 호출
        proxyInstance.sayHello();
    }
}

sayHello()를 호출하면 실제로 InvocationHandler.invoke()가 호출되며, 내부에서 method를 해석해서 그에 맞는 행동을 취해주고 있습니다.

Retrofit에선 아래와 같은 인터페이스를 해석해서 그에 알맞은 행동을 취해줍니다. 이때 중요한 것은 Retrofit에서 따로 정의한 @GET과 같은 어노테이션들을 가져와서 그에 맞는 호출을 해주는 것이 됩니다.

interface PhotoService {
    @GET("api/v1/albums/{id}/photos")
    suspend fun readPhotoList(
        @Header("Authorization") token: String,
        @Path("id") id: Long
    ): PhotoListResponse
}

다시 돌아와서 Retrofit에서는 InvocationHandler.invoke()가 다음과 같이 정의되어 있습니다:

// 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);
  1. Object의 method일 경우 그대로 실행한다.(*예시: hashCode())
  2. default method일 경우 구현되어 있는 메소드를 실행한다.
    interface MyInterface {
        // 일반적인 인터페이스 메서드
        void abstractMethod();
    
        // default method 구현
        default void defaultMethod() {
            System.out.println("This is a default method.");
        }
    }
    
    
    *Default Method란? 자바에서 인터페이스에 구현체를 정의해두는 것을 의미합니다. abstract class에서 부분적으로 구현체가 있는 것과 유사합니다.
  3. 이외의 경우 loadServiceMethod() 함수로 ServiceMethod를 가져온 뒤 그것을 invoke 한다.

loadServiceMethod()

메소드를 파싱해서 적절한 ServiceMethod로 변환하는 함수입니다. ServiceMethod 클래스에는 invoke 함수가 인터페이스로 정의되어 있습니다.

ServiceMethod<?> loadServiceMethod(Method method) {
    ServiceMethod<?> result = serviceMethodCache.get(method);
    if (result != null) return result;

    synchronized (serviceMethodCache) {
      result = serviceMethodCache.get(method);
      if (result == null) {
        result = ServiceMethod.parseAnnotations(this, method);
        serviceMethodCache.put(method, result);
      }
    }
    return result;
  }
  1. serviceMethodCache(Map<Method, ServiceMethod<?>>)에서 기존에 캐시된 ServiceMethod가 있다면 그것을 반환한다.
  2. 적절한 캐시가 없다면 ServiceMethod.parseAnnotations로 메소드를 파싱한 뒤 캐시에 저장하고 반환한다.

ServiceMethod.java

ServiceMethod는 parseAnnotations()와 invoke()를 가지고 있습니다. parseAnnotations()에서 Method를 ServiceMethod로 파싱해주고 있습니다. RequestFactory라는 인스턴스를 생성하고 HttpServiceMethod.parseAnnotations()에 넣어주고 있습니다. 이번에도 HttpServiceMethod라는 클래스에 파싱 처리를 위임해주고 있습니다.

abstract class ServiceMethod<T> {
  static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
    RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);

    Type returnType = method.getGenericReturnType();
    if (Utils.hasUnresolvableType(returnType)) {
      throw methodError(
          method,
          "Method return type must not include a type variable or wildcard: %s",
          returnType);
    }
    if (returnType == void.class) {
      throw methodError(method, "Service methods cannot return void.");
    }

    return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
  }

  abstract @Nullable T invoke(Object[] args);
}

RequestFactory.java

ServiceMethod에서 인스턴스를 생성하는 데 쓰인 parseAnnotations() 함수에서 빌더 패턴을 사용하여 RequestFactory를 반환해주고 있습니다.

RequestFactory 코드가 길고 요소가 많아서 클래스의 개요를 미리 알아보고자, default access modifier인 멤버부터 파악하였습니다.

  • 생성 관련: parseAnnotations(), 생성자, Builder
  • 프로퍼티: httpMethod, isKotlinSuspendFunction
  • 메소드: create()——RequestFactory의 가장 핵심적인 역할을 하는 함수로 okhttp3.Request를 반환한다. 이후에 살펴볼 HttpSeviceMethod 및 OkHttpCall에서 사용된다.
final class RequestFactory {
  static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {
    return new Builder(retrofit, method).build();
  }

  private final Method method;
  private final HttpUrl baseUrl;
  final String httpMethod;
  private final @Nullable String relativeUrl;
  private final @Nullable Headers headers;
  private final @Nullable MediaType contentType;
  private final boolean hasBody;
  private final boolean isFormEncoded;
  private final boolean isMultipart;
  private final ParameterHandler<?>[] parameterHandlers;
  final boolean isKotlinSuspendFunction;

  okhttp3.Request create(Object[] args) throws IOException {
    @SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
    ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;

    int argumentCount = args.length;
    if (argumentCount != handlers.length) {
      throw new IllegalArgumentException(
          "Argument count ("
              + argumentCount
              + ") doesn't match expected count ("
              + handlers.length
              + ")");
    }

    RequestBuilder requestBuilder =
        new RequestBuilder(
            httpMethod,
            baseUrl,
            relativeUrl,
            headers,
            contentType,
            hasBody,
            isFormEncoded,
            isMultipart);

    if (isKotlinSuspendFunction) {
      // The Continuation is the last parameter and the handlers array contains null at that index.
      argumentCount--;
    }

    List<Object> argumentList = new ArrayList<>(argumentCount);
    for (int p = 0; p < argumentCount; p++) {
      argumentList.add(args[p]);
      handlers[p].apply(requestBuilder, args[p]);
    }

    return requestBuilder.get().tag(Invocation.class, new Invocation(method, argumentList)).build();
  }

  static final class Builder {
    // Upper and lower characters, digits, underscores, and hyphens, starting with a character.
    private static final String PARAM = "[a-zA-Z][a-zA-Z0-9_-]*";
    private static final Pattern PARAM_URL_REGEX = Pattern.compile("\\\\{(" + PARAM + ")\\\\}");
    private static final Pattern PARAM_NAME_REGEX = Pattern.compile(PARAM);

    final Retrofit retrofit;
    final Method method;
    final Annotation[] methodAnnotations;
    final Annotation[][] parameterAnnotationsArray;
    final Type[] parameterTypes;

    boolean gotField;
    boolean gotPart;
    boolean gotBody;
    boolean gotPath;
    boolean gotQuery;
    boolean gotQueryName;
    boolean gotQueryMap;
    boolean gotUrl;
    @Nullable String httpMethod;
    boolean hasBody;
    boolean isFormEncoded;
    boolean isMultipart;
    @Nullable String relativeUrl;
    @Nullable Headers headers;
    @Nullable MediaType contentType;
    @Nullable Set<String> relativeUrlParamNames;
    @Nullable ParameterHandler<?>[] parameterHandlers;
    boolean isKotlinSuspendFunction;

    Builder(Retrofit retrofit, Method method) {
      this.retrofit = retrofit;
      this.method = method;
      this.methodAnnotations = method.getAnnotations();
      this.parameterTypes = method.getGenericParameterTypes();
      this.parameterAnnotationsArray = method.getParameterAnnotations();
    }

    RequestFactory build() {
      for (Annotation annotation : methodAnnotations) {
        parseMethodAnnotation(annotation);
      }

      if (httpMethod == null) {
        throw methodError(method, "HTTP method annotation is required (e.g., @GET, @POST, etc.).");
      }

      if (!hasBody) {
        if (isMultipart) {
          throw methodError(
              method,
              "Multipart can only be specified on HTTP methods with request body (e.g., @POST).");
        }
        if (isFormEncoded) {
          throw methodError(
              method,
              "FormUrlEncoded can only be specified on HTTP methods with "
                  + "request body (e.g., @POST).");
        }
      }

      int parameterCount = parameterAnnotationsArray.length;
      parameterHandlers = new ParameterHandler<?>[parameterCount];
      for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) {
        parameterHandlers[p] =
            parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter);
      }

      if (relativeUrl == null && !gotUrl) {
        throw methodError(method, "Missing either @%s URL or @Url parameter.", httpMethod);
      }
      if (!isFormEncoded && !isMultipart && !hasBody && gotBody) {
        throw methodError(method, "Non-body HTTP method cannot contain @Body.");
      }
      if (isFormEncoded && !gotField) {
        throw methodError(method, "Form-encoded method must contain at least one @Field.");
      }
      if (isMultipart && !gotPart) {
        throw methodError(method, "Multipart method must contain at least one @Part.");
      }

      return new RequestFactory(this);
    }

    // ...
  }
}

다음으로 처리 과정 중 개인적으로 중요해보였던 구현 사항입니다.

Suspend function

isKotlinSuspendFunction라는 프로퍼티를 어떻게 구현했을까요?

suspend function이 처리될 때 컴파일러는 마지막 인자로 Continuation 객체를 전달합니다. Continuation에는 코루틴의 컨텍스트와 해당 코루틴을 재개하는 함수가 있으며 이를 통해 코루틴의 실행 상태를 관리할 수 있습니다.(참고 - Continuation를 이해하는 데 도움이 되었던 글: https://june0122.github.io/2021/06/09/coroutines-under-the-hood/)

이러한 특성을 바탕으로 마지막 파라미터가 Continuation인지 검사하여 메소드가 suspend function인지 확인할 수 있습니다. RequestFactory에서는 같은 방식으로 메소드가 suspend function인지 여부를 파악하고 있습니다.

Annotation

어노테이션을 instanceof로 검사하여 그에 맞는 처리를 해주고 있습니다.

if (annotation instanceof DELETE) {
  parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false);
} else if (annotation instanceof GET) {
  parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
} else if (annotation instanceof HEAD) {
  parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false);
} else if (annotation instanceof PATCH) {
  parseHttpMethodAndPath("PATCH", ((PATCH) annotation).value(), true);
} else if (annotation instanceof POST) {
  parseHttpMethodAndPath("POST", ((POST) annotation).value(), true);
} else if (annotation instanceof PUT) {
  parseHttpMethodAndPath("PUT", ((PUT) annotation).value(), true);
}
// ...

RequestFactory 클래스를 요약하면 생성 시 메소드에 붙어있는 여러 어노테이션을 해석하여 적절한 필드값을 채워넣은 뒤 create() 함수로 okhttp3.Request를 생성하는 팩토리 클래스입니다.

이렇게 RequestFactory.parseAnnotations()를 알아보았습니다. 다시 돌아와서 ServiceMethod를 리마인드 해보겠습니다.

abstract class ServiceMethod<T> {
  static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
    RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);

    // ...

    return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
  }

  abstract @Nullable T invoke(Object[] args);
}

다음으로 알아봐야 할 것은 HttpServiceMethod입니다.

HttpServiceMethod.java

HttpServiceMethod는 추상 클래스 ServiceMethod를 확장하는 추상 클래스이며 parseAnnotations() 함수에서 Method, RequestFactory를 기반으로 적절한 구현체를 생성하는 것이 주요 책임입니다.

/** Adapts an invocation of an interface method into an HTTP call. */
abstract class HttpServiceMethod<ResponseT, ReturnT> extends ServiceMethod<ReturnT> {
  // ...
}

HttpServiceMethod를 구현하고 있는 클래스가 내부에 정의되어 있습니다:

static final class CallAdapted<ResponseT, ReturnT> extends HttpServiceMethod<ResponseT, ReturnT> { /* ... */ }
static final class SuspendForResponse<ResponseT> extends HttpServiceMethod<ResponseT, Object> { /* ... */ }
static final class SuspendForBody<ResponseT> extends HttpServiceMethod<ResponseT, Object> { /* ... */ }

구현체가 3개인 이유는 Retrofit의 서비스 인터페이스의 함수 정의 방식이 다음과 같이 3가지이기 때문입니다. 아래 함수들은 각각 CallAdapted, SuspendForResponse, SuspendForBody에 해당합니다.(*사실 다음 글에서 다룰 CallAdapter 인터페이스를 구현하면 Deferred, Observable 등 여러 타입을 지원할 수 있습니다.)

interface ApiService {
    @GET("users/{user}/repos")
    fun listReposCall(@Path("user") user: String): Call<List<Repo>>

    @GET("users/{user}/repos")
    suspend fun listReposResponse(@Path("user") user: String): Response<List<Repo>>

    @GET("users/{user}/repos")
    suspend fun listRepos(@Path("user") user: String): List<Repo>
}

parseAnnotations() 함수에서 suspend function 여부와 반환 타입을 기반으로 세 경우를 나누고 있습니다.(isKotlinSuspendFunction, continuationWantsResponse)

  /**
   * Inspects the annotations on an interface method to construct a reusable service method that
   * speaks HTTP. This requires potentially-expensive reflection so it is best to build each service
   * method only once and reuse it.
   */
  static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
      Retrofit retrofit, Method method, RequestFactory requestFactory) {
  boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction;
  boolean continuationWantsResponse = false;
  boolean continuationBodyNullable = false;

  Annotation[] annotations = method.getAnnotations();
  Type adapterType;
  if (isKotlinSuspendFunction) {
    Type[] parameterTypes = method.getGenericParameterTypes();
    Type responseType =
        Utils.getParameterLowerBound(
            0, (ParameterizedType) parameterTypes[parameterTypes.length - 1]);
    if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {
      // Unwrap the actual body type from Response<T>.
      responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
      continuationWantsResponse = true;
    } else {
      // TODO figure out if type is nullable or not
      // Metadata metadata = method.getDeclaringClass().getAnnotation(Metadata.class)
      // Find the entry for method
      // Determine if return type is nullable or not
    }

    adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
    annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
  } else {
    adapterType = method.getGenericReturnType();
  }

  CallAdapter<ResponseT, ReturnT> callAdapter =
      createCallAdapter(retrofit, method, adapterType, annotations);
  Type responseType = callAdapter.responseType();
  if (responseType == okhttp3.Response.class) {
    throw methodError(
        method,
        "'"
            + getRawType(responseType).getName()
            + "' is not a valid response body type. Did you mean ResponseBody?");
  }
  if (responseType == Response.class) {
    throw methodError(method, "Response must include generic type (e.g., Response<String>)");
  }
  // TODO support Unit for Kotlin?
  if (requestFactory.httpMethod.equals("HEAD") && !Void.class.equals(responseType)) {
    throw methodError(method, "HEAD method must use Void as response type.");
  }

  Converter<ResponseBody, ResponseT> responseConverter =
      createResponseConverter(retrofit, method, responseType);

  okhttp3.Call.Factory callFactory = retrofit.callFactory;
  if (!isKotlinSuspendFunction) {
    return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
  } else if (continuationWantsResponse) {
    //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
    return (HttpServiceMethod<ResponseT, ReturnT>)
        new SuspendForResponse<>(
            requestFactory,
            callFactory,
            responseConverter,
            (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
  } else {
    //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
    return (HttpServiceMethod<ResponseT, ReturnT>)
        new SuspendForBody<>(
            requestFactory,
            callFactory,
            responseConverter,
            (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,
            continuationBodyNullable);
  }
}

더 디테일하게 보면 다음과 같습니다.

  1. suspend function 여부와 함수 리턴 타입을 검사합니다. responseType, continuationWantsResponse, adapterType, annotations가 영향을 받습니다.
  2. CallAdapter, Converter<ResponseBody, ResponseT>를 Retrofit으로부터 가져옵니다.(createCallAdapter, createResponseConverter)
  3. isKotlinSuspendFunction, continuationWantsResponse를 통해 세 가지 경우를 분기하여 HttpServiceMethod의 구현체를 반환합니다.

HttpServiceMethod의 구현체는 각각 다음과 같습니다. 특히 suspend function의 경우 Call.enqueue()를 통해 응답이 오면 코루틴을 재개하는 방식으로 구현되어 있습니다.

static final class CallAdapted<ResponseT, ReturnT> extends HttpServiceMethod<ResponseT, ReturnT> {
  private final CallAdapter<ResponseT, ReturnT> callAdapter;

  CallAdapted(
      RequestFactory requestFactory,
      okhttp3.Call.Factory callFactory,
      Converter<ResponseBody, ResponseT> responseConverter,
      CallAdapter<ResponseT, ReturnT> callAdapter) {
    super(requestFactory, callFactory, responseConverter);
    this.callAdapter = callAdapter;
  }

  @Override
  protected ReturnT adapt(Call<ResponseT> call, Object[] args) {
    return callAdapter.adapt(call);
  }
}
static final class SuspendForResponse<ResponseT> extends HttpServiceMethod<ResponseT, Object> {
  private final CallAdapter<ResponseT, Call<ResponseT>> callAdapter;

  SuspendForResponse(
      RequestFactory requestFactory,
      okhttp3.Call.Factory callFactory,
      Converter<ResponseBody, ResponseT> responseConverter,
      CallAdapter<ResponseT, Call<ResponseT>> callAdapter) {
    super(requestFactory, callFactory, responseConverter);
    this.callAdapter = callAdapter;
  }

  @Override
  protected Object adapt(Call<ResponseT> call, Object[] args) {
    call = callAdapter.adapt(call);

    //noinspection unchecked Checked by reflection inside RequestFactory.
    Continuation<Response<ResponseT>> continuation =
        (Continuation<Response<ResponseT>>) args[args.length - 1];

    // See SuspendForBody for explanation about this try/catch.
    try {
      return KotlinExtensions.awaitResponse(call, continuation);
    } catch (Exception e) {
      return KotlinExtensions.suspendAndThrow(e, continuation);
    }
  }
}
static final class SuspendForBody<ResponseT> extends HttpServiceMethod<ResponseT, Object> {
  private final CallAdapter<ResponseT, Call<ResponseT>> callAdapter;
  private final boolean isNullable;

  SuspendForBody(
      RequestFactory requestFactory,
      okhttp3.Call.Factory callFactory,
      Converter<ResponseBody, ResponseT> responseConverter,
      CallAdapter<ResponseT, Call<ResponseT>> callAdapter,
      boolean isNullable) {
    super(requestFactory, callFactory, responseConverter);
    this.callAdapter = callAdapter;
    this.isNullable = isNullable;
  }

  @Override
  protected Object adapt(Call<ResponseT> call, Object[] args) {
    call = callAdapter.adapt(call);

    //noinspection unchecked Checked by reflection inside RequestFactory.
    Continuation<ResponseT> continuation = (Continuation<ResponseT>) args[args.length - 1];

    // Calls to OkHttp Call.enqueue() like those inside await and awaitNullable can sometimes
    // invoke the supplied callback with an exception before the invoking stack frame can return.
    // Coroutines will intercept the subsequent invocation of the Continuation and throw the
    // exception synchronously. A Java Proxy cannot throw checked exceptions without them being
    // declared on the interface method. To avoid the synchronous checked exception being wrapped
    // in an UndeclaredThrowableException, it is intercepted and supplied to a helper which will
    // force suspension to occur so that it can be instead delivered to the continuation to
    // bypass this restriction.
    try {
      return isNullable
          ? KotlinExtensions.awaitNullable(call, continuation)
          : KotlinExtensions.await(call, continuation);
    } catch (Exception e) {
      return KotlinExtensions.suspendAndThrow(e, continuation);
    }
  }
}

이렇게 ServiceMethod, HttpServiceMethod의 구현체가 어떻게 생성되고 구현되었는지를 알 수 있었습니다.

다시 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);
            }
          });
}

지금까지의 흐름을 요약하면 다음과 같습니다:

1. [Retrofit.java] public <T> T create(final Class<T> service)

1-1. Proxy.newProxyInstance()

1-2. new InvocationHandler()

1-3. [Retrofit.java] ServiceMethod<?> loadServiceMethod(Method method)

2. [ServiceMethod.java] static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method)

2-1. [RequestFactory.java] static RequestFactory parseAnnotations(Retrofit retrofit, Method method)

3. [HttpServiceMethod.java] static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(Retrofit retrofit, Method method, RequestFactory requestFactory)

지금까지는 loadServiceMethod()를 통해 ServiceMethod를 생성하기까지의 과정이었으며 이제 invoke() 함수를 호출할 차례입니다. 해당 함수는 HttpServiceMethod에 다음과 같이 정의되어 있습니다.

@Override
final @Nullable ReturnT invoke(Object[] args) {
  Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
  return adapt(call, args);
}

그리고 adapt 함수는 이전에 살펴보았듯이 CallAdapted, SuspendForResponse, SuspendForBody에 구현되어 있으며 CallAdapter.adapt()를 호출하는 방식이었습니다. 여기서 CallAdapter 인스턴스는 Retrofit 객체에서 가져오는 방식이었습니다.

마치며

RequestFactory.create()가 언제 호출되는지, Retrofit에서 CallAdapter, Converter가 어떻게 초기화 되는지 등 아직 풀리지 않은 의문이 많습니다. 다음에는 Retrofit의 초기화 함수인 build 부분부터 시작해서 top-down으로 구현을 알아보고자 합니다.