如何解决Dagger中TokenAuthenticator与AuthsRepository的依赖循环?
问题背景
添加refreshToken功能后触发Dagger依赖循环错误,移除TokenAuthenticator对AuthsRepository的依赖则错误消失。循环依赖链路如下:
public abstract interface AppComponent extends dagger.android.AndroidInjector<com.technoface.chicmen.ChicMenApplication>
{
^
retrofit2.Retrofit is injected at
com.technoface.chicmen.RestModule.provideAuthsService(retrofit)
com.technoface.data.AuthsService is injected at
com.technoface.chicmen.RepositoryModule.provideAuthsRepository(authsService)
com.technoface.data.repository.AuthsRepository is injected at
com.technoface.chicmen.RetrofitModule.provideTokenAuthenticator(�,authsRepository)
com.technoface.data.TokenAuthenticator is injected at
com.technoface.chicmen.RetrofitApiModule.provideOkHttpClient(�,tokenAuthenticator, �)
okhttp3.OkHttpClient is injected at
com.technoface.chicmen.RetrofitApiModule.provideRetrofitClient(�,client)
com.technoface.data.RetrofitClient is injected at
com.technoface.chicmen.RetrofitApiModule.provideRetrofit(retrofitClient)
retrofit2.Retrofit is injected at
com.technoface.chicmen.RestModule.provideAuthsService(retrofit)
...
相关代码
RetrofitApiModule.kt
@AppScope @Provides fun provideOkHttpClient( headerInterceptor: HeaderInterceptor, tokenAuthenticator: TokenAuthenticator, logger: HttpLoggingInterceptor ): OkHttpClient { val builder = OkHttpClient.Builder() builder.addNetworkInterceptor(headerInterceptor) builder.authenticator(tokenAuthenticator) builder.addInterceptor(logger) builder.readTimeout(READ_TIME_OUT_V1, TimeUnit.SECONDS) builder.writeTimeout(WRITE_TIME_OUT_V1, TimeUnit.SECONDS) return builder.build() } }
RetrofitModule.kt
@Module( includes = [ RetrofitApiModule::class ] ) object RetrofitModule { @AppScope @REST @Provides fun provideBaseUrl(): String { return BuildConfig.BASE_URL } @AppScope @Provides fun provideHeaderInterceptor( userProvider: UserProviderListener ): HeaderInterceptor { return HeaderInterceptor(userProvider) } @AppScope @Provides fun provideTokenAuthenticator( tokenInvalidator: TokenInvalidatorListener, userProvider: UserProviderListener, authsRepository: AuthsRepository ): TokenAuthenticator { return TokenAuthenticator(tokenInvalidator, userProvider, authsRepository) } @AppScope @Provides fun provideLoggingInterceptor(): HttpLoggingInterceptor { val logging = HttpLoggingInterceptor() logging.setLevel(HttpLoggingInterceptor.Level.BODY) return logging } @AppScope @Provides fun provideGson(): Gson { return Gson() } } @Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class REST
RestModule.kt
@Module( includes = [ RetrofitModule::class ] ) object RestModule { @AppScope @Provides fun provideAuthsService( retrofit: Retrofit ): AuthsService { return retrofit.create(AuthsService::class.java) }
TokenAuthenticator.kt
class TokenAuthenticator( private val tokenInvalidator: TokenInvalidatorListener, private val userProvider: UserProviderListener, private val authsRepository: AuthsRepository ) : Authenticator { override fun authenticate(route: Route?, response: Response): Request? { return if (userProvider.getIsLoginUser()) { val refreshResponse = authsRepository.refreshToken().blockingGet().exceptionOrNull() if (refreshResponse != null) { tokenInvalidator.invalidateUser() userProvider.logout() return null } return response.request() } else { null } } }
AuthsRepository.kt
class AuthsRepository( private val authsService: AuthsService ) : Repository { fun verifyOtpForgotPassword(request: VerifyOtpForgotPasswordRequest): Single<Result<RestResponse<Any>>> { return authsService.verifyOtpForgotPassword(request).map { Result.success(it) }.onErrorReturn { Result.failure(it) } } fun refreshToken(): Single<Result<RestResponse<LoginResponse>>> { return authsService.refreshToken().map { Result.success(it) }.onErrorReturn { Result.failure(it) } } }
解决方案
方案1:为RefreshToken请求创建独立的OkHttpClient
刷新token的请求不需要触发token验证(否则会无限循环),因此可以单独为该请求创建一个不绑定TokenAuthenticator的OkHttpClient和Retrofit实例,打破循环链。
步骤1:新增刷新专用的OkHttpClient和Retrofit
在RetrofitApiModule中添加:
@AppScope @Provides @Named("refresh") fun provideRefreshTokenOkHttpClient( headerInterceptor: HeaderInterceptor, logger: HttpLoggingInterceptor ): OkHttpClient { val builder = OkHttpClient.Builder() builder.addNetworkInterceptor(headerInterceptor) // 不添加TokenAuthenticator,避免循环 builder.addInterceptor(logger) builder.readTimeout(READ_TIME_OUT_V1, TimeUnit.SECONDS) builder.writeTimeout(WRITE_TIME_OUT_V1, TimeUnit.SECONDS) return builder.build() } @AppScope @Provides @Named("refresh") fun provideRefreshRetrofit( @REST baseUrl: String, @Named("refresh") client: OkHttpClient, gson: Gson ): Retrofit { return Retrofit.Builder() .baseUrl(baseUrl) .client(client) .addConverterFactory(GsonConverterFactory.create(gson)) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build() }
步骤2:新增刷新专用的AuthsService
在RestModule中添加:
@AppScope @Provides @Named("refresh") fun provideRefreshAuthsService( @Named("refresh") retrofit: Retrofit ): AuthsService { return retrofit.create(AuthsService::class.java) }
步骤3:修改AuthsRepository使用刷新专用的Service
class AuthsRepository( private val authsService: AuthsService, @Named("refresh") private val refreshAuthsService: AuthsService ) : Repository { // 其他方法保持不变 fun refreshToken(): Single<Result<RestResponse<LoginResponse>>> { return refreshAuthsService.refreshToken().map { Result.success(it) }.onErrorReturn { Result.failure(it) } } }
方案2:使用Dagger Lazy延迟注入
通过Dagger的Lazy<T>延迟获取AuthsRepository,避免在初始化TokenAuthenticator时立即实例化依赖,从而打破循环。
步骤1:修改TokenAuthenticator的依赖类型
class TokenAuthenticator( private val tokenInvalidator: TokenInvalidatorListener, private val userProvider: UserProviderListener, private val authsRepository: Lazy<AuthsRepository> // 改为Lazy ) : Authenticator { override fun authenticate(route: Route?, response: Response): Request? { return if (userProvider.getIsLoginUser()) { // 需要使用时才调用get()获取实例 val refreshResult = authsRepository.get().refreshToken().blockingGet() if (refreshResult.isFailure) { tokenInvalidator.invalidateUser() userProvider.logout() return null } // 注意:这里需要更新请求头中的新token,原代码未处理,需补充 refreshResult.getOrNull()?.data?.let { loginResponse -> userProvider.saveToken(loginResponse.token) return response.request().newBuilder() .header("Authorization", "Bearer ${loginResponse.token}") .build() } return null } else { null } } }
步骤2:修改RetrofitModule中的提供方法
@AppScope @Provides fun provideTokenAuthenticator( tokenInvalidator: TokenInvalidatorListener, userProvider: UserProviderListener, authsRepository: Lazy<AuthsRepository> // 改为Lazy ): TokenAuthenticator { return TokenAuthenticator(tokenInvalidator, userProvider, authsRepository) }
注意事项
- 方案1更稳妥,避免了刷新请求触发验证循环的风险,适合大多数场景
- 方案2更简洁,但要确保调用
Lazy.get()时依赖已经完成初始化(网络请求触发的authenticate方法满足这个条件)
内容的提问来源于stack exchange,提问作者Ahmet Yılmaz

