通常來說,移動端的登錄身份狀態(tài)是通過token實現(xiàn)的,有關(guān)個人信息的資源和操作的服務(wù)端接口會要求傳入token參數(shù)來實現(xiàn)身份驗證桑李。所以在大部分接口里我們都要寫一個@Query("token")token: String块请,或者在構(gòu)建Body的JsonBean里加一個token的屬性。那能不能在retrofit的基礎(chǔ)上進(jìn)行擴(kuò)展簡單的完成這件事呢晕讲。
需求:通過自定義注解實現(xiàn)請求參數(shù)的注入
這里涉及兩個問題覆获,自定義注解的解析,請求參數(shù)的注入瓢省。
我們都知道retrofit是通過動態(tài)代理實現(xiàn)的弄息,那么第一個問題就好解決了,可以通過再一次的動態(tài)代理也就是雙重代理拿到方法注解的信息勤婚。
請求參數(shù)的問題摹量,我們可以通過Okhttp的攔截器來實現(xiàn)。GET的方式的請求添加參數(shù)到url結(jié)尾,POST及其他方式需要重構(gòu)RequestBody缨称。
自定義注解:
@SIGN 方法注解凝果,用于注解retrofit所代理的apiService的接口方法上
@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface SIGN {
boolean value() default true;
}
雙重代理:
不多說直接上代碼:
T apiService = retrofit.create(tClass);
T newApiService = (T) newProxyInstance(tClass.getClassLoader(), new Class<?>[]{tClass}, new ProxyHandler(apiService));
上面代碼很簡單,tClass是被代理的接口睦尽,先調(diào)用retrofit的動態(tài)代理生成代理類器净,再調(diào)用一次動態(tài)代理,動態(tài)代理的邏輯實現(xiàn)在ProxyHandler骂删。
ProxyHandler是一個實現(xiàn)InvocationHandler的類掌动,我們需要在實現(xiàn)方法invoke里解析我們自定義的注解@SIGN 。
下面是invoke方法的核心代碼:
SIGN signAnnotation = method.getAnnotation(SIGN.class);
boolean isSign = signAnnotation !=null && signAnnotation.value();
if(!isSign){return;}
String url = null;
for(Annotation annotation: method.getDeclaredAnnotations()){
if(annotation instanceof GET){
url = ((GET) annotation).value();
}else if(annotation instanceof POST){
url = ((POST) annotation).value();
}else if(annotation instanceof PUT){
url = ((PUT) annotation).value();
}else if(annotation instanceof DELETE){
url = ((DELETE) annotation).value();
}
}
//解析@Url
if(TextUtils.isEmpty(url)){
Annotation[][] annotations = method.getParameterAnnotations();
for(int i=0; i<annotations.length;i++){
for(int j=0; j<annotations[i].length;j++){
if(annotations[i][j] instanceof Url){
try {
String path = new URL((String)args[i]).getPath();
url = path.substring(1);
} catch (MalformedURLException e) {
e.printStackTrace();
}
break;
}
}
if(!TextUtils.isEmpty(url)) break;
}
}
if(!TextUtils.isEmpty(url)){
Timber.d(url);
SignUrlSet.INSTANCE.add(url);
}
我們先解析了方法是否被@SIGN標(biāo)記宁玫,若存在則解析該接口的Url路徑粗恢。(這里我沒有使用全路徑,需要全路徑的請傳入baseUrl)
在解析Url的時候我們并沒有使用反射去通過retrofit來獲取欧瘪,感興趣的同學(xué)可以嘗試一下眷射,這里不做贅述。
因為聯(lián)網(wǎng)請求的異步特性存urlPath集合我使用了CopyOnWriteArraySet佛掖,避免出現(xiàn)線程安全問題:
public enum SignUrlSet {
INSTANCE;
private Set<String> signUrls = new CopyOnWriteArraySet<>();
public void add(String url){
signUrls.add(url);
}
public boolean contains(String url){
return signUrls.contains(url);
}
}
Okhttp攔截器:
先看代碼(這個類kotlin代碼):
object SignInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
val path = request.url().encodedPath().removePrefix("/")
if (SignUrlSet.INSTANCE.contains(path)) {
if ("GET".equals(request.method())) {
val httpUrl = request.url().newBuilder()
.addQueryParameter("token", UserRepository.mToken)
.build()
request = request.newBuilder()
.url(httpUrl).build()
} else {
val requestBody = request.body()
if (requestBody != null) {
val buffer = Buffer()
requestBody.writeTo(buffer)
val charset = requestBody.contentType()?.charset() ?: Charset.forName("UTF-8")
val bodyContent = buffer.readString(charset)
val newBodyContent = Gson().fromJson(bodyContent, JsonObject::class.java)
.apply { addProperty("token", UserRepository.mToken) }
.toString()
request = request.newBuilder()
.method(request.method(), RequestBody.create(requestBody.contentType(), newBodyContent))
.build()
Timber.d(newBodyContent)
}
}
}
return chain.proceed(request)
}
}
我們在攔截器里拿到request,先通過從request解析出path路徑妖碉,再去SignUrlSet查找是否存在該urlPath,也就是該請求方法是否被@SIGN標(biāo)記芥被。
再通過處理GET方式的Url和其他方式的RequestBody欧宜,來build一個新的請求。
自此我們就完成了只通過一個注解就可以實現(xiàn)token參數(shù)的注入:
GET方式:
@SIGN
@GET("base/u/user")
fun getPersonalInfo(): Observable>
POST以及其他方式:
data class ModifyLangReq(var language: String)
@SIGN
@PUT("base/u/language")
fun changeLanguage(@Body reqBody: ModifyLangReq): Observable<BaseResponse<Any>>
以上我們就成功的給Retrofit擴(kuò)展了注入token的功能拴魄,其實通過這個方式還可以添加很多你需要的功能冗茸,比如很多業(yè)務(wù)需要用到的Token自動刷新、Token過期退出登錄匹中、加密夏漱、緩存、API版本號顶捷、多BaseUrl挂绰,都可以這樣實現(xiàn),有機(jī)會再和大家分享服赎。