
이전 글에서 Interceptor를 통해 JWT 인증에 필요한 token 갱신하는 로직을 구현하였습니다. 하지만 프로젝트를 하다보면 같은 서버에 요청하는 것이 아닌 다른 서버에 API 요청을 하는 경우가 생깁니다. (ex. 카카오 로그인, 네이버 로그인 ..)
이 때, Interceptor를 동일하게 사용하게 되면 불필요한 헤더 정보가 추가되어 요청에 실패하게 됩니다. Hilt를 사용하는 상황에서 같은 타입의 Interceptor 이지만 다른 인스턴스가 주입이 되게 만드려면 어떻게 해야할까요?
Annotation, Annotation Processor에 대해 알아보고 Qualifier를 사용해서 해결해봅시다! 😎
Annotation 은 메타데이터를 코드에 추가하는 방법으로 코드에 대한 추가 정보나 처리 동작들을 제어할 때 사용합니다. Annotation 은 클래스, 메소드, 변수, 인터페이스에 추가할 수 있으며 Compiler, Annotation Processor, Reflection을 통해 처리됩니다.
많이 사용해봤을 법한 @Override, @Deprecated, @Singleton 등이 해당됩니다.
Annotation을 처리하는 방법은 여러가지지만 대표적인 방법 중 하나인 Annotation Processor에 대해서 설명드리겠습니다. Annotation Processor는 java 컴파일러 플러그인의 일종으로 컴파일 타임에 코드를 검사, 수정 또는 생성하는데 사용됩니다. Annotation Processor 를 사용하면 다양한 이점이 있지만, 보일러 플레이트 코드를 직접 작성하지 않아도 생성할 수 있다는 큰 장점이 있습니다.

위 그림과 함께 보면 이해가 쉬울 것이라고 생각합니다. Annotation을 스캔해서 Processor를 통해 처리하고 이 때 생성된 소스 코드들이 있을 것입니다. 그래서 위 작업을 반복해서 모든 Annotation을 처리하게 되면 컴파일이 시작됩니다.
사실 Qualifier에 대해서만 설명하자면 금방 끝나겠지만, Qualifier가 어떻게 동작하고 어떤 것으로 처리되는지 알면 좋을 것 같아서 Annotaiton Processor까지 설명을 드렸습니다. Annotation을 처리하는 방법에는 리플렉션도 있지만, 리플렉션까지 설명을 하면 너무 길어질 것 같아 다음 기회에 설명해보도록 하겠습니다.
Hilt 에서는 이미 사전에 정의된 다양한 Annotaiton들이 존재합니다. 대표적으로 @Singleton, @Provides, @Binds, @ApplicationContext, @ActivityContext 등이 있습니다. Hilt에 대해서 자세히 알고 싶으면 Hilt에 대해서 알아보자 글을 참고해주세요
안드로이드 공식 문서를 확인하더라도 동일한 유형에 대해서 의존성을 제공해야 하는 상황에서는 Qualifier를 쓰라고 알려주고 있습니다.
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AuthInterceptorOkHttpClient
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class OtherInterceptorOkHttpClient
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@AuthInterceptorOkHttpClient
@Provides
fun provideAuthInterceptorOkHttpClient(
authInterceptor: AuthInterceptor
): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(authInterceptor)
.build()
}
@OtherInterceptorOkHttpClient
@Provides
fun provideOtherInterceptorOkHttpClient(
otherInterceptor: OtherInterceptor
): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(otherInterceptor)
.build()
}
}
Qualifier 는 특정 유형에 대해 여러 의존성이 정의되어 있을 때 그 유형을 식별하는데 사용하는 Annotation 입니다. 위 예에서 같은 OkHttpClient를 제공하지만, 다른 Interceptor를 사용하고 싶을 경우에 Qualifer를 정의하여 해당 유형이 주입받을 수 있게 할 수 있습니다.
// As a dependency of another class.
@Module
@InstallIn(ActivityComponent::class)
object AnalyticsModule {
@Provides
fun provideAnalyticsService(
@AuthInterceptorOkHttpClient okHttpClient: OkHttpClient
): AnalyticsService {
return Retrofit.Builder()
.baseUrl("https://example.com")
.client(okHttpClient)
.build()
.create(AnalyticsService::class.java)
}
}
주입을 받는 쪽에서 해당 Qualifier를 명시적으로 입력하면 Annotation Processor로 의존성 관련 파일이 생길 때 원하는 의존성이 주입받게 됩니다.
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class LoginClient
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BuddyConClient
카카오 로그인 시에는 header에 JWT token 정보가 없이 보내야하고 버디콘 서버에 API 요청 시에는 항상 필요하기 때문에 다른 Interceptor가 필요합니다. 해당 Interceptor에 맞는 OkHttpClient 를 주입받을 수 있도록 Qualifier를 정의하였습니다 .여기서 Retention은 Annotation 이 얼마나 유지될지를 정하는 것으로 BINARY로 지정하여 컴파일 타임까지 유효하고 바이트코드까지 남아있도록 하였습니다.
Retentation 별로 보존 정책이 얼마나 될지 알아보시면 도움이 될 수 있습니다~!