代理模式是什么岳服?
代理模式是一種結(jié)構(gòu)性設(shè)計(jì)模式,意思是在使用者和目標(biāo)中出現(xiàn)一個(gè)中間層鸯乃,中間層才能控制目標(biāo),而使用者只能和中間層交互跋涣,類似:租客 - 中介 - 房東。中間層在雙方之中增加自己的業(yè)務(wù)鸟悴。
代理模式能解決什么問題陈辱?
直接訪問對(duì)象時(shí)帶來的問題。
比如你在訪問中途需要增加中間層業(yè)務(wù)的時(shí)候细诸。
比如你要不同業(yè)務(wù)中增加統(tǒng)一的一層業(yè)務(wù)沛贪。
代理模式分類
靜態(tài)代理
靜態(tài)代理是什么?
給目標(biāo)對(duì)象提供一個(gè)代理對(duì)象,并由代理對(duì)象控制對(duì)目標(biāo)對(duì)象的引用利赋。
例子1:電腦桌面的快捷方式水评。電腦對(duì)某個(gè)程序提供一個(gè)快捷方式(代理對(duì)象)涮母,快捷方式連接客戶端和程序匆浙,客戶端通過操作快捷方式就可以操作那個(gè)程序。
例子2:你要租房缤沦,然后通過第三方的租房平臺(tái)租到了一套房塘偎。這里第三方租房平臺(tái)就是代理對(duì)象疗涉,第三方租房平臺(tái)連接著你和房東,房東就是目標(biāo)對(duì)象吟秩。
例子3:你去銀行存錢咱扣,你只需要找尋柜臺(tái)工作人員幫你做存錢的業(yè)務(wù),而不需要自己操作電腦存錢涵防。這里柜臺(tái)工作人員就是代理對(duì)象闹伪,而存錢的業(yè)務(wù)就是目標(biāo)對(duì)象。
代理對(duì)象:起到了中介作用壮池,連接客戶端和目標(biāo)對(duì)象偏瓤。
解決問題
隱藏了真正的功能細(xì)節(jié),一切通過代理得知結(jié)果火窒。防止直接訪問目標(biāo)對(duì)象帶來的不必要的復(fù)雜度硼补。
模式原理
實(shí)例講解
實(shí)例概況
- 背景:小成希望買一臺(tái)最新的頂配Mac電腦
- 沖突:國(guó)內(nèi)還沒上,只有美國(guó)才有
- 解決方案:尋找代購(gòu)進(jìn)行購(gòu)買
代購(gòu)(代理對(duì)象) 代替 我(真實(shí)對(duì)象) 去買Mac(間接訪問的操作)
實(shí)現(xiàn)步驟
1熏矿、創(chuàng)建抽象對(duì)象接口(ISBusiness):聲明你(真實(shí)對(duì)象)需要讓代購(gòu)(代理對(duì)象)幫忙做的事(買Mac)
interface ISBusiness {
/**
* 買mac
*/
fun buyMac(name:String)
/**
* 買水果
*/
fun buyFruit(name:String,fruit: String)
}
2已骇、創(chuàng)建真實(shí)對(duì)象類(SBusiness),即真正做事的人
class SBusiness : ISBusiness {
override fun buyFruit(name: String,fruit: String) {
println("幫${name}買了一斤$fruit")
}
override fun buyMac(name: String) {
println("幫${name}去買了一臺(tái)mac")
}
}
3、創(chuàng)建代理對(duì)象類(Proxy)票编,即”代購(gòu)“褪储,并通過代理類創(chuàng)建真實(shí)對(duì)象實(shí)例并訪問其方法
class SProxy :ISBusiness{
override fun buyFruit(name: String,fruit: String) {
//真實(shí)做事情的對(duì)象
val business = SBusiness()
business.buyFruit(name,fruit)
//代理對(duì)象做額外的操作
fruitWrap()
}
override fun buyMac(name:String) {
//真實(shí)做事情的對(duì)象
val business = SBusiness()
business.buyMac(name)
//代理對(duì)象做額外的操作
giftWrap()
}
private fun giftWrap() {
println("商品包裝")
}
private fun fruitWrap() {
println("水果打包")
}
}
4、客戶端調(diào)用
@Test
fun test() {
val sproxy = SProxy()
sproxy.buyMac("小明")
sproxy.buyFruit("小明","香蕉")
}
結(jié)果輸出
幫小明去買了一臺(tái)mac
商品包裝
幫小明買了一斤香蕉
水果打包
優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 由代理類對(duì)目標(biāo)對(duì)象進(jìn)行訪問控制和擴(kuò)展慧域,降低了系統(tǒng)的耦合度鲤竹。
- 代理對(duì)象作為客戶端和目標(biāo)對(duì)象的中間層,起到了保護(hù)目標(biāo)對(duì)象的作用昔榴。
缺點(diǎn)
- 重復(fù)性:靜態(tài)代理的原則是一個(gè)代理類對(duì)應(yīng)一個(gè)基礎(chǔ)接口辛藻,如果基礎(chǔ)接口過多,那么就需要?jiǎng)?chuàng)建多個(gè)代理類來分別管理不同的目標(biāo)對(duì)象互订。重復(fù)的模板也會(huì)越來越多吱肌。
- 脆弱性:基礎(chǔ)接口一旦修改,所以相關(guān)的目標(biāo)對(duì)象都要進(jìn)行改動(dòng)仰禽,代理類也無法避免氮墨。
- 實(shí)現(xiàn)代理模式需要額外的工作(有些代理模式的實(shí)現(xiàn)非常復(fù)雜)纺蛆,從而增加了系統(tǒng)實(shí)現(xiàn)的復(fù)雜度。如果多個(gè)不同的基礎(chǔ)接口需要提供一個(gè)相關(guān)的功能规揪,比如日志桥氏,那么所有實(shí)現(xiàn)的代理類都需要進(jìn)行修改。
動(dòng)態(tài)代理
動(dòng)態(tài)代理的意義
假設(shè)項(xiàng)目中要給多個(gè)實(shí)現(xiàn)了一個(gè)相同的業(yè)務(wù)接口增加一個(gè)統(tǒng)一的功能猛铅,比如日志字支,這個(gè)時(shí)候你使用靜態(tài)代理其實(shí)可以實(shí)現(xiàn)。但是如果項(xiàng)目中要給多個(gè)不同的業(yè)務(wù)接口實(shí)現(xiàn)類增加一個(gè)日志功能呢奕坟?你一個(gè)靜態(tài)代理是不夠的祥款,靜態(tài)代理的原則是一個(gè)代理類對(duì)應(yīng)著一個(gè)基礎(chǔ)接口。那如果是多個(gè)基礎(chǔ)接口月杉,就需要?jiǎng)?chuàng)建多個(gè)靜態(tài)代理類刃跛。這就暴露了靜態(tài)代理的缺點(diǎn):
一、重復(fù)性苛萎,在程序規(guī)模開始增加時(shí)桨昙,需要代理的方法越來越多,你所需要的代理類就越來越多腌歉,重復(fù)的模板就越來越多了蛙酪。
二、脆弱性翘盖,基礎(chǔ)接口一旦修改桂塞,除了所有的業(yè)務(wù)類需要修改,代理類也必須改動(dòng)馍驯。
怎么解決這個(gè)問題阁危?
動(dòng)態(tài)代理(Dymanic Proxy API)是JDK1.3中引入的特性,核心API是java.lang.reflect.Proxy類和java.lang.reflect.InvocationHandler接口汰瘫。它利用反射機(jī)制在運(yùn)行時(shí)生成代理類的字節(jié)碼狂打,為Java平臺(tái)在帶來了運(yùn)行時(shí)動(dòng)態(tài)擴(kuò)展對(duì)象行為的能力。
動(dòng)態(tài)代理和靜態(tài)代理的區(qū)別混弥?
靜態(tài)代理:在編譯期間就已經(jīng)確定目標(biāo)對(duì)象是哪一個(gè)趴乡,運(yùn)行前目標(biāo)對(duì)象已經(jīng)存在。
動(dòng)態(tài)代理:程序運(yùn)行時(shí)才確定目標(biāo)對(duì)象是哪一個(gè)蝗拿,運(yùn)行過程中動(dòng)態(tài)生成代理類晾捏。
為什么要用動(dòng)態(tài)代理?
因?yàn)橐粋€(gè)靜態(tài)代理類只能服務(wù)一種類型的對(duì)象哀托,在目標(biāo)對(duì)象較多的情況下粟瞬,會(huì)出現(xiàn)代理類較多,代碼量較大的問題萤捆。
而使用動(dòng)態(tài)代理動(dòng)態(tài)生成代理者對(duì)象能避免這種情況的發(fā)生裙品。
動(dòng)態(tài)代理核心組件
Proxy:代理類,內(nèi)部通過反射機(jī)制動(dòng)態(tài)生成代理類對(duì)象俗或。
InvocationHandler:這是一個(gè)接口市怎,代理實(shí)例的調(diào)用處理程序。
原理時(shí)序圖
實(shí)現(xiàn)步驟
1辛慰、聲明目標(biāo)對(duì)象的抽象接口
interface Ibusiness {
fun business()
}
2区匠、生成目標(biāo)對(duì)象(實(shí)現(xiàn)目標(biāo)對(duì)象的抽象接口,這里由于每個(gè)代理類都繼承了 Proxy 類帅腌,又 Java 的單繼承特性驰弄,所以,只能針對(duì)接口創(chuàng)建代理類速客,不能針對(duì)類創(chuàng)建代理類)
class OneBusiness : Ibusiness {
override fun business() {
println("我外出買了mac")
}
}
3戚篙、聲明調(diào)用處理類
public class BusinessHandler implements InvocationHandler {
private Ibusiness ibusiness;
public BusinessHandler(Ibusiness bui) {
ibusiness = bui;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("外出前先收拾垃圾");
try {
method.invoke(ibusiness, args);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("外出回來了");
return null;
}
}
4、生成相關(guān)對(duì)象并通過代理類對(duì)象調(diào)用目標(biāo)對(duì)象方法
//生成相關(guān)對(duì)象
OneBusiness business = new OneBusiness();
BusinessHandler handler = new BusinessHandler(business);
Ibusiness ibusiness = (Ibusiness) Proxy.newProxyInstance(business.getClass().getClassLoader(), business.getClass().getInterfaces(), handler);
//調(diào)用方法
ibusiness.business();
5溺职、最終會(huì)在調(diào)用處理類中執(zhí)行目標(biāo)對(duì)象的對(duì)應(yīng)方法岔擂,并且你可以在此期間增加新的業(yè)務(wù)
public class BusinessHandler implements InvocationHandler {
private Ibusiness ibusiness;
public BusinessHandler(Ibusiness bui) {
ibusiness = bui;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//可以執(zhí)行一些擴(kuò)展的業(yè)務(wù),比如日志浪耘,埋點(diǎn)等
System.out.println("外出前先收拾垃圾");
try {
//調(diào)用目標(biāo)對(duì)象方法
method.invoke(ibusiness, args);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("外出回來了");
return null;
}
}
結(jié)果輸出
外出前先收拾垃圾 ->這是擴(kuò)展的業(yè)務(wù)
我外出買了mac -> 這是目標(biāo)對(duì)象執(zhí)行的函數(shù)
外出回來了 -> 這也是擴(kuò)展的業(yè)務(wù)
靜態(tài)代理和動(dòng)態(tài)代理
共同點(diǎn):兩種代理模式都是在不改動(dòng)目標(biāo)對(duì)象的前提下乱灵,對(duì)目標(biāo)對(duì)象進(jìn)行訪問和擴(kuò)展,符合開閉原則七冲。
不同點(diǎn):靜態(tài)代理模式在程序規(guī)模稍大時(shí)痛倚,重復(fù)性和脆弱性的缺點(diǎn)出現(xiàn),動(dòng)態(tài)代理實(shí)現(xiàn)了一個(gè)代理處理N個(gè)基礎(chǔ)接口并擴(kuò)展澜躺,本質(zhì)上是代理類和基礎(chǔ)接口的解耦蝉稳,一定程度上規(guī)避了靜態(tài)代理的缺點(diǎn)。從原理上講苗踪,靜態(tài)代理的代理類在編譯期生成颠区,而動(dòng)態(tài)代理的代理類在運(yùn)行時(shí)生成。代理類在coding階段并不存在通铲,代理關(guān)系直到運(yùn)行時(shí)才確定毕莱。
使用動(dòng)態(tài)代理的好處?
它可以無侵入的增加其他的方法颅夺,不會(huì)對(duì)基礎(chǔ)業(yè)務(wù)有任何影響朋截。
應(yīng)用場(chǎng)景
Android的跨進(jìn)程通信中使用了動(dòng)態(tài)代理
比如activity的啟動(dòng)過程中,有動(dòng)態(tài)代理的使用
Retrofit中create()也使用了動(dòng)態(tài)代理吧黄,用途是封裝接口轉(zhuǎn)化成一個(gè)OkHttpCall對(duì)象傳給OkHttp
AOP面向切面編程領(lǐng)域
動(dòng)態(tài)代理源碼分析
這是java.lang.reflect.Proxy的源碼部服,試著理解代理類Class如何生成,這是jdk1.3之后引入的特性
package java.lang.reflect;
* @author Peter Jones
* @see InvocationHandler
* @since 1.3
*/
public class Proxy implements java.io.Serializable {
公共方法
分析newProxyInstance方法
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
throws IllegalArgumentException
參數(shù)一:ClassLoader 類加載器拗慨,這里可以傳代理類要實(shí)現(xiàn)的接口的ClassLoader
為什么要指定ClassLoader對(duì)象廓八?
ClassLoader相當(dāng)于類的命名空間奉芦,類的唯一性由它本身和加載它的ClassLoader確定。一個(gè)Class文件如果由兩個(gè)ClassLoader加載剧蹂,結(jié)果是兩個(gè)獨(dú)立的類声功。
參數(shù)二:Class<?>[] 要實(shí)現(xiàn)的接口,因?yàn)橐粋€(gè)類可以實(shí)現(xiàn)多個(gè)接口宠叼,所以這里是個(gè)數(shù)組
參數(shù)三:InvocationHandler 這個(gè)就是接口代理類執(zhí)行方法的回調(diào)
{
...
//查找或生成指定的代理類
Class<?> cl = getProxyClass0(loader, intfs);
...
//實(shí)例化代理類先巴,H就是我們實(shí)現(xiàn)的BusinessHandler
return cons.newInstance(new Object[]{h});
...
}
分析通過動(dòng)態(tài)代理生成的字節(jié)碼文件
getProxyClass0()獲取代理類字節(jié)碼文件,主要使用了ProxyGenerator.generateProxyClass()生成了代理類冒冬,默認(rèn)在內(nèi)存中伸蚯,如果你想生成文件觀看需要設(shè)置兩個(gè)步驟:
一、設(shè)置System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true") 獲取本地文件, 通過此語(yǔ)句可以立個(gè)flag讓生成字節(jié)碼文件的時(shí)候简烤,輸出本地字節(jié)碼文件,注意這句話一定要在獲取代理類實(shí)例之前剂邮。
二、需要在main函數(shù)中調(diào)用才能生成字節(jié)碼文件乐埠,原因未知抗斤,但是我在其他地方調(diào)用是不能生成字節(jié)碼文件的。
生成的文件在當(dāng)前項(xiàng)目的根目錄丈咐,對(duì)應(yīng)的包名文件夾里:com/sun/proxy/$Proxy().class
ProxyGenerator
這個(gè)類是生成對(duì)應(yīng)的代理類字節(jié)碼,也就是通過這個(gè)方法
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//這是對(duì)應(yīng)設(shè)置是否生成對(duì)應(yīng)路徑的字節(jié)碼文件瑞眼,默認(rèn)只會(huì)生成在內(nèi)存中
public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
final byte[] var4 = var3.generateClassFile();
//我們現(xiàn)在控制的就是這個(gè)變量,如果為ture表示字節(jié)碼文件寫入本地
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);
}
}
});
}
return var4;
}
來看下生成的字節(jié)碼文件
package com.sun.proxy;
import com.wzy.dynamic.proxy.dproxy.Ibusiness;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements Ibusiness {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
//關(guān)鍵點(diǎn)
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 void business() 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 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("com.wzy.dynamic.proxy.dproxy.Ibusiness").getMethod("business");
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());
}
}
}
關(guān)鍵的構(gòu)造函數(shù)
//這個(gè)handler就是我們最初傳入的那個(gè)InvocationHandler的實(shí)現(xiàn)類
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
接著看父類
//這個(gè)H就是我們自己的InvocationHandler
protected Proxy(InvocationHandler var1) {
Objects.requireNonNull(var1);
this.h = var1;
}
Proxy中有一個(gè)這個(gè)靜態(tài)方法就是最終會(huì)回到我們實(shí)現(xiàn)的invoke類中
private static Object invoke(Proxy proxy, Method method, Object[] args) throws Throwable {
InvocationHandler h = proxy.h;
return h.invoke(proxy, method, args);
}
所有相關(guān)的調(diào)用最終也都會(huì)調(diào)用這個(gè)方法棵逊,具體可以看上面的字節(jié)碼類
//$Proxy0.class
public final void business() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
總結(jié)一下伤疙,代理類執(zhí)行的任何一個(gè)方法都會(huì)回調(diào)你的InvocationHandler實(shí)現(xiàn)類。而且通過ProxyGenerator.generateProxyClass()確實(shí)動(dòng)態(tài)生成了字節(jié)碼文件辆影!
那么看完這些咱們也明白了InvocationHandler中invoke(Object proxy, Method method, Object[] args) 這三個(gè)參數(shù)分別是什么?
參數(shù)一:Object 生成的代理類對(duì)象
參數(shù)二:Method 代理類調(diào)用的方法
參數(shù)三:args 調(diào)用方法時(shí)的參數(shù)