代理模式
看完文章你能學(xué)到什么?搞懂代理模式矿微,Retrofit代理模式的使用(其實我就是因為沒看懂巡通,才學(xué)的),文章有點長但是邏輯很簡單
代理模式可以在不修改被代理對象的基礎(chǔ)上,通過擴展代理類礼搁,進行一些功能的附加與增強宙搬。 值得注意的是笨腥,代理類和被代理類應(yīng)該共同實現(xiàn)一個接口,或者是共同繼承某個類害淤。----Frank909 (并不是我總結(jié)的,但是我挺認(rèn)同的)
簡單來說就是:你朋友圈的小花賣面膜扇雕,面膜100塊一盒,但是經(jīng)過小花的代理變300了窥摄,此時面膜還是那個面膜镶奉,就是到手的時候總感覺有點貴!
靜態(tài)代理
把上面說所的崭放,用程序來表達:
定義一個產(chǎn)品的接口哨苛,產(chǎn)品的屬性是價格
public interface IProduct {
void price();
}
創(chuàng)建面膜類實現(xiàn)產(chǎn)品接口
public class FacialMask implements IProduct {
@Override
public void price() {
System.out.println("出廠價格100塊");
}
}
最后創(chuàng)建一個代理類:小花
public class XiaoHuaProxy implements IProduct {
private IProduct product;
public XiaoHuaProxy(IProduct product) {
this.product = product;
}
@Override
public void price() {
System.out.println("我是小fafa");
product.price();
System.out.println("面膜現(xiàn)在300塊,100不存在的");
}
}
main 方法
FacialMask facialMask = new FacialMask();
XiaoHuaProxy xiaoHuaProxy = new XiaoHuaProxy(facialMask);
xiaoHuaProxy.price();
運行一下
我是小fafa
出廠價格100塊
面膜現(xiàn)在300塊币砂,100不存在的
然后這就是靜態(tài)代理建峭,因為代理類是靜態(tài)生成的,以上要知道的一點就是:
代理模式可以在不修改被代理對象的基礎(chǔ)上决摧,通過擴展代理類亿蒸,進行一些功能的附加與增強。
有人可能會杠一下掌桩,老子就不實現(xiàn)同一個接口躏救,同一個繼承對象提岔,照樣能實現(xiàn)领突,所以下面看下動態(tài)代理
動態(tài)代理
動態(tài)代理和靜態(tài)代理功能上是沒有任何區(qū)別的险耀,動態(tài)代理我的理解:動態(tài)生成代理類,完成代理的功能则拷。小花不再被顯示創(chuàng)建出來贡蓖,并不是面膜可以被小花代理曹鸠,那么也可以被小明代理,然后動態(tài)代理就是為了生成多個不同樣的代理對象斥铺。
但是在這之前要補充一個反射的小知識彻桃,才能更好的看下去
反射 Method 方法的執(zhí)行:
我們在應(yīng)用反射的時候往往是想執(zhí)行一些我們無法使用的類或者方法;
Method.invoke(Object obj, Object... args);
public Object invoke(Object obj, Object... args) {}
invoke() 方法中第一個參數(shù) Object 實質(zhì)上是 Method 所依附的 Class 對應(yīng)的類的實例仅父,如果這個方法是一個靜態(tài)方法叛薯,那么 ojb 為 null浑吟,后面的可變參數(shù) Object 對應(yīng)的自然就是參數(shù)笙纤。這里看不懂沒事,下面有例子组力;
舉個例子省容,F(xiàn)acialMask(面膜類)
//不使用反射,獲取對象和執(zhí)行方法
FacialMask facialMask = new FacialMask();
facialMask.price();
//使用反射實例化獲取類對象燎字,有很多種方式
FacialMask reflectMaxk = FacialMask.class.newInstance();
//不使用反射直接調(diào)用
reflectMaxk.price();
//突然發(fā)現(xiàn)price是個私有方法腥椒,可以通過反射執(zhí)行這個方法
Method price = FacialMask.class.getMethod("price");
price.setAccessible(true);//把類的可見性打開,即使是私有也能調(diào)用
//參數(shù)一候衍,就是要調(diào)用類的對象笼蛛,靜態(tài)方法可以傳null,參數(shù)二:調(diào)用方法的參數(shù),一定是要按順尋的蛉鹿,沒有可以不寫
Object invoke = price.invoke(reflectMaxk, new Class[]{});//并且返回方法的返回值
了解這么多看下面這個文章就夠了滨砍,如果想深入了解推薦看《細(xì)說反射,Java 和 Android 開發(fā)者必須跨越的坎》
Proxy.newProxyInstance 生成代理類,完成代理的功能
下面用程序演示
public static void main(String[] args) {
FacialMask facialMask = new FacialMask();
IProduct proxyInstance = (IProduct) Proxy.newProxyInstance(FacialMask.class.getClassLoader(), new Class[]{IProduct.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我是小fafa");
Object invoke = method.invoke(facialMask, args);
System.out.println("面膜現(xiàn)在300塊妖异,100不存在的");
return invoke;
}
});
proxyInstance.price();
}
我們使用Proxy.newProxyInstance()取代了XiaoHuaProxy類惋戏,看下輸出結(jié)果
我是小fafa
出廠價格100塊
面膜現(xiàn)在300塊,100不存在的
源碼中是這樣描述這個類的他膳,返回一個指定接口的代理類實例响逢,這個實例的方法都會去調(diào)用InvocationHandler。
- 參數(shù)一:ClassLoader 類加載器棕孙,這里可以傳代理類要實現(xiàn)的接口的ClassLoader
這里大致介紹下舔亭,類加載器默認(rèn)有三個:Bootstrap ClassLoader 最頂層的加載類;Extention ClassLoader 擴展的類加載器蟀俊;Appclass Loader也稱為SystemAppClass 加載當(dāng)前應(yīng)用的classpath的所有類钦铺,一般你寫的程序都是由這個類加載的,自定義類加載器默認(rèn)的父類(并不是繼承關(guān)系)是Appclass Loader
,詳細(xì)可以看: 《一看你就懂欧漱,超詳細(xì)java中的ClassLoader詳解》
- 參數(shù)二:Class<?>[] 要實現(xiàn)的接口职抡,因為一個類可以實現(xiàn)多個接口,所以這里是個數(shù)組
- 參數(shù)三:InvocationHandler 這個就是接口代理類執(zhí)行方法的回調(diào)
//源碼 jdk 8 基礎(chǔ)上
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
//刪除一些校驗參數(shù)合法的代碼
/**
* Look up or generate the designated proxy class.
* 生成代理類或者查出已生成代理類
*/
Class<?> cl = getProxyClass0(loader, intfs);
/**
* Invoke its constructor with the designated invocation handler.
* 把代理類實例化误甚,返回去
*/
//刪除一些try catch
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
//刪除一些校檢
//注意這個地方缚甩,實例化代理類谱净,把== h ==當(dāng)參數(shù)傳進去了,這個== h ==就是咱們實現(xiàn)InvocationHandler的方法
return cons.newInstance(new Object[]{h});
}
獲取生成的字節(jié)碼文件
getProxyClass0()獲取代理類字節(jié)碼文件擅威,主要使用了ProxyGenerator.generateProxyClass()生成了代理類壕探,默認(rèn)在內(nèi)存中,也可以設(shè)置System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true") 獲取本地文件, 通過此語句可以立個flag讓生成字節(jié)碼文件的時候郊丛,輸出本地字節(jié)碼文件,注意這句話一定要在獲取代理類實例之前,
生成的文件在當(dāng)前項目的根目錄李请,對應(yīng)的包名文件夾里
private static final boolean saveGeneratedFiles =
(Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"));//這就是那個flag的獲取
if (saveGeneratedFiles) {//這就是寫入本地的地方
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
try {
int var1 = var0.lastIndexOf(46);
Path var2;
if (var1 > 0) {
Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));
Files.createDirectories(var3);
var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
} else {
var2 = Paths.get(var0 + ".class");
}
Files.write(var2, var4, new OpenOption[0]);//寫到文件里
return null;
} catch (IOException var4x) {
throw new InternalError("I/O exception saving generated file: " + var4x);
}
}
});
廢了這么大勁兒,看下動態(tài)生成的代理類字節(jié)碼文件吧厉熟!
public final class $Proxy0 extends Proxy implements IProduct {
//刪除一些不重要的成員變量
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
//刪除了一些tryCatch
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
}
public final void price() throws {
//刪除了一些tryCatch,
super.h.invoke(this, m3, (Object[])null);
}
public final String toString() throws {
//刪除了一些tryCatch
return (String)super.h.invoke(this, m2, (Object[])null);
}
public final int hashCode() throws {
//刪除了一些tryCatch
return (Integer)super.h.invoke(this, m0, (Object[])null);
}
static {
//刪除了一些tryCatch
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("com.proxy.demo.IProduct").getMethod("price");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
}
}
咱們最主要看的是
構(gòu)造方法
//在這里把val1也就是InvocationHandler傳給了父類Proxy导盅,回憶一下
//在Proxy.newProxyInstance() 源碼中我讓大家注意的地方 return cons.newInstance(new Object[]{h});這個h其實就是
//newProxyInstance(,揍瑟,h)的第三個參數(shù)白翻,也就是InvocationHandler回調(diào)。
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
和這個代理類所有方法中都會調(diào)用的super.h.invoke(this, m3, (Object[])null);
//這個方法的super.h 其實就是剛剛咱們從$Proxy0(InvocationHandler var1)
//傳進去的h绢片,h在這里被invoke,這里是invoke的實現(xiàn)
super.h.invoke(this, m3, (Object[])null);
//也就是它
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我是小fafa");
Object invoke = method.invoke(facialMask, args);
System.out.println("面膜現(xiàn)在300塊滤馍,100不存在的");
return invoke;
}
});
總結(jié)一下,代理類執(zhí)行的任何一個方法都會回調(diào)你的InvocationHandler實現(xiàn)類底循。而且通過ProxyGenerator.generateProxyClass()確實動態(tài)生成了字節(jié)碼文件巢株!
那么看完這些咱們也明白了InvocationHandler中invoke(Object proxy, Method method, Object[] args) 這三個參數(shù)分別是什么,
- 參數(shù)一:Object 生成的代理類對象
- 參數(shù)二:Method 代理類調(diào)用的方法
- 參數(shù)三:args 調(diào)用方法時的參數(shù)
好了熙涤,上面咱們了解了所有關(guān)于生成代理類的一些必要方法阁苞,一定要記好這三個參數(shù),因為要為下面講解Retrofit的create()方法做鋪墊。
疑問
我們會有一個疑問灭袁,動態(tài)代理有什么用猬错,因為最后執(zhí)行的還是 面膜的price方法,所以感覺很雞肋,但是他把面膜的方法毫無侵略性的增加了其它方法,在這里用的最多是AOP 面向切面編程領(lǐng)域,攔截個日志什么的茸歧。
Retrofit中的動態(tài)代理
終于寫到了大Boss o(╥﹏╥)os
先看一段代碼,先了解Retrofit用動態(tài)代理解決了什么問題?下面代碼是OkHttp一個GET請求實例,為什么扯上了OkHttp,因為Retrofit就是為了封裝OkHttp
//第一步:初始化
OkHttpClient client = new OkHttpClient();
//第二步獲取請求對象
Request request = new Request.Builder()
.url(url)
.build();
//執(zhí)行請求獲取服務(wù)器響應(yīng)對象
Response response = client.newCall(request).execute();
response.body().string();
}
再瞅一下Retrofit如何發(fā)起請求的
//第一步,初始化,配置需要的物料,例如請求地址的根路徑,轉(zhuǎn)化工廠,攔截器什么的
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(API_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
// Create an instance of our GitHub API interface.
//第二步使用動態(tài)代理創(chuàng)建代理類對象
GitHub github = retrofit.create(GitHub.class);
// Create a call instance for looking up Retrofit contributors.
// 然后調(diào)用代理類的方法獲取請求對象
Call<List<Contributor>> call = github.contributors("square", "retrofit");
// Fetch and print a list of the contributors to the library.
//第三步 執(zhí)行請求獲取服務(wù)器響應(yīng)對象
List<Contributor> contributors = call.execute().body();
for (Contributor contributor : contributors) {
System.out.println(contributor.login + " (" + contributor.contributions + ")");
}
都是3步走,初始化,創(chuàng)建請求對象,發(fā)送請求獲取數(shù)據(jù).
Retrofit用這兩行替代了OkHttp的這兩行
GitHub github = retrofit.create(GitHub.class);
Call<List<Contributor>> call = github.contributors("square", "retrofit");
public interface GitHub {
@GET("/repos/{owner}/{repo}/contributors")
Call<List<Contributor>> contributors(@Path("owner") String owner,@Path("repo") String repo);
}
okttp
//String url ="baseUrl"+/repos/"+"square"+"/"+"retrofit"+"/contributors"
Request request = new Request.Builder()
.url(url)
.build();
Response response = client.newCall(request).execute();
String result=response.body().string()
return GsonUtlis.from(result);
其實優(yōu)勢已經(jīng)出來了,okhttp url的拼接,是不是由我們來做,body的數(shù)據(jù)是字符串或者其它,是不是還要手動處理成gson或者其它,這還只是get請求,Post請求呢?創(chuàng)建請求對象的時候還要創(chuàng)建請求體,但是Retrofit不管你這些,按照暴露的接口,傳參就好了,幫你返回你想要對象,你什么都不用做,只用處理傳入?yún)?shù),獲取結(jié)果.實現(xiàn)了一個黑盒!用戶只需關(guān)心服務(wù)器需要傳遞什么參數(shù),然后拿到結(jié)果.
進入正題
剛剛演示的是Retrofit干了些什么,現(xiàn)在看如何干的
- 如何動態(tài)產(chǎn)生請求對象Request,弄明白這兩行我們就算大功告成了.
GitHub github = retrofit.create(GitHub.class);
Call<List<Contributor>> call = github.contributors("square", "retrofit");
如果你站在作者的角度出發(fā),你會如何處理,通過一定的規(guī)則產(chǎn)生不同的請求對象呢?而且不能出現(xiàn)很多用戶寫的代碼,因為用戶就是為了省力,才使用這個封裝.
看看作者怎么做的!
- 首先定義了一個接口,讓用戶告訴我,你是什么請求,傳了那些參數(shù),想獲得什么對象.
public interface GitHub {
@GET("/repos/{owner}/{repo}/contributors")
Call<List<Contributor>> contributors(
@Path("owner") String owner,
@Path("repo") String repo);
}
- 拿到這個接口,處理這個數(shù)據(jù),如何通過接口獲取數(shù)據(jù)呢?還記得動態(tài)代理這幾句代碼么?
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我是小fafa");
Object invoke = method.invoke(facialMask, args);
System.out.println("面膜現(xiàn)在300塊倦炒,100不存在的");
return invoke;
}
});
- 參數(shù)一:Object 生成的代理類對象
- 參數(shù)二:Method 代理類調(diào)用的方法,Method方法可以獲取方法上的注解,主要用的這個
- 參數(shù)三:args 調(diào)用方法時的參數(shù),
- 包括方法的返回值
在這里是不是恍然大悟,我想要的東西都有啦.啊哈哈哈哈~
接下來看具體實現(xiàn):
// 源碼2.4.1基礎(chǔ)上
public <T> T create(final Class<T> service) {
// 刪除一些校驗接口合法性的代碼
//第一個參數(shù)類加載器,第二個要代理的接口對象,第三個代理對象執(zhí)行方法時的回調(diào)
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();//獲取用戶哪個平臺,有android和java8
@Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable {
//刪除一些其他跟主邏輯無關(guān)的代碼,主要刪了兩行,一個是執(zhí)行繼承Object的方法:tostring,requals,一個是public 非接口的代碼
return loadServiceMethod(method).invoke(args);
}
});
}
這里最核心的就是loadServiceMethod(method)獲取被代理對象,也就是獲取咱們上面說的面膜,這里你可要注意,這里loadServiceMethod返回的是ServiceMethod,并不是咱們面膜的那個Method了!!!,這里其實就是想借助你調(diào)用的時機獲得一個飽滿的請求對象
ServiceMethod<?> loadServiceMethod(Method method) {
//從緩存中獲取,因為重新獲取一遍還是挺麻煩的
ServiceMethod<?> result = serviceMethodCache.get(method);
if (result != null) return result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);//這里又獲取了一次
if (result == null) {
result = ServiceMethod.parseAnnotations(this, method);//生成ServiceMethod的地方
serviceMethodCache.put(method, result);
}
}
return result;
}
上面主要看的是 ServiceMethod.parseAnnotations(this, method);
abstract class ServiceMethod<T> {
//靜態(tài)方法,
static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
Type returnType = method.getGenericReturnType();//獲取你方法的返回值類型
//刪除一些校驗合法性代碼,
return new HttpServiceMethod.Builder<Object, T>(retrofit, method).build();
}
abstract T invoke(@Nullable Object[] args);
}
上面主要瞅的是new HttpServiceMethod.Builder<Object, T>(retrofit, method).build();
下面是最終的實現(xiàn)!!!!
HttpServiceMethod<ResponseT, ReturnT> build() {
requestFactory = RequestFactory.parseAnnotations(retrofit, method);//獲取方法體上面的注解,獲取請求方式,還有retrofit對象獲取請求拼接的url
callAdapter = createCallAdapter();//獲取CallAdapter,通過這個adapter可以獲取最終的請求對象Call,這里使用適配器模式
responseType = callAdapter.responseType();//返回類型
//刪除一些校驗代碼
responseConverter = createResponseConverter();//獲取responseConverter,它的作用就是采用你提前設(shè)置的Converter,轉(zhuǎn)換出你想要的結(jié)果,例如string->gson
//面膜終于快出來了,我的淚也出來了
return new HttpServiceMethod<>(this);
}
面膜要出來了,這可不是Method的Invoke,而是剛剛new 出來的 HttpServiceMethod.invoke(args)
@Override ReturnT invoke(@Nullable Object[] args) {
return callAdapter.adapt(//動態(tài)產(chǎn)生請求對象,(咱們的面膜)
new OkHttpCall<>(requestFactory, args, callFactory, responseConverter));
}
這里就獲得了咱們的 Call<List<Contributor>> call,這里沒有詳細(xì)展開去分析,獲取requestFactory,callFactory,responseConverter,callAdapter.adapt()的具體實現(xiàn),因為這里和我們動態(tài)代理沒有關(guān)系了,如果想看的話,要看下篇文章了,但是我還沒寫好.O(∩_∩)O哈哈~,最終會分析完Retrofit源碼.
最后
- Retrofit 使用動態(tài)產(chǎn)生的代理,產(chǎn)生了動態(tài)的被代理對象----請求對象(面膜),真特么活學(xué)活用呀!
- 其實看到這里的時候也解決了我的疑惑,Retrofit到底憑什么那么受歡迎,它最大的特點應(yīng)該就是靈活,通過小的改變,適配你想要的功能.
- 能力有限,肯定會有一些錯誤還請批評指正!
-
最后:給你一個么么噠(づ ̄ 3 ̄)づ
此處應(yīng)該有簽名