こんにちは、Dagger2
、Retrofit
、OkHttp
を使用していますが、依存関係サイクルの問題に直面しています。
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;
}
私のlogcat:
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
に依存していますが、TokenAuthenticator
を作成するときにAPIService
を提供する必要があります。これにより、依存サイクルエラーが発生します。どうすればこれを克服できますか?この問題に直面している人はいますか?前もって感謝します。
あなたの問題は:
したがって、循環依存関係です。
ここで考えられる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;
}
トークンを取得するコードはsynchronousである必要があります。これはAuthenticator
の契約の一部です。 Authenticator
内のコードはoffメインスレッドを実行します。
もちろん、同じために@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;
}
変更可能なグローバル状態は通常、良い考えではないことに注意してください。ただし、パッケージが適切に構成されている場合は、アクセス修飾子を適切に使用して、ホルダーの意図しない使用を回避できる場合があります。
Dagger 2のLazy
インターフェイスを使用することがここでのソリューションです。 TokenAuthenticator
で_APIService mApi
_を_Lazy<APIService> 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を返す場合、authenticate
のTokenAuthenticator
メソッドからもnullを返します。
@Selvinと@Davidに感謝します。私には2つのアプローチがあります。1つは Davidの答え で、もう1つは:
別のOkHttp
またはRetrofit
またはTokenAuthenticator
クラス内の操作を処理する別のライブラリを作成します。
別のOkHttp
またはRetrofit
インスタンスを使用する場合は、修飾子アノテーションを使用する必要があります。
例えば :
@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);
}
利点:2つの分離したAPIインターフェースがあるため、それらを個別に保守できます。また、プレーンなOkHttp
またはHttpUrlConnection
または別のライブラリを使用することもできます。
短所:2つの異なるOkHttpおよびRetrofitインスタンスが作成されます。
PS:Authenticatorクラス内で同期呼び出しを行うようにしてください。
Lazyタイプを介して、サービスの依存関係をオーセンティケーターに注入できます。これにより、インスタンス化への循環的な依存を回避できます。
Lazyがどのように機能するかについては、この link を確認してください。