Java 靜態代理
靜態代理通常用於對原有商務邏輯的擴充。比如持有二方包的某個類,並調用了其中的某些方法。然後出於某種原因,比如記錄日誌、列印方法執行時間,但是又不好將這些邏輯寫入二方包的方法裡。所以可以建立一個代理類實現和二方方法相同的方法,通過讓代理類持有真實對象,然後在原代碼中調用代理類方法,來達到添加我們需要商務邏輯的目的。
這其實也就是代理模式的一種實現,通過對真實對象的封裝,來實現擴充性。
一個典型的代理模式通常有三個角色,這裡稱之為**代理三要素** 共同介面
public interface Action { public void doSomething();}
真實對象
public class RealObject implements Action{ public void doSomething() { System.out.println("do something"); }}
代理對象
public class Proxy implements Action { private Action realObject; public Proxy(Action realObject) { this.realObject = realObject; } public void doSomething() { System.out.println("proxy do"); realObject.doSomething(); }}
運行代碼
Proxy proxy = new Proxy(new RealObject()); proxy.doSomething();
這種代理模式也最為簡單,就是通過proxy持有realObject的引用,並進行一層封裝。 靜態代理的優點和缺點
先看看代理模式的優點: 擴充原功能,不侵入原代碼。
再看看這種代理模式的缺點:
假如有這樣一個需求,有十個不同的RealObject,同時我們要去代理的方法是不同的,比要代理方法:doSomething、doAnotherThing、doTwoAnotherThing,添加代理前,原代碼可能是這樣的:
realObject.doSomething();realObject1.doAnotherThing();realObject2.doTwoAnother();
為瞭解決這個問題,我們有方案一:
為這些方法建立不同的代理類,代理後的代碼是這樣的:
proxy.doSomething();proxy1.doAnotherThing();proxy2.doTwoAnother();
當然,也有方案二:
通過建立一個proxy,持有不同的realObject,實現Action1、Action2、Action3介面,來讓代碼變成這樣:
proxy.doSomething();proxy.doAnotherThing();proxy.doTwoAnother();
於是你的代理模型會變成這樣:
毫無疑問,僅僅為了擴充同樣的功能,在方案一種,我們會重複建立多個邏輯相同,僅僅RealObject引用不同的Proxy。
而在方案二中,會導致proxy的膨脹,而且這種膨脹往往是無意義的。此外,假如方法簽名是相同的,更需要在調用的時候引入額外的判斷邏輯。
java 動態代理
搞清楚靜態代理的缺點十分重要,因為動態代理的目的就是為瞭解決靜態代理的缺點。通過使用動態代理,我們可以通過在運行時,動態產生一個持有RealObject、並實現代理介面的Proxy,同時注入我們相同的擴充邏輯。哪怕你要代理的RealObject是不同的對象,甚至代理不同的方法,都可以動過動態代理,來擴充功能。
簡單理解,動態代理就是我們上面提到的方案一,只不過這些proxy的建立都是自動的並且是在運行期產生的。 動態代理基本用法
使用動態代理,需要將要擴充的功能寫在一個InvocationHandler 實作類別裡:
public class DynamicProxyHandler implements InvocationHandler { private Object realObject; public DynamicProxyHandler(Object realObject) { this.realObject = realObject; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //代理擴充邏輯 System.out.println("proxy do"); return method.invoke(realObject, args); }}
這個Handler中的invoke方法中實現了代理類要擴充的公用功能。
到這裡,需要先看一下這個handler的用法:
public static void main(String[] args) { RealObject realObject = new RealObject(); Action proxy = (Action) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Action.class}, new DynamicProxyHandler(realObject)); proxy.doSomething();}
Proxy.newProxyInstance 傳入的是一個ClassLoader, 一個代理介面,和我們定義的handler,返回的是一個Proxy的執行個體。
仔細體會這個過程,其實有點類似我們在靜態代理中提到的方案一,產生了一個包含我們擴充功能,持有RealObject引用,實現Action介面的代理執行個體Proxy。只不過這個Proxy不是我們自己寫的,而是java幫我們產生的,有沒有一點動態味道。
讓我們再回顧一下代理三要素:真實對象:RealObject,代理介面:Action,代理執行個體:Proxy
上面的代碼實含義也就是,輸入 RealObject、Action,返回一個Proxy。妥妥的代理模式。
綜上,動態產生+代理模式,也就是動態代理。
網上搜了不少文章,到了這裡,接下來就是和cglib等動態代理實現方法做一下橫向比較。本文不做橫向比較,為了不偏離主題,接下來做縱向挖掘。 看一下源碼
道理清楚了,但是這篇文章題目是搞懂,所以來看一下這個Proxy是如何自動被產生的。入口就在newProxyInstance方法,核心代碼如下:
private static final Class<?>[] constructorParams = { InvocationHandler.class };public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException{ Class<?> cl = getProxyClass0(loader, intfs); ... final Constructor<?> cons = cl.getConstructor(constructorParams); if (!Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { cons.setAccessible(true); return null; } }); }return cons.newInstance(new Object[]{h});}
整體流程就是:
1、組建代理程式類Proxy的Class對象。
2、如果Class範圍為私人,通過 setAccessible 支援訪問
3、擷取Proxy Class建構函式,建立Proxy代理執行個體。 產生Proxy的Class檔案
產生Class對象的方法中,先是通過傳進來的ClassLoader參數和Class[] 數組作為組成鍵,維護了一個對於Proxy的Class對象的緩衝。這樣需要相同Proxy的Class對象時,只需要建立一次。
第一次建立該Class檔案時,為了安全執行緒,方法進行了大量的處理,最後會來到ProxyClassFactory的apply方法中,經過以下流程:
1、校正傳入的介面是否由傳入的ClassLoader載入的。
2、校正傳入是否是介面的Class對象。
3、校正是否傳入重複的介面。
4、拼裝代理類包名和類名,產生.class 檔案的位元組碼。
5、調用native方法,傳入位元組碼,產生Class對象。
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";long num = nextUniqueNumber.getAndIncrement();String proxyName = proxyPkg + proxyClassNamePrefix + num;byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags);return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
看一下第四步產生.class檔案位元組碼的過程,主要分為兩個階段:
addProxyMethod(hashCodeMethod, Object.class);addProxyMethod(equalsMethod, Object.class);addProxyMethod(toStringMethod, Object.class);for (int i = 0; i < interfaces.length; i++) { Method[] methods = interfaces[i].getMethods(); for (int j = 0; j < methods.length; j++) { addProxyMethod(methods[j], interfaces[i]); }}methods.add(this.generateConstructor()); for (List<ProxyMethod> sigmethods : proxyMethods.values()) { for (ProxyMethod pm : sigmethods) { fields.add(new FieldInfo(pm.methodFieldName, "Ljava/lang/reflect/Method;", ACC_PRIVATE | ACC_STATIC)); methods.add(pm.generateMethod()); } }methods.add(generateStaticInitializer());
第一個階段的代碼比較清晰,主要就是添加各種Method,比如toString()、equals,以及傳入的代理介面中的方法。再添加一下構造方法以及靜態初始化方法。這要構成了一個對象,儲存產生Proxy的Class的一些資訊。
到了這裡,已經把要構造的Proxy的方法基本定義完成了,接下來就要產生這個.class檔案了。