日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

[动态代理三部曲:下] - 从动态代理,看Retrofit的源码实现

發布時間:2023/12/10 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [动态代理三部曲:下] - 从动态代理,看Retrofit的源码实现 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

關于動態代理的系列文章,到此便進入了最后的“一出好戲”。前倆篇內容分別展開了:從源碼上,了解JDK實現動態代理的原理;以及從動態代理切入,學會看class文件結構的含義。

如果還沒有看過這倆篇文章的小伙伴,可以看一看呦(前倆篇是一個小伙伴總結的,這一篇由我來續上。至于他會不會結合動態代理捋一捋Java中的AOP,那就看他了,emmmmmm~)

[動態代理三部曲:中] - 從動態代理,看Class文件結構定義

[動態代理三部曲:上] - 動態代理是如何"坑掉了"我4500塊錢

不扯這些沒用的直接開整!

上源碼

構建Retrofit對象

毫無疑問,分析源碼要先從使用凡是入手。對于我們正常的Retrofit套路,我們會先構建一個接口,這里我們使用一個post請求(這個接口已經不能用了,很久沒有倒騰我的服務器了~):

public interface RetrofitApi {String URL = "https://www.ohonor.xyz/";@POST("retrofitPost")@FormUrlEncodedCall<ResponseBody> postRetrofit(@Field("username") String username, @Field("password") String password); }

然后,我們會通過Builder構建一個Retrofit:

Retrofit retrofit = new Retrofit.Builder().baseUrl(RetrofitApi.URL).addConverterFactory(ScalarsConverterFactory.create()).build();

對于構建Retrofit來說,從外部看就是通過Builder模式去構建。但是細節之處,并非如此,讓我們看一下baseUrl的內部實現。

public static @Nullable HttpUrl parse(String url) {Builder builder = new Builder();Builder.ParseResult result = builder.parse(null, url);return result == Builder.ParseResult.SUCCESS ? builder.build() : null;}

內部很簡單,通過builder.parase()的返回值來判斷是否應該去調用build()方法。因此很明顯,大量的邏輯是在parse()方法之中處理的。讓我們進去一睹芳澤:

此方法內容非常的長,本質就是對url進行準確性的校驗。這里我截取了一些較為關鍵的內容。

//這里是對HTTP請求類型的判斷,是http還是https,并且記錄一個下標pos。 if (input.regionMatches(true, pos, "https:", 0, 6)) {this.scheme = "https";pos += "https:".length(); } else if (input.regionMatches(true, pos, "http:", 0, 5)) {this.scheme = "http";pos += "http:".length(); } else {return ParseResult.UNSUPPORTED_SCHEME; }//接下來的內容,代碼過于的長,這里就不貼出來啦。主要內容就是對我們url常見的分隔符進行解碼。 //比如@和%40的相愛先殺。大家有興趣的話,可以自行查看一下源碼

url構建之前有一個比較經典的校驗過程:"baseUrl must end in /: " + baseUrl。這個異常大家都不陌生吧?~baseUrl必須以/結尾。這里的過程,大家有興趣可以自己看一下呦,原理是通過切割“/”字符串,來判斷是不是以“/”結尾。這里切的url并非是咱們的baseUrl,而是構建完畢的url。因為篇幅原因,這里就不貼代碼了。

動態代理部分

讓我們進入下一個過程,動態代理開始的地方。構建了Retrofit對象直接,我們就開始生成我們的接口對象啦,點進入之后,我們就能看到,屬于的動態代理的方法。還是熟悉的配方,熟悉的味道:

