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

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

3.靜態代理的缺點是在程序規模稍大時,維護代理類的成本高,靜態代理無法勝任;
4.動態代理只能為實現了接口的類創建代理。