You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

不使用Dagger等依赖注入如何解决OkHttp authenticator与Retrofit ApiService循环依赖问题

无依赖注入框架下的Token刷新循环依赖解决方案

核心思路

循环依赖的根源是你用了同一个Retrofit实例同时生成普通业务接口和刷新令牌接口的ApiService,我们直接拆分出独立的无认证逻辑的Retrofit实例专门用于调用刷新令牌接口,从根源上切断循环依赖。

具体实现步骤

1. 改造TokenAuthenticator类

首先给TokenAuthenticator的构造函数传入你需要的Jetpack Datastore实例,同时传入专门用于刷新令牌的ApiService实例:

class TokenAuthenticator(
    private val dataStore: DataStore<Preferences>,
    private val refreshTokenApiService: ApiService
) : Authenticator {
    override fun authenticate(route: Route?, response: Response): Request? {
        // 1. 从Datastore取出旧的刷新令牌
        val oldRefreshToken = runBlocking { 
            dataStore.data.first()[yourRefreshTokenKey] 
        } ?: return null // 没有刷新令牌直接返回,走登录流程
        
        // 2. 调用刷新令牌接口(用专门的refreshTokenApiService,不会触发authenticator逻辑)
        val refreshResponse = runBlocking {
            runCatching {
                refreshTokenApiService.refreshToken(oldRefreshToken)
            }.getOrNull()
        } ?: return null // 刷新失败返回,走登录流程
        
        // 3. 把新的令牌、刷新令牌存入Datastore
        runBlocking {
            dataStore.edit {
                it[yourAccessTokenKey] = refreshResponse.accessToken
                it[yourRefreshTokenKey] = refreshResponse.refreshToken
            }
        }
        
        // 4. 用新的令牌重新构建请求返回
        return response.request.newBuilder()
            .header("Authorization", "Bearer ${refreshResponse.accessToken}")
            .build()
    }
}

注意:你可以把刷新令牌的接口单独抽成独立的Service接口,也可以直接复用原来的ApiService,只要用独立的Retrofit构建即可

2. 改造RetrofitBuilder,拆分两个Retrofit实例

object RetrofitBuilder {
    private const val BASE_URL = "..."
    
    // 先构建专门用于刷新令牌的Retrofit,不添加TokenAuthenticator
    private fun buildRefreshTokenRetrofit(): Retrofit {
        val loggingInterceptor = HttpLoggingInterceptor()
            .setLevel(HttpLoggingInterceptor.Level.BODY)
        val okHttpClient = OkHttpClient.Builder()
            // 这里不要加TokenAuthenticator,避免循环
            .addInterceptor(loggingInterceptor)
            .build()
        return Retrofit.Builder()
            .client(okHttpClient)
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }
    
    // 对外提供的业务ApiService构建方法
    operator fun invoke(
        networkConnectionInterceptor: NetworkConnectionInterceptor,
        dataStore: DataStore<Preferences>
    ) : ApiService {
        // 先拿到刷新令牌用的ApiService实例
        val refreshApiService = buildRefreshTokenRetrofit().create(ApiService::class.java)
        // 实例化TokenAuthenticator,传入需要的Datastore和刷新用的ApiService
        val tokenAuthenticator = TokenAuthenticator(dataStore, refreshApiService)
        
        val loggingInterceptor = HttpLoggingInterceptor()
            .setLevel(HttpLoggingInterceptor.Level.BODY)
        val okHttpClient = OkHttpClient.Builder()
            .authenticator(tokenAuthenticator)
            .addInterceptor(networkConnectionInterceptor)
            .addInterceptor(loggingInterceptor)
            .build()
        
        return Retrofit.Builder()
            .client(okHttpClient)
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(ApiService::class.java)
    }
}

可选轻量方案:懒加载后置赋值

如果你不想拆分两个Retrofit实例,也可以通过懒加载+后置设置依赖的方式解决:

  1. 给TokenAuthenticator增加可空的ApiService变量,或者用lateinit var声明ApiService属性
  2. 构建OkHttpClient的时候先传入未赋值ApiService的TokenAuthenticator实例
  3. 等Retrofit构建出ApiService之后,再把ApiService赋值给TokenAuthenticator的对应属性
    该方案实现更简单,但要注意多线程并发场景下的空安全问题,更推荐拆分Retrofit的方案

内容的提问来源于stack exchange,提问作者Sagar Ghare

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 05:27:01