【问题标题】:Android Dagger2 + OkHttp + Retrofit dependency cycle errorAndroid Dagger2 + OkHttp + Retrofit 依赖循环错误
【发布时间】:2017-10-10 09:48:13
【问题描述】:

嘿,我正在使用 Dagger2RetrofitOkHttp,我正面临依赖周期问题。

提供OkHttp时:

@Provides
@ApplicationScope
OkHttpClient provideOkHttpClient(TokenAuthenticator auth,Dispatcher dispatcher){
    return new OkHttpClient.Builder()
            .connectTimeout(Constants.CONNECT_TIMEOUT, TimeUnit.SECONDS)
            .readTimeout(Constants.READ_TIMEOUT,TimeUnit.SECONDS)
            .writeTimeout(Constants.WRITE_TIMEOUT,TimeUnit.SECONDS)
            .authenticator(auth)
            .dispatcher(dispatcher)
            .build();
}

提供Retrofit时:

@Provides
@ApplicationScope
Retrofit provideRetrofit(Resources resources,Gson gson, OkHttpClient okHttpClient){
    return new Retrofit.Builder()
            .baseUrl(resources.getString(R.string.base_api_url))
            .addConverterFactory(GsonConverterFactory.create(gson))
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .client(okHttpClient)
            .build();
}

提供APIService时:

@Provides
@ApplicationScope
APIService provideAPI(Retrofit retrofit) {
    return retrofit.create(APIService.class);
}

我的APIService界面:

public interface  APIService {
@FormUrlEncoded
@POST("token")
Observable<Response<UserTokenResponse>> refreshUserToken();

--- other methods like login, register ---

}

我的TokenAuthenticator 班级:

@Inject
public TokenAuthenticator(APIService mApi,@NonNull ImmediateSchedulerProvider mSchedulerProvider) {
    this.mApi= mApi;
    this.mSchedulerProvider=mSchedulerProvider;
    mDisposables=new CompositeDisposable();
}

@Override
public  Request authenticate(Route route, Response response) throws IOException {

    request = null;

    mApi.refreshUserToken(...)
            .subscribeOn(mSchedulerProvider.io())
            .observeOn(mSchedulerProvider.ui())
            .doOnSubscribe(d -> mDisposables.add(d))
            .subscribe(tokenResponse -> {
                if(tokenResponse.isSuccessful()) {
                    saveUserToken(tokenResponse.body());
                    request = response.request().newBuilder()
                            .header("Authorization", getUserAccessToken())
                            .build();
                } else {
                    logoutUser();
                }
            },error -> {

            },() -> {});

    mDisposables.clear();
    stop();
    return request;

}

我的日志:

Error:(55, 16) error: Found a dependency cycle:
com.yasinkacmaz.myapp.service.APIService is injected at com.yasinkacmaz.myapp.darkvane.modules.NetworkModule.provideTokenAuthenticator(…, mApi, …)
com.yasinkacmaz.myapp.service.token.TokenAuthenticator is injected at
com.yasinkacmaz.myapp.darkvane.modules.NetworkModule.provideOkHttpClient(…, tokenAuthenticator, …)
okhttp3.OkHttpClient is injected at
com.yasinkacmaz.myapp.darkvane.modules.NetworkModule.provideRetrofit(…, okHttpClient)
retrofit2.Retrofit is injected at
com.yasinkacmaz.myapp.darkvane.modules.NetworkModule.provideAPI(retrofit)
com.yasinkacmaz.myapp.service.APIService is provided at
com.yasinkacmaz.myapp.darkvane.components.ApplicationComponent.exposeAPI()

所以我的问题是:我的TokenAuthenticator 课程取决于APIService,但我需要在创建APIService 时提供TokenAuthenticator。这会导致依赖循环错误。我该如何解决这个问题,有没有人面临这个问题? 提前致谢。

【问题讨论】:

  • 因为它没有意义... OkHttpClient 与 TokenAuthenticator 用于获取 TokenAuthenticator 所需的身份验证令牌...它具有“循环依赖”甚至“在纸上”...创建另一个服务来获取身份验证令牌与另一个没有身份验证器的 http 客户端实例
  • TokenAuthenticator 用于刷新用户令牌,我想为每个网络调用使用相同的 OkHttp 实例。因为管理用户令牌。我在那个 OkHttp 实例上有调度员
  • 再一次,它没有意义......即使你修复它,它也会导致stackoverflow......你从authenticate调用refreshUserToken......但是refreshUserToken需要致电authenticate
  • @Selvin Authenticate 在我收到 401 错误代码时工作,然后我使用 refreshUserToken 刷新我的令牌,然后继续我的工作。 refreshUserToken 方法不需要调用 authenticate
  • refreshUserToken 正在使用 OkHttpClient,它设置为使用正在使用 refreshUserToken 的 TokenAuthenticator ... refreshUserToken 方法不需要调用 authenticate 可以是真的吗?

