這是我在csdn發(fā)表的一篇技術(shù)博文:https://blog.csdn.net/sinat_23092639/article/details/102237404
1.Retrofit簡(jiǎn)介
retrofit(Retrofit官方)已經(jīng)誕生好幾年了,從誕生開(kāi)始一直都是Android應(yīng)用開(kāi)發(fā)最流行的網(wǎng)絡(luò)請(qǐng)求框架,準(zhǔn)確來(lái)說(shuō),是網(wǎng)絡(luò)請(qǐng)求框架一個(gè)巧妙的包裝。
正如官網(wǎng)所說(shuō),retrofit最大的特點(diǎn),在于可以用一個(gè)Java interface通過(guò)注解去表示一個(gè)Http請(qǐng)求。
1.比如定義一個(gè)GET請(qǐng)求的Java interface:
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}
2.然后 通過(guò)Retrofit創(chuàng)建一個(gè)GitHubService實(shí)例:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
GitHubService service = retrofit.create(GitHubService.class);
3.再獲取GitHubService實(shí)例對(duì)應(yīng)的Call,通過(guò)Call就可以發(fā)起Http請(qǐng)求了:
Call<List<Repo>> repos = service.listRepos("octocat");
call.enqueue(new Callback<List<Repo>>()
{
@Override
public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response)
{
Log.e(TAG, "normalGet:" + response.body() + "");
}
@Override
public void onFailure(Call<List<Repo>> call, Throwable t)
{
}
});
Retrofit底層默認(rèn)是用OkHttp,正如前面所說(shuō)它只是一個(gè)包裝,我們之所以使用Retrofit的目的是為了簡(jiǎn)化和優(yōu)化代碼的調(diào)用,優(yōu)化開(kāi)發(fā)效率。
2.動(dòng)態(tài)代理機(jī)制分析
關(guān)于Retrofit的使用不是本文的重點(diǎn),本文重點(diǎn)談?wù)凴etrofit中的動(dòng)態(tài)代理機(jī)制。
動(dòng)態(tài)代理可以說(shuō)是Retrofit中的核心機(jī)制,其使用體現(xiàn)在上面第2步,即生成一個(gè)我們定義的請(qǐng)求Java interface的請(qǐng)求實(shí)例,將原來(lái)一個(gè)普通的Java Interface轉(zhuǎn)化為一個(gè)可以進(jìn)行Http請(qǐng)求的對(duì)象。
要了解Retrofit中動(dòng)態(tài)代理機(jī)制的運(yùn)用,還是讓我們從動(dòng)態(tài)代理本身講起吧。
建議先看下官網(wǎng)關(guān)于動(dòng)態(tài)代理的基本說(shuō)明,畢竟是權(quán)威資料而且詳細(xì):
Oracle動(dòng)態(tài)代理說(shuō)明
我們先來(lái)看一個(gè)最簡(jiǎn)單的動(dòng)態(tài)代理例子,感受下通過(guò)動(dòng)態(tài)代理將一個(gè)普通Java接口轉(zhuǎn)化為一個(gè)代理對(duì)象:
一.首先創(chuàng)建一個(gè)Java Interface,對(duì)應(yīng)上文的Retrofit請(qǐng)求接口GitHubService :
public interface Book {
String read(String s);
}
二.通過(guò)Proxy生成Book 的代理對(duì)象的Class對(duì)象:
Class bookProxyClass = Proxy.getProxyClass(Book.class.getClassLoader(),Book.class);
我們知道,一個(gè)Java Interface是不可以直接創(chuàng)建一個(gè)對(duì)象的,所以動(dòng)態(tài)代理所做的是在運(yùn)行時(shí)生成一個(gè)實(shí)現(xiàn)了該Interface的類的Class對(duì)象。
Proxy的getProxyClass方法注釋已經(jīng)解釋清楚了:
Returns the {@code java.lang.Class} object for a proxy class given a class loader and an array of interfaces. The proxy class will be defined by the specified class loader and will implement all of the supplied interfaces. If any of the given interfaces is non-public, the proxy class will be non-public. If a proxy class for the same permutation of interfaces has already been defined by the
class loader, then the existing proxy class will be returned; otherwise,a proxy class for those interfaces will be generated dynamically and defined by the class loader.
簡(jiǎn)單來(lái)說(shuō),就是在運(yùn)行時(shí)生成一個(gè)代理Class二進(jìn)制流,并通過(guò)傳入的ClassLoader去加載成一個(gè)代理Class對(duì)象,該Class實(shí)現(xiàn)了傳入的第二個(gè)參數(shù)對(duì)應(yīng)的Interface。
具體怎么生成Class二進(jìn)制流就不在這里講了,這里看下生成的Class具體啥樣子:
通過(guò)添加一行代碼:
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
可以將運(yùn)行時(shí)生成的動(dòng)態(tài)代理Class文件保存下來(lái)(關(guān)于Class文件,詳細(xì)可以看我這篇博文:從字節(jié)碼角度仔細(xì)剖析一個(gè)HelloWorld程序)
用Idea打開(kāi)該Class文件(自動(dòng)進(jìn)行了反編譯):
public final class $Proxy0 extends Proxy implements Book {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String read(String var1) throws {
try {
return (String)super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("proxy.Book").getMethod("read", Class.forName("java.lang.String"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
代碼很簡(jiǎn)單,從該代碼可以提取以下幾個(gè)要點(diǎn):
1.代理類Proxy0繼承Proxy,實(shí)現(xiàn)了我們的Book接口。
2.用4個(gè)Method引用保存equals、hashCode、toString以及Book接口的read方法的Method對(duì)象
3.在equals、hashCode、toString以及Book接口的read方法的實(shí)現(xiàn)都為調(diào)用super.h的invoke方法并傳入2中對(duì)應(yīng)的Method對(duì)象和參數(shù)。
4.通過(guò)查看Proxy的代碼可知super.h為一個(gè)InvocationHandler 引用。而該InvocationHandler 引用就是從Proxy0的構(gòu)造方法中傳入。
所以說(shuō)白了,該代理類就是構(gòu)造方法傳入的InvocationHandler 對(duì)象的代理,無(wú)論調(diào)用什么方法,都會(huì)調(diào)用InvocationHandler 對(duì)象的invoke方法并傳入方法對(duì)應(yīng)的Method對(duì)象和參數(shù)。
三:通過(guò)反射從Class對(duì)象創(chuàng)建對(duì)應(yīng)的具體Book代理的實(shí)例對(duì)象:
有了代理類的Class對(duì)象,就可以通過(guò)反射創(chuàng)建對(duì)象了。
Constructor constructor = bookProxyClass.getConstructor(InvocationHandler.class);
//注意到上文的代理類只有一個(gè)有參構(gòu)造方法,參數(shù)為InvocationHandler類型
Book bookProxy = (Book) constructor.newInstance(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String s = (String) args[0];
return "i am proxy of " + s;
}
});
這里終于得到了通過(guò)Book轉(zhuǎn)換得到的對(duì)象bookProxy。
四.調(diào)用Book代理的實(shí)例對(duì)象的代理方法(即Book接口的方法)
String a = bookProxy.read("紅樓夢(mèng)");
System.out.println(a);
控制臺(tái)打印出結(jié)果:
i am proxy of 紅樓夢(mèng)
正是三傳入的InvocationHandler對(duì)象invoke方法指定的邏輯。
以上2,3步其實(shí)可以合成一步:
Book bookProxy = Proxy.newProxyInstance(Book.class.getClassLoader(),new Class[]{Book.class} ,
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String s = (String) args[0];
return "i am proxy of " + s;
}
});
這就是我們經(jīng)常見(jiàn)到的動(dòng)態(tài)代理的代碼,但是為了解釋清楚內(nèi)在原理流程,所以我拆為了2步。
那么動(dòng)態(tài)代理有什么作用呢?
《深入理解Java虛擬機(jī)》一書(shū)中給出的答案是:
結(jié)合Retrofit來(lái)講,就是在具體的Http請(qǐng)求還未知的情況下,就確定了Http的請(qǐng)求代碼。
3.Retrofit中的動(dòng)態(tài)代理
Retrofit源碼因?yàn)閺?fù)雜就不在這里詳細(xì)敘述了,只要明白了動(dòng)態(tài)代理原理,它的流程就很容易明白。
就拿上文的Retrofit例子來(lái)說(shuō)吧,在執(zhí)行
GitHubService service = retrofit.create(GitHubService.class);
這一行的時(shí)候,虛擬機(jī)內(nèi)部生成了代理類的Class二進(jìn)制流,并且加載到虛擬機(jī)中形成代理類的Class對(duì)象,再反射得到代理類對(duì)象,而該代理類即實(shí)現(xiàn)了GitHubService,并且持有了InvocationHandler的引用。
當(dāng)調(diào)用
Call<List<Repo>> repos = service.listRepos("octocat");
就會(huì)調(diào)用代理對(duì)象的listRepos方法,通過(guò)上文的分析,它會(huì)調(diào)用
該InvocationHandler引用的對(duì)象的invoke方法,并傳入GitHubService的listRepos的Method對(duì)象。
InvocationHandler引用的對(duì)象的invoke方法會(huì)通過(guò)該Method對(duì)象,得到方法的注解以及參數(shù),得到Http請(qǐng)求的鏈接、請(qǐng)求方法、請(qǐng)求路徑、參數(shù)等請(qǐng)求信息,構(gòu)建一個(gè)OkHttp的請(qǐng)求并執(zhí)行。
4.動(dòng)手實(shí)現(xiàn)一個(gè)迷你Retrofit
所謂紙上得來(lái)終覺(jué)淺,絕知此事要躬行。上文的敘述恐怕可能還是讓人聽(tīng)得云里霧里,那么接下來(lái),我就來(lái)重點(diǎn)圍繞動(dòng)態(tài)代理實(shí)現(xiàn)一個(gè)迷你的Retrofit。這個(gè)迷你Retrofit唯一的功能,就是通過(guò)Get請(qǐng)求請(qǐng)求我的csdn首頁(yè)數(shù)據(jù),主要是為了將Retrofit動(dòng)態(tài)代理相關(guān)的的主要流程闡述清楚~
1.創(chuàng)建一個(gè)Get注解:
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Get {
String value();
}
2.創(chuàng)建Call接口和CallBack回調(diào)接口:
這是代理對(duì)象請(qǐng)求方法返回的對(duì)象,T表示請(qǐng)求的數(shù)據(jù)類型。和Retrofit的Call對(duì)應(yīng)。
public interface Call<T> {
void enqueue(CallBack<T> callBack);
}
這是請(qǐng)求的回調(diào)接口,T表示請(qǐng)求的數(shù)據(jù)類型,和Retrofit的CallBack對(duì)應(yīng)。
public interface CallBack<T> {
void onResponse(T response);
void onFail(Exception e);
}
3.GetBaiduService接口添加Get注解和Path:
這里為了簡(jiǎn)單就不添加參數(shù)了。
public interface GetBaiduService {
@Get("sinat_23092639")
Call<String> read();
}
這里為了簡(jiǎn)單就直接將path寫(xiě)在注解上了。
4.根據(jù)Retrofit源碼創(chuàng)建MiniRetrofit:
public class MiniRetrofit {
String mBaseUrl;
public MiniRetrofit(String baseUrl) {
mBaseUrl = baseUrl;
}
public <T> T create(final Class<T> service) {
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[]{service},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//如果是equals、toString、hashCode方法則直接調(diào)用原來(lái)的方法
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
Call<?> call = null;
//獲取被代理的接口的方法注解
Annotation[] annotations = method.getDeclaredAnnotations();
for (Annotation annotation : annotations) {
//如果找到對(duì)應(yīng)的Get注解,則將Get注解中的url和mBaseUrl拼接一起
if (annotation instanceof Get) {
String path = ((Get) annotation).value();
String url = mBaseUrl + "/" + path;
System.out.println("url:" + url);
//通過(guò)OkHttp請(qǐng)求url,并將結(jié)果回調(diào)到外部
final OkHttpClient okHttpClient = new OkHttpClient().newBuilder().build();
final Request request = new Request.Builder()
.url(url)
.build();
call = new Call<Object>() {
@Override
public void enqueue(final CallBack<Object> callBack) {
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NotNull okhttp3.Call call, @NotNull IOException e) {
callBack.onFail(e);
}
@Override
public void onResponse(@NotNull okhttp3.Call call, @NotNull Response response) throws IOException {
callBack.onResponse(response.body().string());
}
});
}
};
}
}
return call;
}
});
}
static class Builder {
String baseUrl;
public Builder baseUrl(String baseUrl) {
this.baseUrl = baseUrl;
return this;
}
public MiniRetrofit build() {
return new MiniRetrofit(baseUrl);
}
}
}
5.最后添加最外層的請(qǐng)求調(diào)用代碼:
MiniRetrofit miniRetrofit = new MiniRetrofit.Builder().baseUrl("https://blog.csdn.net").build();
GetBaiduService getBaiduService = miniRetrofit.create(GetBaiduService.class);
Call<String> call = getBaiduService.read();
call.enqueue(new CallBack<String>() {
@Override
public void onResponse(String response) {
System.out.println("success:"+response);
}
@Override
public void onFail(Exception e) {
System.out.println(e.toString());
}
});
執(zhí)行程序:
成功請(qǐng)求到了我的csdn首頁(yè)數(shù)據(jù)。
動(dòng)態(tài)代理還有很多用處,比如我之前寫(xiě)過(guò)的通過(guò)反射實(shí)現(xiàn)的仿ButterKnife功能Demo
一文里就講過(guò)通過(guò)動(dòng)態(tài)代理巧妙解決控件綁定點(diǎn)擊監(jiān)聽(tīng)回調(diào)方法的方案。總的來(lái)說(shuō),它提供了很大的靈活性,讓我們?cè)谶\(yùn)行期根據(jù)具體情況做具體的應(yīng)變。