嘿,我正在使用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
。这会导致依赖循环错误。我怎么打败这个,是否有人面临这个问题?
提前谢谢。
答案 0 :(得分:20)
你的问题是:
因此循环依赖。
此处的一种可能解决方案是让checkLogin(url: string): Observable<boolean> {
if (this.authService.login_data && (this.authService.login_data.userprofile || !this.authService.login_data.need_login)) {
return Observable.of(true); // wrap true in Observable
}
return this.authService.get_login_data()
.map(
data => {
if (data.userprofile || !data.need_login) {
return true;
}
this.authService.redirectUrl = url;
this.router.navigate(['/login']);
return false;
},
error => { this.authService.errMsg = <any>error;}
);
依赖TokenAuthenticator
而不是APIServiceHolder
。然后,在配置APIService
时,可以将TokenAuthenticator
作为依赖项提供,无论OKHttpClient
(对象图下方是否已经实例化)是否已实例化。
一个非常简单的APIServiceHolder:
APIService
然后重构你的TokenAuthenticator:
public class APIServiceHolder {
private APIService apiService;
@Nullable
APIService apiService() {
return apiService;
}
void setAPIService(APIService apiService) {
this.apiService = apiService;
}
}
请注意,检索令牌的代码应为同步。这是@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();
}
请注意,可变全局状态通常不是一个好主意。但是,如果您的软件包组织得很好,您可以适当地使用访问修饰符,以避免持有者的意外使用。
答案 1 :(得分:4)
使用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
的{{1}}方法返回null。
答案 2 :(得分:1)
非常感谢@Selvin和@David。我有两种方法,其中一种是David's answer,另一种是:
创建另一个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);
}
优点:您有两个独立的api接口,这意味着您可以独立维护它们。您也可以使用普通OkHttp
或HttpUrlConnection
或其他图书馆。
缺点:您将拥有两个不同的OkHttp和Retrofit实例。
P.S:确保在Authenticator类中进行同步调用。
答案 3 :(得分:0)
您可以通过Lazy类型将服务依赖项注入到身份验证器中。这样,您将避免对实例化的周期性依赖。
检查此link上的Lazy的工作方式。