标签: android dependency-injection retrofit2 dagger-2 rx-java2


【解决方案1】:

你的问题是:

  1. 您的 OKHttpClient 取决于您的身份验证器
  2. 您的身份验证器依赖于改造服务
  3. 改造依赖于 OKHttpClient(如第 1 点)

因此循环依赖。

这里一种可能的解决方案是让您的 TokenAuthenticator 依赖于 APIServiceHolder 而不是 APIService。然后,您的 TokenAuthenticator 可以在配置 OKHttpClient 时作为依赖项提供,无论 APIService(在对象图的下方)是否已被实例化。

一个非常简单的APIServiceHolder:

public class APIServiceHolder {

    private APIService apiService;

    @Nullable
    APIService apiService() {
        return apiService;
    }

    void setAPIService(APIService apiService) {
        this.apiService = apiService;
    }
}

然后重构你的 TokenAuthenticator:

@Inject
public TokenAuthenticator(@NonNull APIServiceHolder apiServiceHolder, @NonNull ImmediateSchedulerProvider schedulerProvider) {
    this.apiServiceHolder = apiServiceHolder;
    this.schedulerProvider = schedulerProvider;
    this.disposables = new CompositeDisposable();
}

@Override
public  Request authenticate(Route route, Response response) throws IOException {

    if (apiServiceHolder.get() == null) {
         //we cannot answer the challenge as no token service is available

         return null //as per contract of Retrofit Authenticator interface for when unable to contest a challenge
    }    

    request = null;            

    TokenResponse tokenResponse = apiServiceHolder.get().blockingGet()

    if (tokenResponse.isSuccessful()) {
        saveUserToken(tokenResponse.body());
        request = response.request().newBuilder()
                     .header("Authorization", getUserAccessToken())
                     .build();
    } else {
       logoutUser();
    }

    return request;
}

请注意,检索令牌的代码应该是同步的。这是Authenticator 合同的一部分。 Authenticator 中的代码将关闭主线程。

当然,您需要为此编写 @Provides 方法:

@Provides
@ApplicationScope
apiServiceHolder() {
    return new APIServiceHolder();
}

并重构提供者方法:

@Provides
@ApplicationScope
APIService provideAPI(Retrofit retrofit, APIServiceHolder apiServiceHolder) {
    APIService apiService = retrofit.create(APIService.class);
    apiServiceHolder.setAPIService(apiService);
    return apiService;
}

请注意,可变的全局状态通常不是一个好主意。但是,如果你的包组织得很好,你可以适当地使用访问修饰符来避免对持有者的意外使用。

【讨论】:

  • 我有两种方法。其中一个是这个,另一个是:对 TokenAuthenticator 和 TokenInterceptor 类使用 OkHttp 的另一个实例,因为它们仅在任何 APIService 请求调用时触发,因此它们没有被绑定。同样通过这种方式,我会将令牌处理与我的其他请求分开,以便我可以轻松维护。你推荐哪一个?
  • @Yasin 我个人更喜欢我的解决方案,而不是拥有两个 OkHttpClient 实例。
  • @Yasin 如果您查看 Authenticator 的文档,它似乎旨在与单个 OkHttpClient 一起使用。 authenticate 中的代码与原始请求在同一线程上执行。
  • APIServiceHolder 在我的情况下总是返回 null。
  • 这不还是循环依赖吗?您将服务隐藏在包装器中并应用延迟初始化,但 TokenAuthenticator 仍然依赖于服务。
【解决方案2】:

使用匕首2的Lazy接口是这里的解决方案。 在您的 TokenAuthenticator 中,将 APIService mApi 替换为 Lazy&lt;APIService&gt; mApiLazyWrapper

@Inject
public TokenAuthenticator(Lazy<APIService> mApiLazyWrapper,@NonNull ImmediateSchedulerProvider mSchedulerProvider) {
    this.mApiLazyWrapper= mApiLazyWrapper;
    this.mSchedulerProvider=mSchedulerProvider;
    mDisposables=new CompositeDisposable();
}

要从包装器中获取APIService 实例,请使用mApiLazyWrapper.get()

如果mApiLazyWrapper.get()返回null,同样从TokenAuthenticatorauthenticate方法返回null。

【讨论】:

  • 亲爱的@Tartar 这个问题来自 2017 年,大约 3 岁。这可能也有效,但我们的 APIService 仍然持有 TokenAuthenticator。唯一的区别是我们延迟注入它。所以恕我直言,我更喜欢使用简单的客户端进行身份验证流程,可能与几个拦截器隔离。
  • 这是解决循环依赖错误的 Dagger 方法,因此对于那些想要以这种方式解决问题而不是拥有多个 HTTP 客户端的人来说,答案会很有用。
  • 是的,这将是新观众的指南。此外,@max 也有关于 Lazy 的答案。有几种方法可以处理依赖循环问题,他们可以为他们的应用选择合适的解决方案
  • 我认为这是目前最好的解决方案。我不相信 Lazy 在我写最初的答案时就在身边
