日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

一文帶你弄懂 Java 動(dòng)態(tài)代理 | 原力計(jì)劃

作者 | mjzuo

責(zé)編 | 王曉曼

出品 | CSDN 博客

在說(shuō)動(dòng)態(tài)代理之前,先來(lái)簡(jiǎn)單看下代理模式。

代理是最基本的設(shè)計(jì)模式之一。它能夠插入一個(gè)用來(lái)替代“實(shí)際”對(duì)象的“代理”對(duì)象,來(lái)提供額外的或不同的操作。這些操作通常涉及與“實(shí)際”對(duì)象的通信,因此“代理”對(duì)象通常充當(dāng)著中間人的角色。

一文帶你弄懂 Java 動(dòng)態(tài)代理 | 原力計(jì)劃

代理模式

代理對(duì)象為“實(shí)際”對(duì)象提供一個(gè)替身或占位符以控制對(duì)這個(gè)“實(shí)際”對(duì)象的訪問(wèn)。被代理的對(duì)象可以是遠(yuǎn)程的對(duì)象,創(chuàng)建開(kāi)銷大的對(duì)象或需要安全控制的對(duì)象。來(lái)看下類圖:

一文帶你弄懂 Java 動(dòng)態(tài)代理 | 原力計(jì)劃

再來(lái)看下類圖對(duì)應(yīng)代碼,這是 IObject 接口,真實(shí)對(duì)象 RealObj 和代理對(duì)象 ObjProxy 都實(shí)現(xiàn)此接口:


 

/**

* 為實(shí)際對(duì)象Tested和代理對(duì)象TestedProxy提供對(duì)外接口

*/

public interface IObject {

void request;

}

RealObj 是實(shí)際處理 request 邏輯的對(duì)象,但是出于設(shè)計(jì)的考量,需要對(duì)RealObj內(nèi)部的方法調(diào)用進(jìn)行控制訪問(wèn)。


 

public class RealObject implements IObject {

@Override

public void request {

// 模擬一些操作

}

}

ObjProxy 是 RealObj 的代理類,其同樣實(shí)現(xiàn)了 IObject 接口,所以具有相同的對(duì)外方法。客戶端與 RealObj 的所有交互,都必須通過(guò) ObjProxy。


 

public class ObjProxy implements IObject {

IObject realT;

public ObjProxy(IObject t) {

realT = t;

}

@Override

public void request {

if (isAllow)

realT.request;

}

/**

* 模擬針對(duì)請(qǐng)求的校驗(yàn)判斷

*/

private boolean isAllow {

return true;

}

}

一文帶你弄懂 Java 動(dòng)態(tài)代理 | 原力計(jì)劃

番外

代理模式和裝飾者模式不管是在類圖,還是在代碼實(shí)現(xiàn)上,幾乎是一樣的,但我們?yōu)楹芜€要進(jìn)行劃分呢?其實(shí)學(xué)設(shè)計(jì)模式,不能拘泥于格式,不能死記形式,重要的是要理解模式背后的意圖,意圖只有一個(gè),但實(shí)現(xiàn)的形式卻可能多種多樣。這也就是為何那么多變體依然屬于xx設(shè)計(jì)模式的原因。

代理模式的意圖是替代真正的對(duì)象以實(shí)現(xiàn)訪問(wèn)控制,而裝飾者模式的意圖是為對(duì)象加入額外的行為。

一文帶你弄懂 Java 動(dòng)態(tài)代理 | 原力計(jì)劃

動(dòng)態(tài)代理

JAVA 的動(dòng)態(tài)代理可以動(dòng)態(tài)的創(chuàng)建代理并動(dòng)態(tài)的處理所代理方法的調(diào)用,在動(dòng)態(tài)代理上所做的所以調(diào)用都會(huì)被重定向到單一的調(diào)用處理器上,它的工作是揭示調(diào)用的類型并確定相應(yīng)的策略。類圖見(jiàn)下:

一文帶你弄懂 Java 動(dòng)態(tài)代理 | 原力計(jì)劃

還以上面的代碼為例,這是對(duì)外的接口 IObject:


 

public interface IObject {

void request;

}

這是 InvocationHandler 的實(shí)現(xiàn)類,類圖中 Proxy 的方法調(diào)用都會(huì)被系統(tǒng)傳入此類,即 invoke 方法,而 ObjProxyHandler 又持有著 RealObject 實(shí)例,所以 ObjProxyHandler 是“真正”對(duì) RealObject 對(duì)象進(jìn)行訪問(wèn)控制的代理類。


 

