詳解Java中的靜態(tài)代理和動(dòng)態(tài)代理
代理是一種設(shè)計(jì)模式
在代理模式(Proxy Pattern)中,一個(gè)類代表另一個(gè)類的功能。這種類型的設(shè)計(jì)模式屬于結(jié)構(gòu)型模式。在代理模式中,我們創(chuàng)建具有現(xiàn)有對(duì)象的對(duì)象,以便向外界提供功能接口。目的:為其他對(duì)象提供一種代理以控制對(duì)這個(gè)對(duì)象的訪問。
類關(guān)系圖:

靜態(tài)代理
創(chuàng)建一個(gè)接口,然后創(chuàng)建被代理的類實(shí)現(xiàn)該接口并且實(shí)現(xiàn)該接口中的抽象方法。之后再創(chuàng)建一個(gè)代理類,同時(shí)使其也實(shí)現(xiàn)這個(gè)接口。在代理類中持有一個(gè)被代理對(duì)象的引用,而后在代理類方法中調(diào)用該對(duì)象的方法。
代碼如下:
接口
- public interface HelloInterface{
- void sayHello();
- }
被代理類
- public class Hello implements HelloInterface{
- public void sayHello() {
- System.out.println("Hello Kevin!");
- }
- }
代理類
- public class HelloProxy implements HelloInterface{
- private HelloInterface helloInterface=newHello();
- public void sayHello() {
- System.out.println("Beforeinvoke sayHello" );
- helloInterface.sayHello(); //調(diào)用被代理類Hello中的sayHello方法
- System.out.println("Afterinvoke sayHello");
- }
- }
代理類調(diào)用
被代理類被傳遞給了代理類HelloProxy,代理類在執(zhí)行具體方法時(shí)通過所持用的被代理類完成調(diào)用。
- public class ProxyTest {
- public static void main(String[] args) {
- HelloProxyhelloProxy=newHelloProxy();
- helloProxy.sayHello();
- }
- }
靜態(tài)代理的本質(zhì):由程序員創(chuàng)建或工具生成代理類的源碼,再編譯代理類。所謂靜態(tài)也就是在程序運(yùn)行前就已經(jīng)存在代理類的字節(jié)碼文件,代理類和委托類的關(guān)系在運(yùn)行前就確定了。
動(dòng)態(tài)代理
動(dòng)態(tài)代理類的源碼是在程序運(yùn)行期間由JVM根據(jù)反射等機(jī)制動(dòng)態(tài)的生成,所以不存在代理類的字節(jié)碼文件。代理類和委托類的關(guān)系是在程序運(yùn)行時(shí)確定。JDK中關(guān)于動(dòng)態(tài)代理的重要api如下:
java.lang.reflect.Proxy 這是Java 動(dòng)態(tài)代理機(jī)制生成的所有動(dòng)態(tài)代理類的父類,它提供了一組靜態(tài)方法來為一組接口動(dòng)態(tài)地生成代理類及其對(duì)象。 最重要的方法是:
- static Object newProxyInstance(ClassLoader loader, Class[]interfaces, InvocationHandler h)
該方法用于為指定類裝載器、一組接口及調(diào)用處理器生成動(dòng)態(tài)代理類實(shí)例
java.lang.reflect.InvocationHandler 這是調(diào)用處理器接口,定義了一個(gè)invoke 方法,用于集中處理在動(dòng)態(tài)代理類對(duì)象上的方法調(diào)用,通常在該方法中實(shí)現(xiàn)對(duì)委托類的代理訪問。每次生成動(dòng)態(tài)代理類對(duì)象時(shí)都要指定一個(gè)對(duì)應(yīng)的調(diào)用處理器對(duì)象。Object invoke(Object proxy, Method method, Object[] args) 該方法負(fù)責(zé)集中處理動(dòng)態(tài)代理類上的所有方法調(diào)用。第一個(gè)參數(shù)既是代理類實(shí)例,第二個(gè)參數(shù)是被調(diào)用的方法對(duì)象 ,第三個(gè)方法是調(diào)用參數(shù)。調(diào)用處理器根據(jù)這三個(gè)參數(shù)進(jìn)行預(yù)處理或分派到委托類實(shí)例上反射執(zhí)行
java.lang.ClassLoader 這是類裝載器類,負(fù)責(zé)將類的字節(jié)碼裝載到Java 虛擬機(jī)(JVM)中并為其定義類對(duì)象,然后該類才能被使用。Proxy靜態(tài)方法生成動(dòng)態(tài)代理類同樣需要通過類裝載器來進(jìn)行裝載才能使用,它與普通類的唯一區(qū)別就是其字節(jié)碼是由JVM 在運(yùn)行時(shí)動(dòng)態(tài)生成的而非預(yù)先存在于任何一個(gè).class 文件中。 每次生成動(dòng)態(tài)代理類對(duì)象時(shí)都需要指定一個(gè)類裝載器對(duì)象。我們來看一下動(dòng)態(tài)代理的實(shí)例:
接口
- public interface HelloInterface{
- void sayHello();
- }
被代理類
- public class Hello implements HelloInterface{
- public void sayHello() {
- System.out.println("Hello Kevin!");
- }
- }
實(shí)現(xiàn)InvocationHandler接口,創(chuàng)建自己的調(diào)用處理器
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- public class ProxyHandler implements InvocationHandler{
- private Object object;
- public ProxyHandler(Object object){
- this.object = object;
- }
- public Object invoke(Object proxy,Method method, Object[] args) throws Throwable {
- System.out.println("Before invoke " + method.getName());
- method.invoke(object, args);
- System.out.println("After invoke" + method.getName());
- return null;
- }
- }
測(cè)試類
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Proxy;
- public class DynamicProxyTest{
- public static void main(String[] args) {
- HelloInterface hello = new Hello();
- //把hello實(shí)例傳入動(dòng)態(tài)代理處理器
- InvocationHandler handler = new ProxyHandler(hello);
- //生成動(dòng)態(tài)代理類實(shí)例
- HelloInterface proxyHello = (HelloInterface)Proxy.newProxyInstance(hello.getClass().getClassLoader(),hello.getClass().getInterfaces(), handler);
- proxyHello.sayHello();
- }
- }
運(yùn)行代碼
- Before invoke sayHello
- Hello Kevin!
- After invoke sayHello
我們可以看到,動(dòng)態(tài)代理與靜態(tài)代理相比較,最大的好處是接口中聲明的所有方法都被轉(zhuǎn)移到調(diào)用處理器一個(gè)集中的方法中處理(InvocationHandler.invoke),生成不同類的代理實(shí)例我們只需要在類DynamicProxyTest中處理即可;而靜態(tài)代理需要代理多個(gè)類的時(shí)候,由于代理對(duì)象要實(shí)現(xiàn)與目標(biāo)對(duì)象一致的接口,則會(huì)遇到下面的問題:
只維護(hù)一個(gè)代理類,由這個(gè)代理類實(shí)現(xiàn)多個(gè)接口,但是這樣就導(dǎo)致代理類過于龐大;
新建多個(gè)代理類,每個(gè)目標(biāo)對(duì)象對(duì)應(yīng)一個(gè)代理類,但是這樣會(huì)產(chǎn)生過多的代理類當(dāng)接口;
需要增加、刪除、修改方法的時(shí)候,目標(biāo)對(duì)象與代理類都要同時(shí)修改,不易維護(hù)。
動(dòng)態(tài)代理類也有小小的遺憾,那就是它只能為接口創(chuàng)建代理!如果想對(duì)沒有實(shí)現(xiàn)接口的類創(chuàng)建代理則無能為力。為了解決這種情況,我們通常使用cglib技術(shù),其在AOP(例如spring)和ORM(例如Hibernate)中有廣泛的應(yīng)用,在這里就不對(duì)cglib進(jìn)行展開介紹了。
動(dòng)態(tài)代理類的生成
我們?cè)賮砜匆粋€(gè)實(shí)例,修改類DynamicProxyTest,代碼如下:
- public static void main(String[] args) {
- System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
- HelloInterface hello = new Hello();
- InvocationHandler handler = new ProxyHandler(hello);
- HelloInterface proxyHello=(HelloInterface) Proxy.newProxyInstance(hello.getClass().getClassLoader(), hello.getClass().getInterfaces(),handler);
- proxyHello.sayHello();
- System.out.println(proxyHello.getClass().getName());
- }
運(yùn)行結(jié)果
- Before invoke sayHello
- Hello Kevin!
- After invoke sayHello
- com.sun.proxy.$Proxy0
我們發(fā)現(xiàn)proxyHello的類型是.$Proxy0而不是HelloInterface。我們通過反編譯來查看$Proxy0的源碼,在工程的com.sun.proxy目錄下。注意:必須添加下面的代碼
- System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
$Proxy0就是由JDK創(chuàng)建的動(dòng)態(tài)代理類,是在運(yùn)行時(shí)創(chuàng)建生成的。動(dòng)態(tài)代理類的格式是“$ProxyN”,其中 N 是一個(gè)逐一遞增的阿拉伯?dāng)?shù)字,代表Proxy 類第N 次生成的動(dòng)態(tài)代理類,并不是每次調(diào)用Proxy 的靜態(tài)方法創(chuàng)建動(dòng)態(tài)代理類都會(huì)使得N 值增加,原因是如果對(duì)同一組接口(包括接口排列的順序相同)試圖重復(fù)創(chuàng)建動(dòng)態(tài)代理類,它會(huì)很聰明地返回先前已經(jīng)創(chuàng)建好的代理類的類對(duì)象,而不會(huì)再嘗試去創(chuàng)建一個(gè)全新的代理類,這樣可以節(jié)省不必要的代碼重復(fù)生成,提高了代理類的創(chuàng)建效率。$Proxy0源碼如下:
- import com.my.demo2.HelloInterface;
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- import java.lang.reflect.Proxy;
- import java.lang.reflect.UndeclaredThrowableException;
- public final class $Proxy0 extends Proxy implements HelloInterface {
- private static Method m1;
- private static Method m3;
- private static Method m2;
- private static Method m0;
- public $Proxy0(InvocationHandlerparamInvocationHandler) { super(paramInvocationHandler); }
- public final boolean equals(ObjectparamObject) {
- try {
- return ((Boolean)this.h.invoke(this,m1, new Object[] { paramObject })).booleanValue();
- } catch (Error|RuntimeExceptionerror) {
- throw null;
- } catch (Throwable throwable) {
- throw newUndeclaredThrowableException(throwable);
- }
- }
- public final void sayHello() {
- try {
- this.h.invoke(this, m3, null);
- return;
- } catch (Error|RuntimeExceptionerror) {
- throw null;
- } catch (Throwable throwable) {
- throw newUndeclaredThrowableException(throwable);
- }
- }
- public final String toString() {
- try {
- return (String)this.h.invoke(this,m2, null);
- } catch (Error|RuntimeExceptionerror) {
- throw null;
- } catch (Throwable throwable) {
- throw newUndeclaredThrowableException(throwable);
- }
- }
- public final int hashCode() {
- try {
- return ((Integer)this.h.invoke(this,m0, null)).intValue();
- } catch (Error|RuntimeExceptionerror) {
- throw null;
- } catch (Throwable throwable) {
- throw newUndeclaredThrowableException(throwable);
- }
- }
- static {
- try {
- m1 = Class.forName("java.lang.Object").getMethod("equals",new Class[] { Class.forName("java.lang.Object") });
- m3 = Class.forName("com.my.demo2.HelloInterface").getMethod("sayHello",new Class[0]);
- m2 = Class.forName("java.lang.Object").getMethod("toString",new Class[0]);
- m0 = Class.forName("java.lang.Object").getMethod("hashCode",new Class[0]);
- return;
- } catch (NoSuchMethodExceptionnoSuchMethodException) {
- throw newNoSuchMethodError(noSuchMethodException.getMessage());
- } catch (ClassNotFoundExceptionclassNotFoundException) {
- throw new NoClassDefFoundError(classNotFoundException.getMessage());
- }
- }
- }
從上面的代碼中我們可以看到:
1. 在代理類$ProxyN的實(shí)例上調(diào)用其代理的接口中所聲明的方法時(shí),這些方法最終都會(huì)由調(diào)用處理器的invoke 方法執(zhí)行;
2. 代理類的根類java.lang.Object 中的三個(gè)方法:hashCode,equals 和 toString也同樣會(huì)被分派到調(diào)用處理器的invoke 方法中執(zhí)行。
靜態(tài)代理和動(dòng)態(tài)代理最重要的四個(gè)知識(shí)點(diǎn)
1.靜態(tài)代理在程序運(yùn)行前就已經(jīng)存在代理類的字節(jié)碼文件中確認(rèn)了代理類和委托類的關(guān)系;
2.動(dòng)態(tài)代理類的源碼是在程序運(yùn)行期間由JVM根據(jù)反射等機(jī)制動(dòng)態(tài)的生成,所以不存在代理類的字節(jié)碼文件。代理類和委托類的關(guān)系是在程序運(yùn)行時(shí)確定。 動(dòng)態(tài)代理根據(jù)接口或目標(biāo)對(duì)象,計(jì)算出代理類的字節(jié)碼,然后再加載到JVM中使用。其實(shí)現(xiàn)原理如下:由于JVM通過字節(jié)碼的二進(jìn)制信息加載類的,那么,如果我們?cè)谶\(yùn)行期系統(tǒng)中,遵循Java編譯系統(tǒng)組織.class文件的格式和結(jié)構(gòu),生成相應(yīng)的二進(jìn)制數(shù)據(jù),然后再把這個(gè)二進(jìn)制數(shù)據(jù)加載轉(zhuǎn)換成對(duì)應(yīng)的類,這樣,就完成了在代碼中,動(dòng)態(tài)創(chuàng)建一個(gè)類的能力了。

3.靜態(tài)代理的缺點(diǎn)是在程序規(guī)模稍大時(shí),維護(hù)代理類的成本高,靜態(tài)代理無法勝任;
4.動(dòng)態(tài)代理只能為實(shí)現(xiàn)了接口的類創(chuàng)建代理。