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

靜態(tài)代理
創(chuàng)建一個接口,然后創(chuàng)建被代理的類實現(xiàn)該接口并且實現(xiàn)該接口中的抽象方法。之后再創(chuàng)建一個代理類,同時使其也實現(xiàn)這個接口。在代理類中持有一個被代理對象的引用,而后在代理類方法中調(diào)用該對象的方法。
代碼如下:
接口
- 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í)行具體方法時通過所持用的被代理類完成調(diào)用。
- public class ProxyTest {
- public static void main(String[] args) {
- HelloProxyhelloProxy=newHelloProxy();
- helloProxy.sayHello();
- }
- }
靜態(tài)代理的本質(zhì):由程序員創(chuàng)建或工具生成代理類的源碼,再編譯代理類。所謂靜態(tài)也就是在程序運行前就已經(jīng)存在代理類的字節(jié)碼文件,代理類和委托類的關(guān)系在運行前就確定了。
動態(tài)代理
動態(tài)代理類的源碼是在程序運行期間由JVM根據(jù)反射等機制動態(tài)的生成,所以不存在代理類的字節(jié)碼文件。代理類和委托類的關(guān)系是在程序運行時確定。JDK中關(guān)于動態(tài)代理的重要api如下:
java.lang.reflect.Proxy 這是Java 動態(tài)代理機制生成的所有動態(tài)代理類的父類,它提供了一組靜態(tài)方法來為一組接口動態(tài)地生成代理類及其對象。 最重要的方法是:
- static Object newProxyInstance(ClassLoader loader, Class[]interfaces, InvocationHandler h)
該方法用于為指定類裝載器、一組接口及調(diào)用處理器生成動態(tài)代理類實例
java.lang.reflect.InvocationHandler 這是調(diào)用處理器接口,定義了一個invoke 方法,用于集中處理在動態(tài)代理類對象上的方法調(diào)用,通常在該方法中實現(xiàn)對委托類的代理訪問。每次生成動態(tài)代理類對象時都要指定一個對應(yīng)的調(diào)用處理器對象。Object invoke(Object proxy, Method method, Object[] args) 該方法負責集中處理動態(tài)代理類上的所有方法調(diào)用。第一個參數(shù)既是代理類實例,第二個參數(shù)是被調(diào)用的方法對象 ,第三個方法是調(diào)用參數(shù)。調(diào)用處理器根據(jù)這三個參數(shù)進行預(yù)處理或分派到委托類實例上反射執(zhí)行
java.lang.ClassLoader 這是類裝載器類,負責將類的字節(jié)碼裝載到Java 虛擬機(JVM)中并為其定義類對象,然后該類才能被使用。Proxy靜態(tài)方法生成動態(tài)代理類同樣需要通過類裝載器來進行裝載才能使用,它與普通類的唯一區(qū)別就是其字節(jié)碼是由JVM 在運行時動態(tài)生成的而非預(yù)先存在于任何一個.class 文件中。 每次生成動態(tài)代理類對象時都需要指定一個類裝載器對象。我們來看一下動態(tài)代理的實例:
接口
- public interface HelloInterface{
- void sayHello();
- }
被代理類
- public class Hello implements HelloInterface{
- public void sayHello() {
- System.out.println("Hello Kevin!");
- }
- }
實現(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;
- }
- }
測試類
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Proxy;
- public class DynamicProxyTest{
- public static void main(String[] args) {
- HelloInterface hello = new Hello();
- //把hello實例傳入動態(tài)代理處理器
- InvocationHandler handler = new ProxyHandler(hello);
- //生成動態(tài)代理類實例
- HelloInterface proxyHello = (HelloInterface)Proxy.newProxyInstance(hello.getClass().getClassLoader(),hello.getClass().getInterfaces(), handler);
- proxyHello.sayHello();
- }
- }
運行代碼
- Before invoke sayHello
- Hello Kevin!
- After invoke sayHello
我們可以看到,動態(tài)代理與靜態(tài)代理相比較,最大的好處是接口中聲明的所有方法都被轉(zhuǎn)移到調(diào)用處理器一個集中的方法中處理(InvocationHandler.invoke),生成不同類的代理實例我們只需要在類DynamicProxyTest中處理即可;而靜態(tài)代理需要代理多個類的時候,由于代理對象要實現(xiàn)與目標對象一致的接口,則會遇到下面的問題:
只維護一個代理類,由這個代理類實現(xiàn)多個接口,但是這樣就導(dǎo)致代理類過于龐大;
新建多個代理類,每個目標對象對應(yīng)一個代理類,但是這樣會產(chǎn)生過多的代理類當接口;
需要增加、刪除、修改方法的時候,目標對象與代理類都要同時修改,不易維護。
動態(tài)代理類也有小小的遺憾,那就是它只能為接口創(chuàng)建代理!如果想對沒有實現(xiàn)接口的類創(chuàng)建代理則無能為力。為了解決這種情況,我們通常使用cglib技術(shù),其在AOP(例如spring)和ORM(例如Hibernate)中有廣泛的應(yīng)用,在這里就不對cglib進行展開介紹了。
動態(tài)代理類的生成
我們再來看一個實例,修改類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());
- }
運行結(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)建的動態(tài)代理類,是在運行時創(chuàng)建生成的。動態(tài)代理類的格式是“$ProxyN”,其中 N 是一個逐一遞增的阿拉伯數(shù)字,代表Proxy 類第N 次生成的動態(tài)代理類,并不是每次調(diào)用Proxy 的靜態(tài)方法創(chuàng)建動態(tài)代理類都會使得N 值增加,原因是如果對同一組接口(包括接口排列的順序相同)試圖重復(fù)創(chuàng)建動態(tài)代理類,它會很聰明地返回先前已經(jīng)創(chuàng)建好的代理類的類對象,而不會再嘗試去創(chuàng)建一個全新的代理類,這樣可以節(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的實例上調(diào)用其代理的接口中所聲明的方法時,這些方法最終都會由調(diào)用處理器的invoke 方法執(zhí)行;
2. 代理類的根類java.lang.Object 中的三個方法:hashCode,equals 和 toString也同樣會被分派到調(diào)用處理器的invoke 方法中執(zhí)行。
靜態(tài)代理和動態(tài)代理最重要的四個知識點
1.靜態(tài)代理在程序運行前就已經(jīng)存在代理類的字節(jié)碼文件中確認了代理類和委托類的關(guān)系;
2.動態(tài)代理類的源碼是在程序運行期間由JVM根據(jù)反射等機制動態(tài)的生成,所以不存在代理類的字節(jié)碼文件。代理類和委托類的關(guān)系是在程序運行時確定。 動態(tài)代理根據(jù)接口或目標對象,計算出代理類的字節(jié)碼,然后再加載到JVM中使用。其實現(xiàn)原理如下:由于JVM通過字節(jié)碼的二進制信息加載類的,那么,如果我們在運行期系統(tǒng)中,遵循Java編譯系統(tǒng)組織.class文件的格式和結(jié)構(gòu),生成相應(yīng)的二進制數(shù)據(jù),然后再把這個二進制數(shù)據(jù)加載轉(zhuǎn)換成對應(yīng)的類,這樣,就完成了在代碼中,動態(tài)創(chuàng)建一個類的能力了。

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