代理模式(動(dòng)態(tài)代理)及其原理分析
概念
有一種設(shè)計(jì)模式叫做代理模式,其中也用到了動(dòng)態(tài)代理今艺。動(dòng)態(tài)代理就是為某一個(gè)對(duì)象提供一個(gè)代理對(duì)象犀勒,已達(dá)到對(duì)這個(gè)對(duì)象的代理訪問(wèn),最典型的例子就是“律師”和“普通民眾”的關(guān)系够滑。
代理模式
我們直接上一個(gè)簡(jiǎn)單的小例子來(lái)闡述這個(gè)模式。
事件:小明要上訴吕世,他找了一個(gè)律師幫他彰触。
這里是上訴的步驟
public interface ILawsuit {
/**
* 提交申請(qǐng)
*/
void submit();
}
接下來(lái)是小明要開(kāi)始上訴,上訴的實(shí)現(xiàn)類(lèi)
public class XiaoMin implements ILawsuit {
@Override
public void submit() {
System.out.println("我要上訴命辖!");
}
}
接下來(lái)是律師實(shí)現(xiàn)類(lèi)况毅,他也要實(shí)現(xiàn)上訴的步驟
public class Lawyer implements ILawsuit {
//持有小明的引用
private ILawsuit mLawsuit;
public Lawyer(ILawsuit lawsuit) {
mLawsuit = lawsuit;
}
@Override
public void submit() {
//除了小明自己的上訴,我還可以加上一些屬于律師的邏輯
mLawsuit.submit();
}
}
一個(gè)簡(jiǎn)單的代理模式就實(shí)現(xiàn)了尔艇,這里可以感受出來(lái)代理模式的一些好處了尔许。方便后期邏輯拓展,以及簡(jiǎn)化了客戶(hù)端(使用者)使用這個(gè)對(duì)象(小明)的邏輯终娃。
但是在一個(gè)被代理對(duì)象有大量邏輯或者多個(gè)對(duì)象都需要代理的時(shí)候味廊,我們不可能再去寫(xiě)一個(gè)大量邏輯的代理類(lèi)了,不然也太麻煩了棠耕,因此Java就給我們了一個(gè)快速拿到代理對(duì)象的方法余佛,此時(shí)就沒(méi)有“律師”類(lèi)了,請(qǐng)看下面的一個(gè)實(shí)現(xiàn)代理邏輯的類(lèi):
public class DynamicProxy implements InvocationHandler {
//代理的對(duì)象的引用
private Object object;
public DynamicProxy(Object o) {
object = o;
}
/**
* 實(shí)現(xiàn)方法的調(diào)用
* @param proxy 真實(shí)對(duì)象(小明)
* @param method 原方法的對(duì)象
* @param args 原方法的參數(shù)
* @return 原方法的返回結(jié)果
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//實(shí)現(xiàn)方法的調(diào)用
//每當(dāng)代理對(duì)象調(diào)用一個(gè)方法的時(shí)候窍荧,最后都觸發(fā)的是這一個(gè)方法
Object result = method.invoke(object, args);
return result;
}
}
在這個(gè)(動(dòng)態(tài)代理)的類(lèi)中衙熔,我們實(shí)現(xiàn)了一個(gè)接口,這個(gè)接口就是 InvocationHandler
搅荞,里面只有一個(gè) invoke()
方法红氯,這個(gè)方法就是被代理對(duì)象真正實(shí)現(xiàn)的地方,例如:小明調(diào)用上訴方法咕痛,真正執(zhí)行的是method.invoke(object, args)
痢甘,這一句即是實(shí)現(xiàn)method方法。這個(gè)類(lèi)的實(shí)現(xiàn)就讓我們可以同時(shí)對(duì)多個(gè)代理邏輯相同的對(duì)象進(jìn)行代理茉贡,而且這個(gè)類(lèi)只將所有方法都集中在了一個(gè)invoke()
方法中塞栅,省去了大量重復(fù)代碼,例如可以在這里添加打印日志的功能等腔丧。
接下來(lái)來(lái)看小明再次怎么被代理的:
//對(duì)真實(shí)對(duì)象的代理的邏輯實(shí)現(xiàn)
DynamicProxy proxy = new DynamicProxy(xiaoMin);
//定義了由哪個(gè)ClassLoader對(duì)象來(lái)對(duì)生成的代理對(duì)象進(jìn)行加載
ClassLoader loader = xiaoMin.getClass().getClassLoader();
//這才是最終的代理對(duì)象
ILawsuit lawyer = (ILawsuit) Proxy.newProxyInstance(loader, new Class[]
{ILawsuit.class},proxy);
lawyer.submit();
這里我們關(guān)注一下Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h);
方法放椰,先說(shuō)三個(gè)參數(shù):
- loader: 一個(gè)ClassLoader對(duì)象作烟,定義了由哪個(gè)ClassLoader對(duì)象來(lái)對(duì)生成的代理對(duì)象進(jìn)行加載。
- interfaces: 一個(gè)Interface對(duì)象的數(shù)組砾医,表示的是我將要給我需要代理的對(duì)象提供一組什么接口拿撩,如果我提供了一組接口給它,那么這個(gè)代理對(duì)象就宣稱(chēng)實(shí)現(xiàn)了該接口(多態(tài))如蚜,這樣我就能調(diào)用這組接口中的方法了压恒。
- h: 一個(gè)InvocationHandler對(duì)象,表示的是當(dāng)我這個(gè)動(dòng)態(tài)代理對(duì)象在調(diào)用方法的時(shí)候错邦,會(huì)關(guān)聯(lián)到哪一個(gè)InvocationHandler對(duì)象上探赫。
細(xì)心的你可能發(fā)現(xiàn)了不對(duì)的地方,我們拿到的lawyer
對(duì)象如果只是一個(gè)XiaoMin
對(duì)象的話(huà)撬呢,那么在調(diào)用submit()
方法的時(shí)候不可能觸發(fā)invoke()
方法伦吠,而是直接實(shí)現(xiàn)。那么證實(shí) Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h);
返回的不是XiaoMin
對(duì)象魂拦,而是一個(gè)JVM根據(jù)ILawsuit接口讨勤、 xiaoMin對(duì)象、proxy動(dòng)態(tài)代理對(duì)象
來(lái)生成的一個(gè)對(duì)象晨另。
然后我去跟著這些方法去源碼里面看一下,先看newProxyInstance()
方法谱姓,這個(gè)方法負(fù)責(zé)的就是生成代理類(lèi)的對(duì)象:
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
throws IllegalArgumentException
{
//要求代理邏輯對(duì)象非空
Objects.requireNonNull(h);
//根據(jù)傳入的類(lèi)構(gòu)造器和接口對(duì)象借尿,查找或者生成指定的代理類(lèi)
Class<?> cl = getProxyClass0(loader, intfs);
//調(diào)用構(gòu)造函數(shù)
try {
//拿到含有InvocationHandler類(lèi)型參數(shù)的構(gòu)造方法
//目的是最后的類(lèi)對(duì)象是跟我們的代理邏輯的對(duì)象相關(guān)聯(lián)
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
//生成的類(lèi)的修飾符不是公開(kāi)的話(huà)
if (!Modifier.isPublic(cl.getModifiers())) {
//這里其實(shí)是拿到報(bào)異常的
cons.setAccessible(true);
}
//通過(guò)反射拿到生成類(lèi)的實(shí)例
return cons.newInstance(new Object[]{h});
}
}
其中的constructorParams
是來(lái)自private static final Class<?>[] constructorParams ={ InvocationHandler.class };
這一句話(huà),也就是實(shí)現(xiàn)我們的代理邏輯的那個(gè)接口的一個(gè)數(shù)組對(duì)象屉来,其實(shí)這里我們根據(jù)最后的返回值return cons.newInstance(new Object[]{h})
就應(yīng)該了解到編譯器為我們生成的類(lèi)中的構(gòu)造函數(shù)有一個(gè)是傳入我們的代理邏輯對(duì)象(InvocationHandler
對(duì)象)的構(gòu)造函數(shù)路翻,因此實(shí)現(xiàn)了代理邏輯和生成類(lèi)的關(guān)聯(lián),那么此時(shí)我們的生成類(lèi)就包含了我們的被代理類(lèi)(小明)的邏輯和我們(對(duì)小明)的代理邏輯茄靠。
接下來(lái)我們?cè)冱c(diǎn)進(jìn)去這個(gè)方法中的一個(gè)重要操作getProxyClass0(loader, intfs)
茂契,經(jīng)過(guò)兩步操作,我們最終是來(lái)到了WeakCache(弱緩存)的public V get(K key, P parameter){}
方法:
/**
*V: 最后的代理類(lèi)泛型對(duì)象
*key: classLoader(小明類(lèi)對(duì)應(yīng)的類(lèi)加載器)
*parameter: 接口類(lèi)(小明的申訴步驟接口)的數(shù)組對(duì)象
*/
public V get(K key, P parameter) {
Objects.requireNonNull(parameter);
expungeStaleEntries();
//根據(jù)類(lèi)加載器拿到一個(gè)從緩存讀取數(shù)據(jù)的key
Object cacheKey = CacheKey.valueOf(key, refQueue);
// 從對(duì)應(yīng)的類(lèi)加載器中拿到的并行集合慨绳,存放的值是Supplier(接口“提供者”)
// map一開(kāi)始是空的掉冶,map也是從這個(gè)方法里進(jìn)行填充的
ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
if (valuesMap == null) {
// 如果沒(méi)有這個(gè)并行集合,則new 一個(gè)并行集合并放入這個(gè)map脐雪。
ConcurrentMap<Object, Supplier<V>> oldValuesMap
= map.putIfAbsent(cacheKey,
valuesMap = new ConcurrentHashMap<>());
if (oldValuesMap != null) {
valuesMap = oldValuesMap;
}
}
// 創(chuàng)建子鍵厌小,并從valueMap 中檢索可能存在的對(duì)應(yīng)子鍵的Supplier(提供者)
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
Supplier<V> supplier = valuesMap.get(subKey);
//工廠(Supplier的實(shí)現(xiàn)類(lèi))
Factory factory = null;
while (true) {
// 提供者不為空的話(huà)直接拿到我們需要的代理類(lèi)的Class對(duì)象
// 返回一個(gè)值,結(jié)束該方法
if (supplier != null) {
V value = supplier.get();
if (value != null) {
return value;
}
}
// 實(shí)例出一個(gè)工廠對(duì)象(判斷代理類(lèi)就是在Factory中生成的)
if (factory == null) {
factory = new Factory(key, parameter, subKey, valuesMap);
}
//當(dāng)提供者為空的時(shí)候
if (supplier == null) {
//如果并行集合中沒(méi)得對(duì)應(yīng)的提供者战秋,則放入剛創(chuàng)建的factory(提供者)
supplier = valuesMap.putIfAbsent(subKey, factory);
if (supplier == null) {
supplier = factory;
}
} else {
// 不為空的話(huà)替換并行集合中對(duì)應(yīng)的元素
if (valuesMap.replace(subKey, supplier, factory)) {
// 直接拿到提供者
supplier = factory;
} else {
// 根據(jù)子鍵拿出對(duì)應(yīng)的提供者
supplier = valuesMap.get(subKey);
}
}
}
}
看到這個(gè)方法是不是腦袋都大了璧亚,沒(méi)錯(cuò),其實(shí)我的腦袋也大了脂信,很多東西是懵懵懂懂的癣蟋。但是我們依舊可以梳理一遍這個(gè)方法的大體邏輯:
我們?yōu)榱四玫轿覀兿胍拇眍?lèi)透硝,我們是從弱緩存中去找,先根據(jù)我們的類(lèi)加載器從一個(gè)map中去找疯搅,找的目標(biāo)是也是一個(gè)集合濒生,這是一個(gè)放了可以拿到我們想要的代理類(lèi)的集合————一個(gè)并行集合。但是一般的話(huà)這個(gè)map都是空的秉撇,因?yàn)樗侵苯觧ew 出來(lái)的(這個(gè)類(lèi)的頂部有)甜攀,里面的元素也是在這個(gè)方法中被放進(jìn)去的,那么也就是說(shuō)我們找到的并行集合也可能是個(gè)空的琐馆,對(duì)吧规阀?所以的話(huà)我們也是直接new 一個(gè)并行集合進(jìn)去的。那我們new 出來(lái)的并行集合也是空的啊瘦麸,沒(méi)關(guān)系谁撼,因?yàn)槲覀兘酉聛?lái)往里面添加?xùn)|西就對(duì)了。重點(diǎn)來(lái)了滋饲,我們的Factory類(lèi)厉碟,它本身也是一個(gè)Supplier(接口) 的實(shí)現(xiàn)類(lèi),在實(shí)例化的過(guò)程屠缭,我們根據(jù)Factory構(gòu)造器傳入值箍鼓,生成對(duì)應(yīng)的代理類(lèi),并且factory對(duì)象自身會(huì)被放入緩存集合呵曹,以便下次直接從它拿到目標(biāo)代理類(lèi)款咖。也就是說(shuō)我們可以通過(guò)Factory直接生產(chǎn)出我們想要的代理類(lèi)的,而這個(gè)方法中的其它邏輯則是為了讓我們能夠?qū)崿F(xiàn)保存奄喂,在下一次便可以直接調(diào)用了铐殃,以達(dá)到節(jié)省資源的目的。
最后的最后跨新,我們?cè)偃?strong>Factory中去看看吧富腊,驗(yàn)證我們的猜想是否正確,我們?nèi)フ椅覀兿胍拇a域帐,
在這里我們保留了關(guān)鍵的代碼:
private final class Factory implements Supplier<V> {
Factory(K key, P parameter, Object subKey,
ConcurrentMap<Object, Supplier<V>> valuesMap) {
this.key = key;
this.parameter = parameter;
this.subKey = subKey;
this.valuesMap = valuesMap;
}
@Override
public synchronized V get() {
// re-check
Supplier<V> supplier = valuesMap.get(subKey);
if (supplier != this) {
// 簡(jiǎn)單理解赘被,防止意外發(fā)生,返回Null讓其一直循環(huán)
return null;
}
// 激動(dòng)的時(shí)候肖揣,這就是我們的目標(biāo)代理類(lèi)
V value = null;
try {
// 再深入就又要扯好遠(yuǎn)帘腹,這里又是另外的工廠類(lèi)了,反正就是生產(chǎn)valueP矶觥Q粲!
value = Objects.requireNonNull(valueFactory.apply(key, parameter));
}
//哈哈哈,拿到了G蚧;嗤怼!
return value;
}
}
這便是我們生產(chǎn)我們的目標(biāo)代理類(lèi)的全部過(guò)程啦筒愚,我們應(yīng)該注意的是最后跟隨我們到底的一直有我們的“申訴步驟”的接口赴蝇,以確保我們拿到的是這個(gè)接口的對(duì)象,然后我們就可以執(zhí)行里面的方法了巢掺。
我還在這里制作了一張簡(jiǎn)單的流程圖:
由于筆者水平有限句伶,我在找了大篇的博客后也沒(méi)能找出JVM為我們生成的代理類(lèi),所以我們也只能估摸著這個(gè)代理類(lèi)的作用了陆淀,就是在調(diào)用被代理對(duì)象方法的時(shí)候考余,記錄該目標(biāo)方法,并直到調(diào)用invoke()
方法中去轧苫,在invoke()
方法中我們自行實(shí)現(xiàn)目標(biāo)方法的調(diào)用楚堤。
補(bǔ)充 —— AOP
翻譯過(guò)來(lái)叫做“面向切面的編程”,簡(jiǎn)單說(shuō)含懊,就是找到一個(gè)模塊的切入點(diǎn)身冬,對(duì)它進(jìn)行切面操作。
比如打Log這一操作吧岔乔,我們好多個(gè)對(duì)象酥筝、好多個(gè)方法都需要打上Log,那我們?cè)诿恳粋€(gè)方法里面都打上自己的Log類(lèi)的Log吧雏门。首先嘿歌,打這么多的Log不累嗎?而且這么多Log肯定少不了大量重復(fù)代碼剿配。所以我們面對(duì)打Log這一功能進(jìn)行處理,就用動(dòng)態(tài)代理的形式來(lái)做吧阅束,我給每一個(gè)對(duì)象一個(gè)代理呼胚,讓它執(zhí)行方法的時(shí)候都在這個(gè)代理對(duì)象中去執(zhí)行,那么是不是就是可以在代理對(duì)象中去實(shí)現(xiàn)打Log 的功能了息裸,這樣就將Log和方法分開(kāi)來(lái)了蝇更,簡(jiǎn)化了代碼,增強(qiáng)了后期拓展性呼盆,而且代碼變整潔了年扩,有木有? 访圃。
你可能會(huì)問(wèn)了厨幻,我給每一個(gè)對(duì)象都搞一個(gè)代理是不是更麻煩喲?那么,我的第二個(gè)目的就是要簡(jiǎn)化這一實(shí)現(xiàn)過(guò)程了况脆,是不是饭宾?例如Retrofit 第三方庫(kù)就有采用一種方法————利用Apt(編譯時(shí)掃描和處理注解),使用的時(shí)候在需要代理的對(duì)象上面加上簡(jiǎn)單的注解就實(shí)現(xiàn)了這一功能格了,在這里就不詳細(xì)解釋如何實(shí)現(xiàn)的了看铆。