RetrofitApi retrofitApi = retrofit.create(RetrofitApi.class);public <T> T create(final Class<T> service) {//省略一些判斷代碼return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },new InvocationHandler() {private final Platform platform = Platform.get();@Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)throws Throwable {// 如果該方法是來自Object的方法,則遵循正常調用。(正常來說,咱們也不會傳一個Object進來)if (method.getDeclaringClass() == Object.class) {return method.invoke(this, args);}//判斷是否是默認方法,這是1.8新增的內容。下文簡單展開一些:if (platform.isDefaultMethod(method)) {return platform.invokeDefaultMethod(method, service, proxy, args);}//這里才是我們重點關注的地方:ServiceMethod<Object, Object> serviceMethod =(ServiceMethod<Object, Object>) loadServiceMethod(method);OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);return serviceMethod.callAdapter.adapt(okHttpCall);}});}

默認方法: 是JDK1.8增加的接口中的內容。其關鍵字為default。(如果感興趣這個新特性,小伙伴們可以自行了解~)

官網解釋:如果此方法是默認方法,則返回true; 否則返回false。 默認方法:即在在接口類型中,聲明的具有主體的非靜態方法(有具體實現的)。(Returns true if this method is a default method; returns false otherwise. A default method is a public non-abstract instance method, that is, a non-static method with a body, declared in an interface type.)

倆種類型判斷結束,讓我們重點看一下:ServiceMethod<Object, Object> serviceMethod = (ServiceMethod<Object, Object>) loadServiceMethod(method);這行代碼做了什么。我們點進去loadSerivceMethod()方法。

ServiceMethod<?, ?> loadServiceMethod(Method method) {ServiceMethod<?, ?> result = serviceMethodCache.get(method);if (result != null) return result;synchronized (serviceMethodCache) {result = serviceMethodCache.get(method);if (result == null) {result = new ServiceMethod.Builder<>(this, method).build();serviceMethodCache.put(method, result);}}return result; }

很明顯,這里做了一次緩存。如果沒有ServiceMethod對象,那么就通過Builder的方式去構建這個對象。那么Buidler的過程是什么樣子的呢?

build()方法相對比較的長,這里我們看一些比較關鍵的地方。

關鍵點1:

拿到方法上的所有注解,然后遍歷:

for (Annotation annotation : methodAnnotations) {parseMethodAnnotation(annotation); }

parseMethodAnnotation()方法:

private void parseMethodAnnotation(Annotation annotation) {if (annotation instanceof DELETE) {parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false);} else if (annotation instanceof GET) {parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);} else if (annotation instanceof HEAD) {parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false);if (!Void.class.equals(responseType)) {throw methodError("HEAD method must use Void as response type.");}} else if (annotation instanceof PATCH) {parseHttpMethodAndPath("PATCH", ((PATCH) annotation).value(), true);} else if (annotation instanceof POST) {parseHttpMethodAndPath("POST", ((POST) annotation).value(), true);} //省略一些注解類型 }

parseHttpMethodAndPath()方法中,主要做了一件事情:通過傳進來的注解對應的value去判斷是否有?,如果有,那么?后邊不能包含{}(通過正則表達式實現),否則拋異常。如果沒有拋異常,那么通過正則切割{},存到一個Set之中,后續進行處理,也就是和參數中的Path注解的內容進行替換。(下文會涉及替換過程)

關鍵點2:

遍歷過所有方法上的注解后,接下來就是參數注解了。

參數類型校驗:

到達這里,第一步進行的操作,是判斷參數類型。如果參數類型是TypeVariable(類型變量:T、V...)、WildcardType (通配符;?)則直接拋異常:

