本文涉及java中的一些反射知識,如果你對反射這部分不太熟悉,建議先去了解一下反射知識,或者看一下我的這篇文章 Java反射以及在Android中的使用
代理模式
一、定義
給某一個對象提供一個代理對象,並由代理對象控制對原對象的引用。
舉例說明:
代理模式從字面上就是我們理解的生活中那個中介代理,比如公司A(原對象)爲海外公司,消費者B(某一個對象)直接從公司A購買商品需要各種各樣複雜的步驟,這時候就出現了代理人C(代理對象),讓他來替我們去處理那些複雜的步驟,我們只需要告訴代理人C我們需要什麼商品就可以了,由代理人C去跟公司A進行購買,消費只需要等着收快遞,其他的不用關心。
二、使用代理好處
1、通過引入代理對象的方式 來間接訪問目標對象,防止直接訪問目標對象給系統帶來的不必要複雜性;
2、通過代理對象對原有的業務增強;
三、UML圖
公共接口角色:定義了委託角色和代理角色的共同接口或者抽象類。
委託角色(公司A) :實現或者繼承抽象主題角色,定義實現具體業務邏輯的實現。
代理角色(代理C) : 實現或者繼承抽象主題角色,持有委託角色的引用,控制和限制委託角色的實現,並且擁有自己的處理方法(預處理和善後)。
特點:
委託角色和代理角色共同繼承同一個接口或者實現抽象類
代理角色持有委託角色對象的引用
四、靜態代理
根據上面的例子,我們來實現一個靜態代理:
靜態代理在使用時,需要先定義接口或者抽象類,委託類與代理類一起實現相同的接口或者是繼承相同抽象類。一般來說,委託類和代理類是一對一的關係,當然一個代理對象對應多個委託類也是可以的。
步驟 :定義公共接口——>創建委託類(公司A)——>創建代理類(代理C)——>訪問(消費者執行)
1、定義公共接口
/**
* @author : EvanZch
* description: 抽象主題角色,商家和代理銷售這個操作
**/
public interface IFactoryA {
void saleManTools(String size);
}
2、委託類 (公司A)
/**
* @author : EvanZch
* description: 公司A, 實現IFactory
**/
public class FactoryA implements IFactoryA {
/**
* 實現具體的業務邏輯
*
* @param size
*/
@Override
public void saleManTools(String size) {
System.out.println("公司A——>出貨:" + size + "MM");
}
}
3、代理類 (代理C)
/**
* @author : EvanZch
* description: 代理C,實現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——>根據客戶需求定製方案");
}
private void doAfter() {
System.out.println("代理C——>收集使用反饋");
}
}
在執行被代理類方法前,可以進行功能拓展,符合開閉原則。
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");
}
}
結果:
如果這個時候出現了專賣女性用品的公司B,我們需要按照下面步驟再走一遍
定義公共接口——>創建委託類——>創建代理類——>訪問
優點:
靜態代理好處就是能對目標對象方法進行功能拓展。
上面例子中可以看到,海外公司只負責發貨,代理類可以在不改動委託類的情況下對目標人羣進行需求方案定製和使用情況反饋收集工作,對委託類的方法進行了功能拓展。
缺點:
靜態代理,一對一(一個代理只代理一個公司)則會出現代理對象量多、代碼量大,從而導致代碼複雜,可維護性差的問題,一對多(一個代理代理多個公司)則代理對象會出現擴展能力差的問題。
可以看到我們公共接口是銷售男性用品,如果後續需求增加女性用品是不是又要改動接口或者增加接口?接口一改動,代理類也要跟着改,牽一髮而動全身,當需求越來越多越來越複雜的時候,就會使整個代碼臃腫,並且維護性變差。
五、動態代理
使用動態代理不必要自己在去實現代理類,只需要一個動態代理類 (代理公司) 就可以讓程序運行在期間動態的創建接口的實現。
動態代理實現的關鍵是需要使用 InvocationHandler
接口和通過 Proxy
類動態創建對象。
步驟 :定義公共接口——>創建委託類(公司A)——>只需要創建一個動態代理類(代理公司)——>訪問(消費者執行)
1、InvocationHandler 接口
處理動態代理類對象方法的調用,每個動態代理類都會關聯一個。
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類
用來創建代理對象的類,是所有動態代理類的父類,主要使用 newProxyInstance
方法
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,
InvocationHandler h)
先貼動態代理類實現的代碼:
/**
* @author : EvanZch
* description: 動態代理類,必須實現InvocationHandler接口
**/
public class ProxyCompany implements InvocationHandler {
/**
* 依舊持有真實對象
*/
private Object mFactory;
public void setFactory(Object factory) {
this.mFactory = factory;
}
/**
* 獲取動態代理對象
*/
public Object getDynamicProxy() {
/**
* 拿到動態代理對象
* ClassLoader loader :真實對象的ClassLoader
* Class<?>[] interfaces : 真實對象實現的接口
* InvocationHandler h : InvocationHandler對象
*/
return Proxy.newProxyInstance(mFactory.getClass().getClassLoader()
, mFactory.getClass().getInterfaces(), this);
}
/**
* InvocationHandler 接口方法
*
* @param proxy 代理類本身
* @param method 我們所要調用某個對象真實的方法的 Method 對象
* @param args method 對象中本身需要傳入的參數
* @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("代理公司——>收集反饋");
}
}
調用動態代理
// 創建動態代理對象
ProxyCompany proxyCompany = new ProxyCompany();
// 公司A
IFactoryA factoryA = new FactoryA();
// 動態代理引入真實對象
proxyCompany.setFactory(factoryA);
// 動態的創建代理類
IFactoryA proxyA = (IFactoryA) proxyCompany.getDynamicProxy();
proxyA.saleManTools("F");
結果:
仍然獲取正確結果,縱觀整個過程,我們並沒有實質的創建一個代理類,整個過程只需要一個動態代理類就能完成,如果這個時候我們出現了公司B,我們只需要執行的步驟 定義公共接口——>創建委託類(公司B)——>訪問(消費者執行) 具體代碼就不貼了,跟公司A類似,我把調用代碼貼在一起,你品,細細的品。
// 創建動態代理對象
ProxyCompany proxyCompany = new ProxyCompany();
// 公司A IFactoryA:公共接口 FactoryA:委託類(公司A)
IFactoryA factoryA = new FactoryA();
// 動態代理引入真實對象
proxyCompany.setFactory(factoryA);
// 動態的創建代理類
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);
結果:
不知道各位看官可有一點感悟,我們可以看到,就算這個時候出現了公司B,我們整個過程也沒有創建真實的代理對象,而是直接通過一個動態代理類中 proxyCompany.getDynamicProxy()
來動態的獲取我們的代理類對象。既然是動態代理,那代理類肯定存在,只是jdk動態的給我們生成,那真實的代理類是誰?怎麼創建的?
我們先來看代理類是誰這個問題,我們對剛剛的代碼進行debug調試
從日誌輸入信息裏面可以看到,jdk爲在運行時分別給我們的 IFactoryA
和 IFactoryB
生成了名字爲$Proxy0
和$Proxy1
的代理類,那它在哪產生的?帶着這個問題,我們開始深入。
六、源碼分析
我們是通過 這個方法來獲取動態代理對象,那我們從這裏切入,先看看newProxyInstance
做了啥?
如果你對反射熟悉的話,圖片中標註的幾處你應該很容易知道在幹嘛。
A:獲取類的Class對象
B:反射獲取其構造方法
C:遍歷構造賦予權限
D:返回該類的實例
這裏出現的類,沒的說,肯定就是我們需要的那個實際的代理類,我們再看一下 getProxyClass0
方法做了啥?
感覺貼圖比貼代碼舒服,就直接貼圖了,這個方法很簡單,先對接口數目進行判斷,65535這個數字搞Android的就很熟悉了吧,方法數不能超過65535,這不是我們本文討論的關鍵,關鍵看 proxyClassCache.get(loader, interfaces)
我們看到這個方法傳入了我們設置的 ClassLoader
參數 和 interfaces
參數 進入 get
方法裏面
我們看到標識這裏,傳入了我們的參數,應該是一行關鍵性代碼,我們再看它做了啥?
可以看到它是一個接口方法,這個時候我們需要再去找方法的實現類
可以看到 apply
方法實現類有 KeyFactory
和 ProxyClassFactory
聰明你的看名字也應該知道,我們只需要關注 ProxyClassFactory
這個類
這裏隱隱約約出現了 proxyName
, 還記得我們前面debug調試出現的 $Proxy0
和$Proxy1
嗎?
private static final String proxyClassNamePrefix = "$Proxy";
String proxyName = proxyPkg + proxyClassNamePrefix + num;
看到 $Proxy
了嗎? 原來名字就是從這裏出來的
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
這裏通過ProxyGenerator
類生成指定的代理類字節數組。
其實jdk生成.class文件的內容就是一串串字節數組
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對象,到這裏,我們前面關於動態產生的代理類怎麼產生的問題也就解決了。
既然 ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags)
方法生成指定代理類的字節數組,那我們可以通過這個方法來看看,具體內容是啥?
我們通過 generateProxyClass
獲取到字節數組,並保存到本地。
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();
}
}
}
將我們動態產生的類,用上面的方法保存到本地。
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、實現了
IFactoryA
接口3、實現了我們接口裏面的方法 :saleManTools(String var1)
可以看到我們動態產生的代理類 $Proxy0
繼承至 Proxy
,前面也說過,Proxy
是所有動態代理類的父類,所有動態代理類都需要繼承它,通過生成的文件,我們可以證實這點。
前面提到代理模式特點一直就是 代理類和委託類要同時實現一個接口或者實現抽象類,這裏可以看到,我們創建的動態代理類同樣也實現了我們的 IFactoryA
接口。
我們再看一下saleManTools
方法的實現
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
是啥?在動態產生的代理類 $Proxy0
沒看到這個參數,我們再在其父類 Proxy
中查看
就是我們前面動態代理類中實現的 InvocationHandler
接口。所以在 saleManTools
方法中,再調用了 InvocationHandler
接口的 invoke
方法,我們再回憶一下前面寫動態代理類時候,怎麼處理invoke
方法的?回憶不起來,我就再貼一次!
/**
* InvocationHandler 接口方法
*
* @param proxy 代理類本身
* @param method 我們所要調用某個對象真實的方法的 Method 對象
* @param args method 對象中本身需要傳入的參數
* @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
方法實現中的這行,你對比着看,你品,細細的品。
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
方法???看到這裏,再回頭去看看我們前面的動態代理類,你對 InvocationHandler
和 Proxy
這兩個關鍵類應該就有了更清晰的認識了,如果沒有,就再看一遍??
好了,以上源碼分析內容基本就是jdk對靜態代理的實現(這個車是不是剎的有點快,哈哈)。
七、動態代理在Android中的運用
retrofit
這個網絡請求庫我相信搞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、動態創建 xxxApi
實例
xxxApi service = retrofit.create(xxxApi.class);
service.sendEmailCode(xxxx);
有沒有很熟悉,我們 create
的時候不是隻傳了一個 interface
進去嗎?怎麼就可以直接通過返回的實例調用方法了呢?跟我們前面的動態代理是不是有幾分相似?我們去看看Retrofit
的源碼,看他 Create
到底操作了啥?
我們在github上可以看到 retrofit 的 create 方法
看到 Proxy.newProxyInstance
這個方法,就應該很清楚了,證實我們前面的猜測,至於具體怎麼操作的跟前面的類似,這裏就不再分析了。