作者 | mjzuo
責(zé)編 | 王曉曼
出品 | CSDN 博客
在說動態(tài)代理之前,先來簡單看下代理模式。
代理是最基本的設(shè)計模式之一。它能夠插入一個用來替代“實際”對象的“代理”對象,來提供額外的或不同的操作。這些操作通常涉及與“實際”對象的通信,因此“代理”對象通常充當(dāng)著中間人的角色。
代理模式
代理對象為“實際”對象提供一個替身或占位符以控制對這個“實際”對象的訪問。被代理的對象可以是遠(yuǎn)程的對象,創(chuàng)建開銷大的對象或需要安全控制的對象。來看下類圖:
再來看下類圖對應(yīng)代碼,這是 IObject 接口,真實對象 RealObj 和代理對象 ObjProxy 都實現(xiàn)此接口:
/**
* 為實際對象Tested和代理對象TestedProxy提供對外接口
*/
public interface IObject {
void request;
}
RealObj 是實際處理 request 邏輯的對象,但是出于設(shè)計的考量,需要對RealObj內(nèi)部的方法調(diào)用進行控制訪問。
public class RealObject implements IObject {
@Override
public void request {
// 模擬一些操作
}
}
ObjProxy 是 RealObj 的代理類,其同樣實現(xiàn)了 IObject 接口,所以具有相同的對外方法。客戶端與 RealObj 的所有交互,都必須通過 ObjProxy。
public class ObjProxy implements IObject {
IObject realT;
public ObjProxy(IObject t) {
realT = t;
}
@Override
public void request {
if (isAllow)
realT.request;
}
/**
* 模擬針對請求的校驗判斷
*/
private boolean isAllow {
return true;
}
}
番外
代理模式和裝飾者模式不管是在類圖,還是在代碼實現(xiàn)上,幾乎是一樣的,但我們?yōu)楹芜€要進行劃分呢?其實學(xué)設(shè)計模式,不能拘泥于格式,不能死記形式,重要的是要理解模式背后的意圖,意圖只有一個,但實現(xiàn)的形式卻可能多種多樣。這也就是為何那么多變體依然屬于xx設(shè)計模式的原因。
代理模式的意圖是替代真正的對象以實現(xiàn)訪問控制,而裝飾者模式的意圖是為對象加入額外的行為。
動態(tài)代理
JAVA 的動態(tài)代理可以動態(tài)的創(chuàng)建代理并動態(tài)的處理所代理方法的調(diào)用,在動態(tài)代理上所做的所以調(diào)用都會被重定向到單一的調(diào)用處理器上,它的工作是揭示調(diào)用的類型并確定相應(yīng)的策略。類圖見下:
還以上面的代碼為例,這是對外的接口 IObject:
public interface IObject {
void request;
}
這是 InvocationHandler 的實現(xiàn)類,類圖中 Proxy 的方法調(diào)用都會被系統(tǒng)傳入此類,即 invoke 方法,而 ObjProxyHandler 又持有著 RealObject 實例,所以 ObjProxyHandler 是“真正”對 RealObject 對象進行訪問控制的代理類。
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方法時,進行校驗
if (method.getName.equals("request") && !isAllow)
return ;
return method.invoke(realT, args);
}
/**
* 模擬針對請求的校驗判斷
*/
private boolean isAllow {
return false;
}
}
RealObj 是實際處理 request 邏輯的對象。
public class RealObject implements IObject {
@Override
public void request {
// 模擬一些操作
}
}
動態(tài)代理的使用方法如下:我們通過 Proxy.newProxyInstance 靜態(tài)方法來創(chuàng)建代理,其參數(shù)如下,一個類加載器、一個代理實現(xiàn)的接口列表、一個 InvocationHandler 的接口實現(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方法會被調(diào)用
}
Proxy源碼
來看下Proxy 源碼,當(dāng)我們 newProxyInstance(…) 時,首先系統(tǒng)會進行判空處理,之后獲取我們實際的 Proxy 代理類 Class 對象,再通過一個參數(shù)的構(gòu)造方法生成我們的代理對象 p(p : 返回值),這里能看出來 p 是持有我們的對象 h 的。注意 cons.setAccessible(true) 表示,即使是 cl 是私有構(gòu)造,也可以獲得對象。源碼見下:
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(…) 是用來檢查并獲取實際代理對象的。首先會有一個65535的接口限制檢測,隨后從代理緩存 ProxyClassCache 中獲取代理類,如果給定的接口不存在,則通過 ProxyClassFactory 新建。見下:
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,是一個靜態(tài)常量,所以在我們類加載時,其就已經(jīng)被初始化完畢了。見下:
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)用處理器對象 InvocationHandler,并將當(dāng)前Proxy方法調(diào)用調(diào)度給 InvocationHandler。
是不是與上面的代理思維很像,至于這兩個方法何時被調(diào)用的,推測是寫在了本地方法內(nèi),當(dāng)我們調(diào)用 proxy.request 方法時(系統(tǒng)創(chuàng)建Proxy時,會自動 implements 用戶傳遞的接口,可以為多個),系統(tǒng)就會調(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);
}
ProxyClassFactory
重點是ProxyClassFactory 類,這里的邏輯不少,所以我將ProxyClassFactory 單獨抽出來了。能看到,首先其會檢測當(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");
}
之后會進行判斷是否為接口。這也是我們說的第二個參數(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);
}
檢測非 public 修飾符的 interface 是否在是同一個包名,如果不是則拋出異常:
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");
}
}
}
檢驗通過后,會 getMethods(…) 獲取接口內(nèi)的全部方法。
隨后會對 methords 進行一個排序。具體的代碼我就不貼了,排序規(guī)則是:如果方法相等(返回值和方法簽名一樣)或同是一個接口內(nèi)方法,則當(dāng)前順序不變,如果兩個方法所在的接口存在繼承關(guān)系,則父類在前,子類在后。
之后 validateReturnTypes(…) 判斷 methords 是否存在方法簽名相同并且返回值類型也相同的methord,如果有則拋出異常。
接著通過
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è)計模式:中國電力出版社
版權(quán)聲明:本文為CSDN博主「mjzuo」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明。
原文鏈接:
https://blog.csdn.net/mingjiezuo/article/details/105930334