【解决方案3】:

非常感谢@Selvin 和@David。我有两种方法,一种是David's answer,另一种是:

创建另一个 OkHttpRetrofit 或另一个库来处理我们在 TokenAuthenticator 类中的操作。

如果你想使用另一个OkHttpRetrofit 实例,你必须使用限定符注解。

例如:

@Qualifier
public @interface ApiClient {}


@Qualifier
public @interface RefreshTokenClient {}

然后提供:

@Provides
@ApplicationScope
@ApiClient
OkHttpClient provideOkHttpClientForApi(TokenAuthenticator tokenAuthenticator, TokenInterceptor tokenInterceptor, Dispatcher dispatcher){
    return new OkHttpClient.Builder()
            .connectTimeout(Constants.CONNECT_TIMEOUT, TimeUnit.SECONDS)
            .readTimeout(Constants.READ_TIMEOUT,TimeUnit.SECONDS)
            .writeTimeout(Constants.WRITE_TIMEOUT,TimeUnit.SECONDS)
            .authenticator(tokenAuthenticator)
            .addInterceptor(tokenInterceptor)
            .dispatcher(dispatcher)
            .build();
}

@Provides
@ApplicationScope
@RefreshTokenClient
OkHttpClient provideOkHttpClientForRefreshToken(Dispatcher dispatcher){
    return new OkHttpClient.Builder()
            .connectTimeout(Constants.CONNECT_TIMEOUT, TimeUnit.SECONDS)
            .readTimeout(Constants.READ_TIMEOUT,TimeUnit.SECONDS)
            .writeTimeout(Constants.WRITE_TIMEOUT,TimeUnit.SECONDS)
            .dispatcher(dispatcher)
            .build();
}

@Provides
@ApplicationScope
@ApiClient
Retrofit provideRetrofitForApi(Resources resources, Gson gson,@ApiClient OkHttpClient okHttpClient){
    return new Retrofit.Builder()
            .baseUrl(resources.getString(R.string.base_api_url))
            .addConverterFactory(GsonConverterFactory.create(gson))
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .client(okHttpClient)
            .build();
}

@Provides
@ApplicationScope
@RefreshTokenClient
Retrofit provideRetrofitForRefreshToken(Resources resources, Gson gson,@RefreshTokenClient OkHttpClient okHttpClient){
    return new Retrofit.Builder()
            .baseUrl(resources.getString(R.string.base_api_url))
            .addConverterFactory(GsonConverterFactory.create(gson))
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .client(okHttpClient)
            .build();
}

然后我们可以提供我们单独的接口:

@Provides
@ApplicationScope
public APIService provideApi(@ApiClient Retrofit retrofit) {
    return retrofit.create(APIService.class);
}

@Provides
@ApplicationScope
public RefreshTokenApi provideRefreshApi(@RefreshTokenClient Retrofit retrofit) {
    return retrofit.create(RefreshTokenApi.class);
}

当提供我们的 TokenAuthenticator 时:

@Provides
@ApplicationScope
TokenAuthenticator provideTokenAuthenticator(RefreshTokenApi mApi){
    return new TokenAuthenticator(mApi);
}

优点:您有两个独立的 api 接口,这意味着您可以独立维护它们。您也可以使用普通的 OkHttpHttpUrlConnection 或其他库。

缺点:你会有两个不同的 OkHttp 和 Retrofit 实例。

P.S : 确保在 Authenticator 类中进行同步调用。

【讨论】:

  • 我是 dagger 2 的初学者,当我使用您的方法时,我在以前的 apiService 中开始出现错误,如果没有 @Provides-annotated 方法,就无法提供 ApiService。我错过了什么?
  • 亲爱的@BajrangHudda,如果您不显示您的代码,我无法帮助您。我的意思是你的组件,模块。另外,如果您是 dagger 的初学者,我强烈建议您阅读 Dagger 示例并尝试了解什么是依赖注入。
  • 我用大卫的回答,有什么缺点吗?
  • @BajrangHudda 我看不出大卫的回答有什么缺点;但是您知道在软件中有时有多种解决方案可供您使用。而且我个人不喜欢依赖注入模块中可以为空的东西。这个问题也很老了,我现在正在使用KotlinDaggerDagger 已经更新了很多,让我看看这个问题,看看是否有其他解决方法。
  • @BajrangHudda 在 Dagger 也有 Lazy。没用过,可以看看。
【解决方案4】:

您可以通过 Lazy 类型将服务依赖项注入您的身份验证器。这样可以避免对实例化的循环依赖。

查看link,了解 Lazy 的工作原理。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-03-08
    • 2018-01-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-17
    • 2015-07-24
    相关资源
    最近更新 更多