Retrofit의 구현체는 어디에 있을까?

Galmaegi
9 min readAug 1, 2021

--

먼저 쉬운 방법으로 jar를 decompile 해봤지만 어디에도 내가 선언한 Retrofit interface의 구현체는 찾을 수 없었다.

그도 그럴것이 retrofit의 dependency를 추가하는 경우 annotation processor에 대한 정의가 따로 없었기 때문이다.

그렇다면 runtime에 코드가 구현된다는 것이고,

코드상으로 확인한 결과 Retrofit.Builder().create()하는 순간 java reflection의 proxy를 통해 해당 구현체가 만들어지고 있음을 알 수 있다.

return Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
.create(ItBookApi::class.java)

아래는 Retrofit.java의 create method이다.

@SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety.
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);
}
});
}

내부의 loadServiceMethod를 통해 내가 선언한 annotation들이 코드화되는것을 확인할 수 있는데,

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;
}

ServiceMethod.parseAnnotation() -> RequestFactory.parseAnnotations() -> HttpServiceMethod.parseAnnotations() 를 통해 구현체가 생성되고 있음을 확인할 수 있다.

사실 이 모든것의 출발점은 retrofit interface에 suspend 키워드를 붙이면 내부에서 생성되는 컴파일 코드를 보고싶기 때문이었다.

private @Nullable ParameterHandler<?> parseParameter(...if (result == null) {
if (allowContinuation) {
try {
if (Utils.getRawType(parameterType) == Continuation.class) {
isKotlinSuspendFunction = true;
return null;
}
} catch (NoClassDefFoundError ignored) {
}
}
throw parameterError(method, p, "No Retrofit annotation found.");
}

RequestFactory.java에서 위와같이 isKotlinSuspendFunction flag를 true로 바꿔주고 있고

static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
Retrofit retrofit, Method method, RequestFactory requestFactory) {
boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction;
boolean continuationWantsResponse = false;
boolean continuationBodyNullable = false;

감사하게도 HttpServiceMethod의 parseAnnotations에서 해당 flag를 기반으로 coroutine관련 코드를 생성해내고 있다. 어떻게?

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();
}
...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);
}

이런 코드로 말이다.

결과적으론 SuspendForResponse, SuspendForBody 둘 중 하나의 클래스로 변환되어 suspend fun을 가진 response를 뱉게 된다.

--

--

Galmaegi
Galmaegi

Written by Galmaegi

0 Followers

Android developer

No responses yet