Type parameterType = parameterTypes[p];if (Utils.hasUnresolvableType(parameterType)) {throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s",parameterType); }static boolean hasUnresolvableType(Type type) {if (type instanceof Class<?>) {return false;}//省略遞歸遍歷的過程if (type instanceof GenericArrayType) {return hasUnresolvableType(((GenericArrayType) type).getGenericComponentType());}if (type instanceof TypeVariable) {return true;}if (type instanceof WildcardType) {return true;} }

參數類型完畢后,便進入參數注解類型的判斷。

參數注解類型校驗:

正式校驗參數注解類型的時候,會先判斷是否有不含注解的參數,這里就會直接拋異常(也就是我們為什么不能在參數中傳不用注解修飾參數報錯的原因):

Annotation[] parameterAnnotations = parameterAnnotationsArray[p]; if (parameterAnnotations == null) {throw parameterError(p, "No Retrofit annotation found."); }

接下來便是校驗參數注解類型。不過,這一部分實在沒辦法貼出來了,核心的判斷方法大概有400行。為啥這么多?因為參數注解類型太多了,每一種都有自己的規則,所以判斷內容很多。如果小伙伴有感興趣的,可以自行去ServiceMethod類中的parseParameterAnnotation()方法查看。

請求接口Api類中,注解使用的異常。基本都是在這里處理的。如果小伙伴們遇到什么奇怪的異常,不妨不著急去百度/Google;讓我們看看源碼是怎么說的~~

Path替換{}的內容

這里我們解決一個疑問:那就是我們最開始處理url的時候,通過正則切割{},我們都知道,這里會通過Path注解去替換。那么這里就讓我們看一看Retrofit是如何處理Path類型的注解的。

else if (annotation instanceof Path) {//省略部分內容Path path = (Path) annotation;String name = path.value();validatePathName(p, name);Converter<?, String> converter = retrofit.stringConverter(type, annotations);return new ParameterHandler.Path<>(name, converter, path.encoded()); }

這里我們能看到,想進行接下來的操作。必然和Converter這個類有著密不可分的關系。

public <T> Converter<T, String> stringConverter(Type type, Annotation[] annotations) {// 省略判空及緩存取值操作。return (Converter<T, String>) BuiltInConverters.ToStringConverter.INSTANCE; }

我們可以看到,第一次一定是沒有Converter對象的。點進INSTANCE之后我們會發現這里構建了一個ToStringConverter類。初始化之后,再讓我們回到Path類型中的判斷里。最終我們會return一個return new ParameterHandler.Path<>(name, converter, path.encoded());很明顯這是一個內部類。其實它是一個封裝類。對應封裝了所有注解對應的java類。用于在請求網絡的時候統一管理。而這個類只需要重寫了apply方法。

static final class Path<T> extends ParameterHandler<T> {//省略構造方法@Override void apply(RequestBuilder builder, @Nullable T value) throws IOException {//省略拋異常。我們Path替換{}的過程就在下面這個方法中。builder.addPathParam(name, valueConverter.convert(value), encoded);} }void addPathParam(String name, String value, boolean encoded) {//省略拋異常,看到replace應該很清楚了吧。relativeUrl = relativeUrl.replace("{" + name + "}", canonicalizeForPath(value, encoded));}

當然,執行replace勢必要引起apply方法的調用。很顯然目前在動態代理的這個過程中,我們沒有辦法看到apply被調用。因此現在先按住不表,讓我們先把動態代理部分整完。

newProxyInstance的return

我們上面看了,校驗接口方法的參數類型/參數注解類型。這個邏輯過后,就是調用build,構建ServiceMethod。

public ServiceMethod build() {// 省略上訴的檢驗過程return new ServiceMethod<>(this); }

構建完了ServiceMethod之后,讓我們再把目光轉移到Retrofit.create()中newProxyInstance的最后一點內容:

ServiceMethod<Object, Object> serviceMethod =(ServiceMethod<Object, Object>) loadServiceMethod(method); OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args); return serviceMethod.callAdapter.adapt(okHttpCall);

走到這,就通過動態代理構建出了我們接口方法中的Call對象。從這三行代碼中,我們很明顯看不出來貓膩,讓我們走進OkHttpCall中:

final class OkHttpCall<T> implements Call<T>

這其中重寫了Call中我們常用的方法,比如:enqueue()。內部是轉發給okhttp3.Call(OkHttp)去處理真正的網絡請求。
接下來讓我們重點看一下return的serviceMethod.callAdapter.adapt(okHttpCall)方法。這里callAdapter的初始化就不展開,默認的是DefaultCallAdapterFactory:

這里我們因為沒有設置適配的Adapter,比如:RxJava的。

final class DefaultCallAdapterFactory extends CallAdapter.Factory {//省略構造方法@Overridepublic CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {//省略判空final Type responseType = Utils.getCallResponseType(returnType);return new CallAdapter<Object, Call<?>>() {@Override public Type responseType() {return responseType;}@Override public Call<Object> adapt(Call<Object> call) {return call;}};} }

看到這個類,我們就可以明確,這了返回的Call實際上就是我們動態代理中傳遞的OkHttpCall。

return serviceMethod.callAdapter.adapt(okHttpCall);
有了它,我們就可以執行我們想執行的網絡請求的方法了。

那么此時我們就可以這么做了:

Call<ResponseBody> call = retrofitApi.postRetrofit(username,password); call.enqueue(....);

動態代理部分接近尾聲

走到這里,動態代理部分就結束了。不過我們還有一些問題沒有看到結果。最簡單的,上面所說的apply方式是誰調用的?其實這個問題很好解答。

我們通過上面的梳理,可以明確動態代理的部分僅僅是為了構建我們的接口類,而真正的調用并非在此。因此我們可以推斷出apply的調用時機應該是正在去請求網絡的時候。

因為本篇的主題是梳理Retrofit中動態代理的部分。所以關于真正請求的部分,就簡單的進行總結下見諒了,各位

我們知道,我們正真請求網絡是調用了Call中的方法:

public interface Call extends Cloneable {Request request();Response execute() throws IOException;void enqueue(Callback responseCallback); }

那么Call的實現類是怎么被創建出來的呢?其中,上文我們已經看到,在newProxyInstance方法中return的時候,初始化的OkHttpCall。既然知道了Call的實現類是什么,那么我們就取其中比較有代表性的方法,來展開apply被調用的過程。

這里我們展開enqueue()方法做代表吧:

@Override public void enqueue(final Callback<T> callback) {checkNotNull(callback, "callback == null");okhttp3.Call call;Throwable failure;//省略判空,同步等操作call = rawCall = createRawCall();//省略真正發起請求的過程。 }private okhttp3.Call createRawCall() throws IOException {//apply就在此方法中被調用Request request = serviceMethod.toRequest(args);okhttp3.Call call = serviceMethod.callFactory.newCall(request);//省略拋異常return call; }Request toRequest(@Nullable Object... args) throws IOException {//省略無關的代碼for (int p = 0; p < argumentCount; p++) {//到此我們的apply就被調用了。handlers[p].apply(requestBuilder, args[p]);}return requestBuilder.build(); }

在這我們就很清晰的看到apply方法被調用~

總結

我們的Retrofit,通過動態代理,構建我們所需要的接口方法,其中校驗我們的接口方法的注解,參數類型,參數注解類型;構建ServiceMethod對象,最終通過OkHttpCall,return出我們所需要的Call類型對象。
有了Call,我們就可以開始網絡請求,當然網絡請求的過程,在OkHttpCall中是被轉發給OkHttp框架中的okhttp3.Call去執行的。

到此,從動態代理,看Retrofit的源碼實現就結束了。這篇文章重點是去分析Retrofit中的動態代理的思路,所以在網絡請求的源碼過程并沒有過多的涉獵。有機會的話,在Retrofit的源碼實現中去總結吧。

在看源碼的過程中,最大的感慨是框架設計上的巧妙。自己最近在重構公司的相機庫,越來越感覺整體設計的重要性!唉,好難。


這里是一個應屆生/初程序員公眾號~~歡迎圍觀

我是一個應屆生,最近和朋友們維護了一個公眾號,內容是我們在從應屆生過渡到開發這一路所踩過的坑,已經我們一步步學習的記錄,如果感興趣的朋友可以關注一下,一同加油~

個人公眾號:IT面試填坑小分隊

總結

以上是生活随笔為你收集整理的[动态代理三部曲:下] - 从动态代理,看Retrofit的源码实现的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 成人av免费在线看 | 久久色视频 | 久久久久国产精品熟女影院 | 色婷婷精品视频 | 国产精品美女久久久免费 | 人人艹人人爽 | 欧美高清日韩 | 黄色av高清 | 国产自在线拍 | 久久亚洲色图 | 中文字幕一区二区三区手机版 | 天堂av中文在线 | 青青草社区 | 日本久久黄色 | 亚洲一区二区欧美 | 男生裸体视频 | 成人性生活免费看 | 天天插美女| 美国一级片网站 | 久久久久久亚洲中文字幕无码 | 日本少妇作爱视频 | 亚洲午夜一区 | 波多野结衣av中文字幕 | 97成人超碰 | 天天干天天舔 | 丝袜老师办公室里做好紧好爽 | 日本在线一本 | 天天干干干干干 | 免费黄色看片 | 国产免费av一区二区 | 欧美日韩人妻精品一区二区三区 | 日韩一级片免费 | 69久久久久 | 韩国一区在线 | 亚洲情se| 椎名由奈av一区二区三区 | 精品久久伊人 | 久久美利坚 | 韩国三色电费2024免费吗怎么看 | 久久精品av | 超碰人人射 | 国产黄色精品网站 | 久久理伦| 国产字幕在线观看 | 久久久久久久久久久久Av | 欧美一区二区三区 | 欧洲精品一区二区三区久久 | 成人精品影院 | 国外成人在线视频 | 99自拍视频在线观看 | 国产91精品一区二区 | 少妇婷婷 | 高清黄色一级片 | 蜜桃在线一区二区三区 | 亚洲 自拍 另类 欧美 丝袜 | 亚洲精视频 | 国产在线一区不卡 | 欧美一级片免费观看 | 亚洲欧洲日韩综合 | 波多野结衣一区二区三区高清av | 人人插人人爽 | 最新理伦片eeuss影院 | 四虎精品永久在线 | 亚洲xx网 | 精品亚洲国产成人av制服丝袜 | 久久免费片 | 亚洲国产果冻传媒av在线观看 | 欧美久久久久久久久中文字幕 | 黄色链接视频 | 男女爽爽视频 | 国产精品一区二区白浆 | 亚洲精品~无码抽插 | 国产日韩一区二区三区 | 免费看成年人视频 | 成人国产精品入口免费视频 | 91狠狠干 | 扒开女人屁股进去 | 波多野结衣小视频 | 国产激情福利 | 亚欧美色图 | 亚洲天堂影视 | 免费黄色看片网站 | 久久国产精品一区二区三区 | 久久99亚洲精品 | 成人xxxx | 蜜臀aⅴ国产精品久久久国产老师 | 精品无码一区二区三区在线 | 毛片免费一区二区三区 | 天堂网一区二区 | 亚洲一区二区视频在线 | 欧美在线一二三 | 成年人免费小视频 | 黄色片a级片 | 亚洲一区二区三区四区在线播放 | 亚洲天堂av中文字幕 | 激情视频网址 | 亚洲中文字幕无码av | 国产日韩视频在线观看 | 国产视频一区二区三区四区五区 |