public class ObjProxyHandler implements InvocationHandler {

IObject realT;

public ObjProxyHandler(IObject t) {

realT = t;

}

@Override

public Object invoke(Object proxy, Method method, Object[] args)

throws Throwable {

// request方法時(shí),進(jìn)行校驗(yàn)

if (method.getName.equals("request") && !isAllow)

return ;

return method.invoke(realT, args);

}

/**

* 模擬針對(duì)請(qǐng)求的校驗(yàn)判斷

*/

private boolean isAllow {

return false;

}

}

RealObj 是實(shí)際處理 request 邏輯的對(duì)象。


 

public class RealObject implements IObject {

@Override

public void request {

// 模擬一些操作

}

}

動(dòng)態(tài)代理的使用方法如下:我們通過(guò) Proxy.newProxyInstance 靜態(tài)方法來(lái)創(chuàng)建代理,其參數(shù)如下,一個(gè)類加載器、一個(gè)代理實(shí)現(xiàn)的接口列表、一個(gè) InvocationHandler 的接口實(shí)現(xiàn)。


 

public void startTest {

IObject proxy = (IObject) Proxy.newProxyInstance(

IObject.class.getClassLoader,

new Class{IObject.class},

new ObjProxyHandler(new RealObject));

proxy.request; // ObjProxyHandler的invoke方法會(huì)被調(diào)用

}

一文帶你弄懂 Java 動(dòng)態(tài)代理 | 原力計(jì)劃

Proxy源碼

來(lái)看下Proxy 源碼,當(dāng)我們 newProxyInstance(…) 時(shí),首先系統(tǒng)會(huì)進(jìn)行判空處理,之后獲取我們實(shí)際的 Proxy 代理類 Class 對(duì)象,再通過(guò)一個(gè)參數(shù)的構(gòu)造方法生成我們的代理對(duì)象 p(p : 返回值),這里能看出來(lái) p 是持有我們的對(duì)象 h 的。注意 cons.setAccessible(true) 表示,即使是 cl 是私有構(gòu)造,也可以獲得對(duì)象。源碼見(jiàn)下:


 

public static Object newProxyInstance(ClassLoader loader,

Class<?>[] interfaces,

InvocationHandler h)

throws IllegalArgumentException

{

Objects.requireNon(h);

final Class<?> intfs = interfaces.clone;

/*

* Look up or generate the designated proxy class.

*/

Class<?> cl = getProxyClass0(loader, intfs);

...

final Constructor<?> cons = cl.getConstructor(constructorParams);

final InvocationHandler ih = h;

if (!Modifier.isPublic(cl.getModifiers)) {

cons.setAccessible(true);

// END Android-removed: Excluded AccessController.doPrivileged call.

}

return cons.newInstance(new Object[]{h});

...

}

其中 getProxyClass0(…) 是用來(lái)檢查并獲取實(shí)際代理對(duì)象的。首先會(huì)有一個(gè)65535的接口限制檢測(cè),隨后從代理緩存 ProxyClassCache 中獲取代理類,如果給定的接口不存在,則通過(guò) ProxyClassFactory 新建。見(jiàn)下:


 

private static Class<?> getProxyClass0(ClassLoader loader,

Class<?>... interfaces) {

if (interfaces.length > 65535) {

throw new IllegalArgumentException("interface limit exceeded");

}

// If the proxy class defined by the given loader implementing

// the given interfaces exists, this will simply return the cached copy;

// otherwise, it will create the proxy class via the ProxyClassFactory

return proxyClassCache.get(loader, interfaces);

}

存放代理 Proxy.class 的緩存 proxyClassCache,是一個(gè)靜態(tài)常量,所以在我們類加載時(shí),其就已經(jīng)被初始化完畢了。見(jiàn)下:


 

private static final WeakCache<ClassLoader, Class<?>, Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory, new ProxyClassFactory);

Proxy 提供的getInvocationHandler(Object proxy)方法和 invoke(…) 方法很重要。分別為獲取當(dāng)前代理關(guān)聯(lián)的調(diào)用處理器對(duì)象 InvocationHandler,并將當(dāng)前Proxy方法調(diào)用調(diào)度給 InvocationHandler。

是不是與上面的代理思維很像,至于這兩個(gè)方法何時(shí)被調(diào)用的,推測(cè)是寫在了本地方法內(nèi),當(dāng)我們調(diào)用 proxy.request 方法時(shí)(系統(tǒng)創(chuàng)建Proxy時(shí),會(huì)自動(dòng) implements 用戶傳遞的接口,可以為多個(gè)),系統(tǒng)就會(huì)調(diào)用 Proxy invoke 方法,隨后 proxy 將方法調(diào)用傳遞給 InvocationHandler。


 

public static InvocationHandler getInvocationHandler(Object proxy)

throws IllegalArgumentException

