1. 程式人生 > 其它 >java 動態代理_Java動態代理的原理

java 動態代理_Java動態代理的原理

技術標籤:java 動態代理

Java動態代理機制的出現,使得 Java 開發人員不用手工編寫代理類,只要簡單地指定一組介面及委託類物件,便能動態地獲得代理類。代理類會負責將所有的方法呼叫分派到委託物件上反射執行,在分派執行的過程中,開發人員還可以按需調整委託類物件及其功能,這是一套非常靈活有彈性的代理框架。下面我們開始動態代理的學習。

動態代理的簡要說明

在java的動態代理機制中,有兩個重要的類或介面,一個是 InvocationHandler(Interface)、另一個則是 Proxy(Class)。

01

InvocationHandler(interface)的描述

InvocationHandleris the interface implemented by the invocation handler of a proxy instance.Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.

每一個動態代理類都必須要實現InvocationHandler這個介面,並且每個代理類的例項都關聯到了一個handler,當我們通過代理物件呼叫 一個方法的時候,這個方法的呼叫就會被轉發為由InvocationHandler這個介面的 invoke 方法來進行呼叫。我們來看看InvocationHandler這個介面的唯一一個方法 invoke 方法:

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

這個方法接收三個引數和返回一個Object型別,它們分別代表的意思如下:

  • proxy:指代我們所代理的那個真實物件
  • method:指代的是我們所要呼叫真實物件的方法的Method物件
  • args:指代的是呼叫真實物件某個方法時接受的引數

返回的Object是指真實物件方法的返回型別,以上會在接下來的例子中加以深入理解。

the value to returnfrom the method invocation on the proxy instance.

02

Proxy(Class)的描述

Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods.

Proxy這個類的作用就是用來動態建立一個代理物件。我們經常使用的是newProxyInstance這個方法:

publicstaticObject newProxyInstance(ClassLoader loader, Class>[] interfaces,  InvocationHandler h)  throwsIllegalArgumentException

引數的理解:

// 一個ClassLoader物件,定義了由哪個ClassLoader物件來對生成的代理物件進行載入loader - the class loader to define the proxy class// 一個Interface物件的陣列,表示的是我將要給我需要代理的物件提供一組什麼介面interfaces - the list of interfaces for the proxy class to implement// 一個InvocationHandler物件,表示的是當我這個動態代理物件在呼叫方法的時候,會關聯到哪一個InvocationHandler物件上h - the invocation handler to dispatch method invocations to

返回結果的理解:一個代理物件的例項

a proxy instance with the specified invocation handler of a proxy class that isdefinedby the specified class loader and that implements the specified interfaces

簡單的Java代理

我們建立一個Java專案用於對動態代理的測試與理解,專案結構如下:

7e92c9d61687fe702773f87988ff99fb.png

01

先定義一個介面Interface,新增兩個方法

package com.huhx.proxy;publicinterfaceInterface{void getMyName();String getNameById(String id);}

02

定義一個真實的實現上述介面的類,RealObject

package com.huhx.proxy;publicclassRealObjectimplementsInterface{@Overridepublicvoid getMyName() {System.out.println("my name is huhx");}@OverridepublicString getNameById(String id) {System.out.println("argument id: "+ id);return"huhx";}}

03

定義一個代理物件,也實現了上述的Interface介面

package com.huhx.proxy;publicclassSimpleProxyimplementsInterface{privateInterface proxied;publicSimpleProxy(Interface proxied) {this.proxied = proxied;}@Overridepublicvoid getMyName() {System.out.println("proxy getmyname");        proxied.getMyName();}@OverridepublicString getNameById(String id) {System.out.println("proxy getnamebyid");return proxied.getNameById(id);}}

04

SimpleMain在Main方法中,測試上述的結果

package com.huhx.proxy;publicclassSimpleMain{privatestaticvoid consume(Interface iface) {        iface.getMyName();String name = iface.getNameById("1");System.out.println("name: "+ name);}publicstaticvoid main(String[] args) {        consume(newRealObject());System.out.println("========================================================");        consume(newSimpleProxy(newRealObject()));}}

05

執行的結果如下

my name is huhxargument id: 1name: huhx========================================================proxy getmynamemy name is huhxproxy getnamebyidargument id: 1name: huhx

Java的動態代理

完成了上述簡單的Java代理,現在我們開始學習Java的動態代理,它比代理的思想更向前一步,因為它可以動態地建立代理並動態的處理對所代理方法的呼叫。在動態代理上所做的所有呼叫都會被重定向到單一的呼叫處理器上,它的工作是揭示呼叫的型別並確定相應的對策。下面我們通過案例來加深Java動態代理的理解:

01

建立一個繼承了InvocationHandler的處理器:DynamicProxyHandler

