此篇文章說是通過代理模式來實現(xiàn)簡單的AOP其實只是順帶的涮母,主要目的還是講一下代理模式甲锡,在Android中使用的代理模式主要分為靜態(tài)代理和動態(tài)代理衙耕,靜態(tài)代理編譯期間就已確認代理類安寺,而動態(tài)代理在運行期間才能確認代理類。
1第美、代理模式簡介
- 定義:給一個對象提供一個代理對象蝶锋,通過代理對象來控制目標(biāo)對象的訪問,代理對象就像是中介什往,起到客戶到目標(biāo)的溝通扳缕,即客戶不直接操控目標(biāo)對象,而是通過中介(代理對象)間接地操控目標(biāo)對象别威。
通過類圖可以發(fā)現(xiàn)躯舔,代理模式的代理對象Proxy
和目標(biāo)對象Subject
實現(xiàn)同一個接口,客戶調(diào)用的是Proxy
對象省古,Proxy
可以控制Subject
的訪問粥庄,真正的功能實現(xiàn)是在Subject
完成的。
代理模式在Java中的實現(xiàn)分為兩種豺妓,一種是靜態(tài)代理惜互,一種是動態(tài)代理。
- 靜態(tài)代理其實就是以上UML類圖的標(biāo)準實現(xiàn)琳拭,其在編譯期間就會確定真正的代理類训堆,即.class文件編譯完成后就確定下來了。
- 動態(tài)代理是標(biāo)準靜態(tài)代理的一種擴展和變形臀栈,最主要一點是蔫慧,動態(tài)代理在編譯完成后是沒有一個代理類的具體類的,但這不代表它沒有代理類权薯,其代理類會在運行期間自動生成姑躲,并通過反射的方式進行方法調(diào)用。
2盟蚣、靜態(tài)代理
靜態(tài)代理可以說是很簡單了黍析,拿點江湖事來舉例,一個武林盟主想去揍一個人屎开,他一般不會自己去阐枣,他可以找武當(dāng)派的人,或者少林寺的人奄抽,最后打人的是各個門派的弟子蔼两,武林盟主其實除了下命令之后啥都沒干,干活的都是小弟逞度。
/**
* Author:xishuang
* Date:2018.03.07
* Des:少林絕學(xué)《易筋經(jīng)》
*/
public interface IShaolin {
void playYiGinChing();
}
/**
* Author:xishuang
* Date:2018.03.07
* Des:武當(dāng)派絕學(xué)太極拳
*/
public interface IWuDang {
void playTaijiquan();
}
public class ShaoLinMan implements IShaolin {
@Override
public void playYiGinChing() {
System.out.println("在下少林->易筋經(jīng)");
}
}
public class WuDangMan implements IWuDang {
@Override
public void playTaijiquan() {
System.out.println("在下武當(dāng)->太極拳");
}
}
/**
* Author:xishuang
* Date:2018.02.06
* Des:靜態(tài)代理需要的代理類额划,編譯器就已經(jīng)確定
*/
public class MengZhuProxy implements IWuDang, IShaolin {
private IWuDang mWuDangMan;
private IShaolin mShaolinMan;
public MengZhuProxy() {
mWuDangMan = new WuDangMan();
mShaolinMan = new ShaoLinMan();
}
@Override
public void playYiGinChing() {
System.out.println("給自己加戲");
mShaolinMan.playYiGinChing();
}
@Override
public void playTaijiquan() {
System.out.println("給自己加戲");
mWuDangMan.playTaijiquan();
}
}
就像代碼里頭展示的,定義好ISubject
接口档泽,這個接口可以是多個俊戳,這里定義了兩個IShaolin
和IWuDang
揖赴,再定義類圖中的目標(biāo)對象類ISubject
,這里的各自接口實現(xiàn)類是ShaoLinMan
和WuDangMan
抑胎,而MengZhuProxy
就是代理類盟主了燥滑,盟主不需要自己會少林絕學(xué),他只需要叫少林寺的弟子去干活阿逃,盟主也不必會武當(dāng)太極拳铭拧,他能控制武當(dāng)派弟子就好。
/**
* 靜態(tài)代理
*/
private void staticProxy() {
MengZhuProxy proxy = new MengZhuProxy();
proxy.playTaijiquan();
proxy.playYiGinChing();
}
運行效果就是這樣恃锉,沒啥大驚小怪的羽历,基本上就是按照UML類圖照葫蘆畫瓢就完成了。
3淡喜、動態(tài)代理
相比于靜態(tài)代理, 動態(tài)代理的優(yōu)勢在于可以很方便的對代理類的函數(shù)進行統(tǒng)一的處理诵闭,而不用修改每個代理類的函數(shù)炼团,這也就是AOP的一種實現(xiàn)方式,但是只能針對接口方法進行處理疏尿,具體原因后面會順勢提到瘟芝。
通過其UML類圖可以發(fā)現(xiàn)其比標(biāo)準的類圖要復(fù)雜,主要引入了Proxy
類褥琐,這是Java自帶的锌俱,我們通過調(diào)用Proxy
類的newProxyInstance
方法來獲取一個代理類實例,此外還引入了InvocationHandler
接口及其實現(xiàn)類敌呈,稱之為調(diào)節(jié)處理器類贸宏。
初看起來有點懵逼,讓我們理一下磕洪,會發(fā)現(xiàn)動態(tài)代理包含了兩層的靜態(tài)代理關(guān)系:
- 第一層是
Proxy
和InvocationHandler
吭练,在這層關(guān)系中,Proxy
是代理類析显,而InvocationHandler
是真正的實現(xiàn)類鲫咽,即目標(biāo)對象類。 - 第二層是
InvocationHandler
和ISubject
谷异,這里的話分尸,InvocationHandler
中持有ISubject
的實現(xiàn)類對象,所以InvocationHandler
是代理類歹嘹,而ISubject
是最終的實現(xiàn)類箩绍。
會發(fā)現(xiàn),饒了一圈荞下,最后的目標(biāo)對象還是ISubject
伶选,和靜態(tài)代理的最主要區(qū)別在于SubjectProxy
這個類是在程序運行中動態(tài)生成的史飞。
續(xù)上面的武林盟主栗子,我們只需要增加一個InvocationHandler
類仰税,這個類會接收到所有對接口方法的調(diào)用构资,在其中我們可以對接口方法進行攔截和實現(xiàn)自己的功能。
/**
* Author:xishuang
* Date:2018.02.07
* Des:具體的代理邏輯實現(xiàn)
*/
public class MengZhuInvocationHandler implements InvocationHandler {
/**
* InvocationHandler持有的被代理對象
*/
private IWuDang mWuDangMan;
private IShaolin mShaolinMan;
MengZhuInvocationHandler(IWuDang wuDang, IShaolin shaolin) {
mWuDangMan = wuDang;
mShaolinMan = shaolin;
}
/**
* 進行具體的代理操作
*
* @param proxy 所代理的類
* @param method 正在調(diào)用得方法
* @param args 方法的參數(shù)
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("playTaijiquan")) {
System.out.println("給自己加戲-動態(tài)代理");
mWuDangMan.playTaijiquan();
} else if (method.getName().equals("playYiGinChing")) {
System.out.println("給自己加戲-動態(tài)代理");
mShaolinMan.playYiGinChing();
}
return proxy;
}
}
最關(guān)鍵在于invoke()
方法陨簇,它會攔截所有實現(xiàn)了接口的方法吐绵,根據(jù)不同的方法名來達到我們的目的,在InvocationHandler
類完成之后河绽,就可以進行調(diào)用己单,調(diào)用的代碼和運行結(jié)果如下:
/**
* 動態(tài)代理
*/
private static void daynamicProxy() {
// 創(chuàng)建一個與代理對象相關(guān)聯(lián)的InvocationHandler
InvocationHandler handler = new MengZhuInvocationHandler(new WuDangMan(), new ShaoLinMan());
// 創(chuàng)建一個代理對象studentProxy來代理student,代理對象的每個執(zhí)行方法都會替換執(zhí)行Invocation中的invoke方法
IWuDang studentProxy = (IWuDang) Proxy.newProxyInstance(getClassLoader(), new Class[]{IWuDang.class, IShaolin.class}, handler);
studentProxy.playTaijiquan();
((IShaolin) studentProxy).playYiGinChing();
}
對比靜態(tài)代理的使用過程耙饰,動態(tài)代理的使用顯得要復(fù)雜一些纹笼,其中最關(guān)鍵的是
Proxy.newProxyInstance()
方法,用于返回動態(tài)生成的代理對象苟跪,需要傳入三個參數(shù)廷痘,類加載器ClassLoader
,需要實現(xiàn)的接口數(shù)組件已,以及InvocationHandler
攔截對象笋额,這些參數(shù)都是生成動態(tài)代理對象所需要的。
4篷扩、動態(tài)代理調(diào)用分析
單單從代碼調(diào)用關(guān)系上來看是比較難看出UML類圖中的那種關(guān)系的兄猩,最主要原因是沒有直觀的看到動態(tài)生成的SubjectProxy
類,所以我們就順著Proxy.newProxyInstance()
這條線來看一下背后的調(diào)用軌跡鉴未。
/**
* 返回指定接口的動態(tài)生成類的實例
*/
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,
InvocationHandler invocationHandler)
throws IllegalArgumentException {
...
return getProxyClass(loader, interfaces)
.getConstructor(InvocationHandler.class)
.newInstance(invocationHandler);
...
}
首先通過類加載器和接口數(shù)組來生成類文件枢冤,然后通過反射的方式來實例化對象,并把InvocationHandler
在構(gòu)造方法中傳入以便后續(xù)調(diào)用铜秆,繼續(xù)看getProxyClass()
方法掏导。
public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
throws IllegalArgumentException {
...
for (Class<?> c : interfaces) {
if (!c.isInterface()) {
// 傳入的要實現(xiàn)類必須是接口類,否則會拋出異常
throw new IllegalArgumentException(c + " is not an interface");
}
...
}
...
Class<?> result;
synchronized (loader.proxyCache) {
result = loader.proxyCache.get(interfaceList);
if (result == null) {
String name = baseName + nextClassNameIndex++;
result = generateProxy(name, interfaces, loader, methodsArray, exceptionsArray);
loader.proxyCache.put(interfaceList, result);
}
}
return result;
}
這里只抽取關(guān)鍵代碼羽峰,可以發(fā)現(xiàn)源碼中限制了我們傳入的需要實現(xiàn)的類必須是接口類趟咆,非接口類會拋出異常,最后通過本地native方法generateProxy()
生成并返回類對象梅屉。
這一個過程是比較明了的值纱,把動態(tài)生成的類文件打印出來就知道代理類中的具體調(diào)用了,這里我們使用Java中提供的ProxyGenerator
類的靜態(tài)方法generateProxyClass()
坯汤,來打印出動態(tài)生成的代理類class字節(jié)碼虐唠。
private static void generyClass() {
byte[] classFile = ProxyGenerator.generateProxyClass("GenerateProxy", new Class[]{IWuDang.class, IShaolin.class});
String path = "D:/項目/proxyTest/GenerateProxy.class";
try (FileOutputStream fos = new FileOutputStream(path)) {
fos.write(classFile);
fos.flush();
System.out.println("文件寫入成功");
} catch (Exception e) {
System.out.println("文件寫入失敗");
}
}
需要提到的一點是,在Android Studio中調(diào)用不了ProxyGenerator
這個類惰聂,避免太麻煩疆偿,所以我這邊是直接把項目遷到IntelliJ IDEA中咱筛,最后生成代理class文件。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class GenerateProxy extends Proxy implements IWuDang, IShaolin {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m4;
private static Method m0;
public GenerateProxy(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 void playTaijiquan() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
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 void playYiGinChing() throws {
try {
super.h.invoke(this, m4, (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 {
// 通過反射機制來調(diào)用各個方法,動態(tài)代理會對運行期間的性能造成一定影響
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("IWuDang").getMethod("playTaijiquan");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m4 = Class.forName("IShaolin").getMethod("playYiGinChing");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
SubjectProxy
繼承了Proxy
并實現(xiàn)我們定義的接口,其中每個方法調(diào)用時沒有做具體的事情妹沙,而是直接調(diào)用super.h.invoke()
,super.h
就是InvocationHandler
對象饲趋,所以SubjectProxy
把任務(wù)拋給了InvocationHandler
進行處理,而InvocationHandler
就是通過我們可以控制的撤蟆,繞來繞去把控制權(quán)又交到了我們的手中奕塑。
前面提到過動態(tài)代理只能代理接口方法,原因也在這里家肯,動態(tài)生成的類直接實現(xiàn)我們定義的接口龄砰,從而實現(xiàn)其中的接口方法,在方法調(diào)用時再把功能交還到我們的手中讨衣。動態(tài)代理可以統(tǒng)一對接口的所有實現(xiàn)類進行操作寝贡,而不用修改每個實現(xiàn)類,通過Proxy
->InvocationHandler
->ISubject
的順序把控制權(quán)一步步的交接值依,最后真正的實現(xiàn)類和靜態(tài)代理是一樣的,只是因為要動態(tài)生成代理類從而導(dǎo)致中間過程饒了一點碟案。