{

/*

* Verify that the object is actually a proxy instance.

*/

if (!isProxyClass(proxy.getClass)) {

throw new IllegalArgumentException("not a proxy instance");

}

final Proxy p = (Proxy) proxy;

final InvocationHandler ih = p.h;

return ih;

}

// Android-added: Helper method invoke(Proxy, Method, Object[]) for ART native code.

private static Object invoke(Proxy proxy, Method method, Object[] args) throws Throwable {

InvocationHandler h = proxy.h;

return h.invoke(proxy, method, args);

}

一文帶你弄懂 Java 動(dòng)態(tài)代理 | 原力計(jì)劃

ProxyClassFactory

重點(diǎn)是ProxyClassFactory 類,這里的邏輯不少,所以我將ProxyClassFactory 單獨(dú)抽出來(lái)了。能看到,首先其會(huì)檢測(cè)當(dāng)前 interface 是否已被當(dāng)前類加載器所加載。


 

Class<?> interfaceClass = ;

try {

interfaceClass = Class.forName(intf.getName, false, loader);

} catch (ClassNotFoundException e) {

}

if (interfaceClass != intf) {

throw new IllegalArgumentException(

intf + " is not visible from class loader");

}

之后會(huì)進(jìn)行判斷是否為接口。這也是我們說(shuō)的第二個(gè)參數(shù)為何不能傳基類或抽象類的原因。


 

if (!interfaceClass.isInterface) {

throw new IllegalArgumentException(

interfaceClass.getName + " is not an interface");

}

之后判斷當(dāng)前 interface 是否已經(jīng)存在于緩存cache內(nèi)了。


 

if (interfaceSet.put(interfaceClass, Boolean.TRUE) != ) {

throw new IllegalArgumentException(

"repeated interface: " + interfaceClass.getName);

}

檢測(cè)非 public 修飾符的 interface 是否在是同一個(gè)包名,如果不是則拋出異常:


 

for (Class<?> intf : interfaces) {

int flags = intf.getModifiers;

if (!Modifier.isPublic(flags)) {

accessFlags = Modifier.FINAL;

String name = intf.getName;

int n = name.lastIndexOf('.');

String pkg = ((n == -1) ? "" : name.substring(0, n + 1));

if (proxyPkg == ) {

proxyPkg = pkg;

} else if (!pkg.equals(proxyPkg)) {

throw new IllegalArgumentException(

"non-public interfaces from different packages");

}

}

}

檢驗(yàn)通過(guò)后,會(huì) getMethods(…) 獲取接口內(nèi)的全部方法。

隨后會(huì)對(duì) methords 進(jìn)行一個(gè)排序。具體的代碼我就不貼了,排序規(guī)則是:如果方法相等(返回值和方法簽名一樣)或同是一個(gè)接口內(nèi)方法,則當(dāng)前順序不變,如果兩個(gè)方法所在的接口存在繼承關(guān)系,則父類在前,子類在后。

之后 validateReturnTypes(…) 判斷 methords 是否存在方法簽名相同并且返回值類型也相同的methord,如果有則拋出異常。

接著通過(guò)
deduplicateAndGetExceptions(…) 方法,將 methords方法內(nèi)的相同方法的父類方法剔除掉,并將 methord 保存在數(shù)組中。

轉(zhuǎn)成一維數(shù)組和二維數(shù)組,Method methodsArray,Class< ? > exceptionsArray,隨后給當(dāng)前代理類命名:包名 + “$Proxy” + num。

最后調(diào)用系統(tǒng)提供的 native 方法 generateProxy(…) 。這是真正的代理類創(chuàng)建方法。感興趣的可以查看下
java_lang_reflect_Proxy.cc源碼和 class_linker.cc源碼:


 

List<Method> methods = getMethods(interfaces);

Collections.sort(methods, ORDER_BY_SIGNATURE_AND_SUBTYPE);

validateReturnTypes(methods);

List<Class<?>> exceptions = deduplicateAndGetExceptions(methods);

Method methodsArray = methods.toArray(new Method[methods.size()]);

Class<?> exceptionsArray = exceptions.toArray(new Class<?>[exceptions.size()]);

/*

* Choose a name for the proxy class to generate.

*/

long num = nextUniqueNumber.getAndIncrement;

String proxyName = proxyPkg + proxyClassNamePrefix + num;

return generateProxy(proxyName, interfaces, loader, methodsArray,

exceptionsArray);

參考:Head First 設(shè)計(jì)模式:中國(guó)電力出版社

版權(quán)聲明:本文為CSDN博主「mjzuo」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接及本聲明。

原文鏈接:
https://blog.csdn.net/mingjiezuo/article/details/105930334

分享到:
標(biāo)簽:代理 動(dòng)態(tài) Java
用戶無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定