package com.huhx.dynamicproxy;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.util.Arrays;publicclassDynamicProxyHandlerimplementsInvocationHandler{privateObject proxied;publicDynamicProxyHandler(Object proxied) {System.out.println("dynamic proxy handler constuctor: "+ proxied.getClass());this.proxied = proxied;}@OverridepublicObject invoke(Object proxy, Method method, Object[] args) throwsThrowable{System.out.println("dynamic proxy name: "+ proxy.getClass());System.out.println("method: "+ method.getName());System.out.println("args: "+ Arrays.toString(args));Object invokeObject = method.invoke(proxied, args);if(invokeObject != null) {System.out.println("invoke object: "+ invokeObject.getClass());} else{System.out.println("invoke object is null");}return invokeObject;}}

02

我們寫一個測試的Main方法,DynamicProxyMain

package com.huhx.dynamicproxy;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;import com.huhx.proxy.Interface;import com.huhx.proxy.RealObject;publicclassDynamicProxyMain{publicstaticvoid consumer(Interface iface) {        iface.getMyName();String name = iface.getNameById("1");System.out.println("name: "+ name);}publicstaticvoid main(String[] args) throwsException, SecurityException, Throwable{RealObject realObject = newRealObject();        consumer(realObject);System.out.println("==============================");// 動態代理ClassLoader classLoader = Interface.class.getClassLoader();Class>[] interfaces = newClass[] { Interface.class};InvocationHandler handler = newDynamicProxyHandler(realObject);Interface proxy = (Interface) Proxy.newProxyInstance(classLoader, interfaces, handler);System.out.println("in dynamicproxyMain proxy: "+ proxy.getClass());        consumer(proxy);}}

03

執行結果如下

my name is huhxargument id: 1name: huhx==============================dynamic proxy handler constuctor: class com.huhx.proxy.RealObjectin dynamicproxyMain proxy: class com.sun.proxy.$Proxy0dynamic proxy name: class com.sun.proxy.$Proxy0method: getMyNameargs: nullmy name is huhxinvoke objectisnulldynamic proxy name: class com.sun.proxy.$Proxy0method: getNameByIdargs: [1]argument id: 1invoke object: class java.lang.Stringname: huhx

從以上輸出結果,我們可以得出以下結論:

  • 與代理物件相關聯的InvocationHandler,只有在代理物件呼叫方法時,才會執行它的invoke方法
  • invoke的三個引數的理解:Object proxy是代理的物件, Method method是真實物件中呼叫方法的Method類, Object[] args是真實物件中呼叫方法的引數

Java動態代理的原理

動態代理的關鍵程式碼就是Proxy.newProxyInstance(classLoader, interfaces, handler),我們跟進原始碼看看

publicstaticObject newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h) throwsIllegalArgumentException{  // handler不能為空if(h == null) {thrownewNullPointerException();}finalClass>[] intfs = interfaces.clone();finalSecurityManager sm = System.getSecurityManager();if(sm != null) {        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);}/*     * Look up or generate the designated proxy class.     */  // 通過loader和介面,得到代理的Class物件Class> cl = getProxyClass0(loader, intfs);/*     * Invoke its constructor with the designated invocation handler.     */try{finalConstructor> cons = cl.getConstructor(constructorParams);finalInvocationHandler ih = h;if(sm != null&& ProxyAccessHelper.needsNewInstanceCheck(cl)) {// create proxy instance with doPrivilege as the proxy class may// implement non-public interfaces that requires a special permissionreturnAccessController.doPrivileged(newPrivilegedAction() {publicObject run() {return newInstance(cons, ih);}});} else{       // 建立代理物件的例項return newInstance(cons, ih);}} catch(NoSuchMethodException e) {thrownewInternalError(e.toString());}}

我們看一下newInstance方法的原始碼:

privatestaticObject newInstance(Constructor> cons, InvocationHandler h) {try{return cons.newInstance(newObject[] {h} );} catch(IllegalAccessException| InstantiationException e) {thrownewInternalError(e.toString());} catch(InvocationTargetException e) {Throwable t = e.getCause();if(t instanceofRuntimeException) {throw(RuntimeException) t;} else{thrownewInternalError(t.toString());}}}

當我們通過代理物件呼叫 一個方法的時候,這個方法的呼叫就會被轉發為由InvocationHandler這個介面的 invoke 方法來進行呼叫。

體現這句話的程式碼,我在原始碼中沒有找到,於是我在測試類的main方法中加入以下程式碼:

if(proxy instanceofProxy) {InvocationHandler invocationHandler = Proxy.getInvocationHandler(proxy);    invocationHandler.invoke(proxy, realObject.getClass().getMethod("getMyName"), null);System.out.println("--------------------------------------");}

這段程式碼的輸出結果如下,與上述中呼叫代理物件中的getMyName方法輸出是一樣的,不知道Jvm底層是否是這樣判斷的:

dynamic proxy handler constuctor: class com.huhx.proxy.RealObjectdynamic proxy name: class com.sun.proxy.$Proxy0method: getMyNameargs: nullmy name is huhxinvoke objectisnull--------------------------------------