本文涉及java中的一些反射知識,如果你對反射這部分不太熟悉,建議先去了解一下反射知識,或者看一下我的這篇文章 Java反射以及在Android中的使用
代理模式
一、定義
給某一個對象提供一個代理對象,并由代理對象控制對原對象的引用。
舉例說明:
代理模式從字面上就是我們理解的生活中那個中介代理,比如公司A(原對象)為海外公司,消費者B(某一個對象)直接從公司A購買商品需要各種各樣復雜的步驟,這時候就出現(xiàn)了代理人C(代理對象),讓他來替我們去處理那些復雜的步驟,我們只需要告訴代理人C我們需要什么商品就可以了,由代理人C去跟公司A進行購買,消費只需要等著收快遞,其他的不用關心。
二、使用代理好處
1、通過引入代理對象的方式 來間接訪問目標對象,防止直接訪問目標對象給系統(tǒng)帶來的不必要復雜性;
2、通過代理對象對原有的業(yè)務增強;
三、UML圖
公共接口角色:定義了委托角色和代理角色的共同接口或者抽象類。
委托角色(公司A) :實現(xiàn)或者繼承抽象主題角色,定義實現(xiàn)具體業(yè)務邏輯的實現(xiàn)。
代理角色(代理C) : 實現(xiàn)或者繼承抽象主題角色,持有委托角色的引用,控制和限制委托角色的實現(xiàn),并且擁有自己的處理方法(預處理和善后)。
特點:
委托角色和代理角色共同繼承同一個接口或者實現(xiàn)抽象類
代理角色持有委托角色對象的引用
四、靜態(tài)代理
根據(jù)上面的例子,我們來實現(xiàn)一個靜態(tài)代理:
靜態(tài)代理在使用時,需要先定義接口或者抽象類,委托類與代理類一起實現(xiàn)相同的接口或者是繼承相同抽象類。一般來說,委托類和代理類是一對一的關系,當然一個代理對象對應多個委托類也是可以的。
步驟 :定義公共接口——>創(chuàng)建委托類(公司A)——>創(chuàng)建代理類(代理C)——>訪問(消費者執(zhí)行)
1、定義公共接口
/**
* @author : EvanZch
* description: 抽象主題角色,商家和代理銷售這個操作
**/
public interface IFactoryA {
void saleManTools(String size);
}
2、委托類 (公司A)
/**
* @author : EvanZch
* description: 公司A, 實現(xiàn)IFactory
**/
public class FactoryA implements IFactoryA {
/**
* 實現(xiàn)具體的業(yè)務邏輯
*
* @param size
*/
@Override
public void saleManTools(String size) {
System.out.println("公司A——>出貨:" + size + "MM");
}
}
3、代理類 (代理C)
/**
* @author : EvanZch
* description: 代理C,實現(xiàn)IFactory,并持有真實對象FactoryA引用
**/
public class ProxyC implements IFactoryA {
private IFactoryA factory;
/**
* 持有被代理角色的引用
* @param iFactory
*/
public void setFactory(IFactoryA iFactory) {
this.factory = iFactory;
}
/**
* 對真實對象方法進行增強
* @param size
*/
@Override
public void saleManTools(String size) {
doBefore();
factory.saleManTools(size);
doAfter();
}
private void doBefore() {
System.out.println("代理C——>根據(jù)客戶需求定制方案");
}
private void doAfter() {
System.out.println("代理C——>收集使用反饋");
}
}
在執(zhí)行被代理類方法前,可以進行功能拓展,符合開閉原則。
4、消費者
/**
* @author : EvanZch
* description: 消費者B
**/
public class ConsumerB {
public static void main(String[] args) {
IFactoryA factoryA = new FactoryA();
ProxyC proxyC = new ProxyC(factoryA);
proxyC.saleManTools("36D");
}
}
結果:
如果這個時候出現(xiàn)了專賣女性用品的公司B,我們需要按照下面步驟再走一遍
定義公共接口——>創(chuàng)建委托類——>創(chuàng)建代理類——>訪問
優(yōu)點:
靜態(tài)代理好處就是能對目標對象方法進行功能拓展。
上面例子中可以看到,海外公司只負責發(fā)貨,代理類可以在不改動委托類的情況下對目標人群進行需求方案定制和使用情況反饋收集工作,對委托類的方法進行了功能拓展。
缺點:
靜態(tài)代理,一對一(一個代理只代理一個公司)則會出現(xiàn)代理對象量多、代碼量大,從而導致代碼復雜,可維護性差的問題,一對多(一個代理代理多個公司)則代理對象會出現(xiàn)擴展能力差的問題。
可以看到我們公共接口是銷售男性用品,如果后續(xù)需求增加女性用品是不是又要改動接口或者增加接口?接口一改動,代理類也要跟著改,牽一發(fā)而動全身,當需求越來越多越來越復雜的時候,就會使整個代碼臃腫,并且維護性變差。
五、動態(tài)代理
使用動態(tài)代理不必要自己在去實現(xiàn)代理類,只需要一個動態(tài)代理類 (代理公司) 就可以讓程序運行在期間動態(tài)的創(chuàng)建接口的實現(xiàn)。
動態(tài)代理實現(xiàn)的關鍵是需要使用 InvocationHandler
接口和通過 Proxy
類動態(tài)創(chuàng)建對象。
步驟 :定義公共接口——>創(chuàng)建委托類(公司A)——>只需要創(chuàng)建一個動態(tài)代理類(代理公司)——>訪問(消費者執(zhí)行)
1、InvocationHandler 接口
處理動態(tài)代理類對象方法的調用,每個動態(tài)代理類都會關聯(lián)一個。
package java.lang.reflect;
/**
* {@code InvocationHandler} is the interface implemented by
* the <i>invocation handler</i> of a proxy instance.
*
* <p>Each proxy instance has an associated invocation handler.
* When a method is invoked on a proxy instance, the method
* invocation is encoded and dispatched to the {@code invoke}
* method of its invocation handler.
*
* @author Peter Jones
* @see Proxy
* @since 1.3
*/
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
2、Proxy類
用來創(chuàng)建代理對象的類,是所有動態(tài)代理類的父類,主要使用 newProxyInstance
方法
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,
InvocationHandler h)
先貼動態(tài)代理類實現(xiàn)的代碼:
/**
* @author : EvanZch
* description: 動態(tài)代理類,必須實現(xiàn)InvocationHandler接口
**/
public class ProxyCompany implements InvocationHandler {
/**
* 依舊持有真實對象
*/
private Object mFactory;
public void setFactory(Object factory) {
this.mFactory = factory;
}
/**
* 獲取動態(tài)代理對象
*/
public Object getDynamicProxy() {
/**
* 拿到動態(tài)代理對象
* ClassLoader loader :真實對象的ClassLoader
* Class<?>[] interfaces : 真實對象實現(xiàn)的接口
* InvocationHandler h : InvocationHandler對象
*/
return Proxy.newProxyInstance(mFactory.getClass().getClassLoader()
, mFactory.getClass().getInterfaces(), this);
}
/**
* InvocationHandler 接口方法
*
* @param proxy 代理類本身
* @param method 我們所要調用某個對象真實的方法的 Method 對象
* @param args method 對象中本身需要傳入的參數(shù)
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
doBefore();
// 調用真實對象方法
Object result = method.invoke(mFactory, args);
doAfter();
return result;
}
private void doBefore() {
System.out.println("代理公司——>方案制定");
}
private void doAfter() {
System.out.println("代理公司——>收集反饋");
}
}
調用動態(tài)代理
// 創(chuàng)建動態(tài)代理對象
ProxyCompany proxyCompany = new ProxyCompany();
// 公司A
IFactoryA factoryA = new FactoryA();
// 動態(tài)代理引入真實對象
proxyCompany.setFactory(factoryA);
// 動態(tài)的創(chuàng)建代理類
IFactoryA proxyA = (IFactoryA) proxyCompany.getDynamicProxy();
proxyA.saleManTools("F");
結果:
仍然獲取正確結果,縱觀整個過程,我們并沒有實質的創(chuàng)建一個代理類,整個過程只需要一個動態(tài)代理類就能完成,如果這個時候我們出現(xiàn)了公司B,我們只需要執(zhí)行的步驟 定義公共接口——>創(chuàng)建委托類(公司B)——>訪問(消費者執(zhí)行) 具體代碼就不貼了,跟公司A類似,我把調用代碼貼在一起,你品,細細的品。
// 創(chuàng)建動態(tài)代理對象
ProxyCompany proxyCompany = new ProxyCompany();
// 公司A IFactoryA:公共接口 FactoryA:委托類(公司A)
IFactoryA factoryA = new FactoryA();
// 動態(tài)代理引入真實對象
proxyCompany.setFactory(factoryA);
// 動態(tài)的創(chuàng)建代理類
IFactoryA proxyA = (IFactoryA) proxyCompany.getDynamicProxy();
proxyA.saleManTools("F");
// 公司B IFactoryB:公共接口 FactoryB : 委托類(公司B)
IFactoryB factoryB = new FactoryB();
proxyCompany.setFactory(factoryB);
IFactoryB proxyB = (IFactoryB) proxyCompany.getDynamicProxy();
proxyB.saleWomanTool(180);
結果:
不知道各位看官可有一點感悟,我們可以看到,就算這個時候出現(xiàn)了公司B,我們整個過程也沒有創(chuàng)建真實的代理對象,而是直接通過一個動態(tài)代理類中 proxyCompany.getDynamicProxy()
來動態(tài)的獲取我們的代理類對象。既然是動態(tài)代理,那代理類肯定存在,只是jdk動態(tài)的給我們生成,那真實的代理類是誰?怎么創(chuàng)建的?
我們先來看代理類是誰這個問題,我們對剛剛的代碼進行debug調試
從日志輸入信息里面可以看到,jdk為在運行時分別給我們的 IFactoryA
和 IFactoryB
生成了名字為$Proxy0
和$Proxy1
的代理類,那它在哪產生的?帶著這個問題,我們開始深入。
六、源碼分析
我們是通過 這個方法來獲取動態(tài)代理對象,那我們從這里切入,先看看newProxyInstance
做了啥?
如果你對反射熟悉的話,圖片中標注的幾處你應該很容易知道在干嘛。
A:獲取類的Class對象
B:反射獲取其構造方法
C:遍歷構造賦予權限
D:返回該類的實例
這里出現(xiàn)的類,沒的說,肯定就是我們需要的那個實際的代理類,我們再看一下 getProxyClass0
方法做了啥?
感覺貼圖比貼代碼舒服,就直接貼圖了,這個方法很簡單,先對接口數(shù)目進行判斷,65535這個數(shù)字搞Android的就很熟悉了吧,方法數(shù)不能超過65535,這不是我們本文討論的關鍵,關鍵看 proxyClassCache.get(loader, interfaces)
我們看到這個方法傳入了我們設置的 ClassLoader
參數(shù) 和 interfaces
參數(shù) 進入 get
方法里面
我們看到標識這里,傳入了我們的參數(shù),應該是一行關鍵性代碼,我們再看它做了啥?
可以看到它是一個接口方法,這個時候我們需要再去找方法的實現(xiàn)類
可以看到 apply
方法實現(xiàn)類有 KeyFactory
和 ProxyClassFactory
聰明你的看名字也應該知道,我們只需要關注 ProxyClassFactory
這個類
這里隱隱約約出現(xiàn)了 proxyName
, 還記得我們前面debug調試出現(xiàn)的 $Proxy0
和$Proxy1
嗎?
private static final String proxyClassNamePrefix = "$Proxy";
String proxyName = proxyPkg + proxyClassNamePrefix + num;
看到 $Proxy
了嗎? 原來名字就是從這里出來的
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
這里通過ProxyGenerator
類生成指定的代理類字節(jié)數(shù)組。
其實jdk生成.class文件的內容就是一串串字節(jié)數(shù)組
defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);
// 這是一個 native 方法
private static native Class<?> defineClass0(ClassLoader loader, String name,
byte[] b, int off, int len);
再通過 defineClass0方法
通過指定ClassLoader
生成代理類的Class對象,到這里,我們前面關于動態(tài)產生的代理類怎么產生的問題也就解決了。
既然 ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags)
方法生成指定代理類的字節(jié)數(shù)組,那我們可以通過這個方法來看看,具體內容是啥?
我們通過 generateProxyClass
獲取到字節(jié)數(shù)組,并保存到本地。
public static void generateProxyClass(String proxyName, Class clazz) {
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces(), 1);
String paths = clazz.getResource(".").getPath();
System.out.println(paths);
FileOutputStream out = null;
try {
out = new FileOutputStream(paths + proxyName + ".class");
out.write(proxyClassFile);
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
將我們動態(tài)產生的類,用上面的方法保存到本地。
generateProxyClass(proxyA.getClass().getSimpleName(), factoryA.getClass());
generateProxyClass(proxyB.getClass().getSimpleName(), factoryB.getClass());
可以看到生成的兩個class文件,名字剛好跟我們前面debug看到的一直,我們反編譯文件看一下里面是啥。
idea 打開.class文件自動進行反編譯。
$Proxy0
public class $Proxy0 extends Proxy implements IFactoryA {
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})).booleanValue();
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void saleManTools(String var1) throws {
try {
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)).intValue();
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
m3 = Class.forName("com.evan.proxy.staticProxy.IFactoryA").getMethod("saleManTools", new Class[]{Class.forName("java.lang.String")});
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
關鍵信息:
1、
$Proxy0
繼承至Proxy
2、實現(xiàn)了
IFactoryA
接口3、實現(xiàn)了我們接口里面的方法 :saleManTools(String var1)
可以看到我們動態(tài)產生的代理類 $Proxy0
繼承至 Proxy
,前面也說過,Proxy
是所有動態(tài)代理類的父類,所有動態(tài)代理類都需要繼承它,通過生成的文件,我們可以證實這點。
前面提到代理模式特點一直就是 代理類和委托類要同時實現(xiàn)一個接口或者實現(xiàn)抽象類,這里可以看到,我們創(chuàng)建的動態(tài)代理類同樣也實現(xiàn)了我們的 IFactoryA
接口。
我們再看一下saleManTools
方法的實現(xiàn)
public final void saleManTools(String var1) throws {
try {
super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
關鍵信息:
super.h.invoke(this, m3, new Object[]{var1});
這個 h
是啥?在動態(tài)產生的代理類 $Proxy0
沒看到這個參數(shù),我們再在其父類 Proxy
中查看
就是我們前面動態(tài)代理類中實現(xiàn)的 InvocationHandler
接口。所以在 saleManTools
方法中,再調用了 InvocationHandler
接口的 invoke
方法,我們再回憶一下前面寫動態(tài)代理類時候,怎么處理invoke
方法的?回憶不起來,我就再貼一次!
/**
* InvocationHandler 接口方法
*
* @param proxy 代理類本身
* @param method 我們所要調用某個對象真實的方法的 Method 對象
* @param args method 對象中本身需要傳入的參數(shù)
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
doBefore();
// 調用真實對象方法
Object result = method.invoke(mFactory, args);
doAfter();
return result;
}
再看 saleManTools
方法實現(xiàn)中的這行,你對比著看,你品,細細的品。
super.h.invoke(this, m3, new Object[]{var1});
那這里的m3不就是Method嘛
m3 = Class.forName("com.evan.proxy.staticProxy.IFactoryA").getMethod("saleManTools", new Class[]{Class.forName("java.lang.String")});
再看m3的值,不就是通過反射拿到 我們自己定義的IFactoryA
接口的 saleManTools
方法???看到這里,再回頭去看看我們前面的動態(tài)代理類,你對 InvocationHandler
和 Proxy
這兩個關鍵類應該就有了更清晰的認識了,如果沒有,就再看一遍??
好了,以上源碼分析內容基本就是jdk對靜態(tài)代理的實現(xiàn)(這個車是不是剎的有點快,哈哈)。
七、動態(tài)代理在Android中的運用
retrofit
這個網(wǎng)絡請求庫我相信搞Android的大哥們應該都用過吧,我們一般怎么操作?
1、編寫 xxxApi 接口
public interface xxxApi {
String HOST = "xxxxxxxxxx";
@POST("app/xxxx")
@FormUrlEncoded
Observable<BaseResponse<String>> sendEmailCode(@Field("email") String email);
}
2、初始化 retrofit
:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://xxxxxx")
.build();
3、動態(tài)創(chuàng)建 xxxApi
實例
xxxApi service = retrofit.create(xxxApi.class);
service.sendEmailCode(xxxx);
有沒有很熟悉,我們 create
的時候不是只傳了一個 interface
進去嗎?怎么就可以直接通過返回的實例調用方法了呢?跟我們前面的動態(tài)代理是不是有幾分相似?我們去看看Retrofit
的源碼,看他 Create
到底操作了啥?
我們在github上可以看到 retrofit 的 create 方法
看到 Proxy.newProxyInstance
這個方法,就應該很清楚了,證實我們前面的猜測,至于具體怎么操作的跟前面的類似,這里就不再分析了。