Java動態代理
引言
最近在看AOP代碼,其中利用到了Java動態代理機制來實現AOP織入。所以好好地把Java動態代理學習了一遍。其中重點感謝《Java動態代理機制分析及擴展》這篇博文。受益匪淺,比《Thinking in Java》講的還要詳細。本文在原文的基礎上增加了些其他參考資料的內容和自己的測試Demo及理解。
代理模式
首先,先來理解下什么是代理模式。
代理模式:給某一個對象提供一個代理,并由代理對象控制對原對象的引用。
代理模式包含如下角色:
- Subject:抽象主題角色
- Proxy:代理主題角色
- RealSubject:真實主題角色
Client將不直接調用RealSubject。而是通過Proxy作為中間人。Proxy可在調用RealSubject的request()方法前后加上一些處理。例如寫日志等。
//: typeinfo/SimpleProxyDemo.java
import static net.mindview.util.Print.*;
interface Interface {
void doSomething();
void somethingElse(String arg);
}
class RealObject implements Interface {
public void doSomething() { print("doSomething"); }
public void somethingElse(String arg) {
print("somethingElse " + arg);
}
}
class SimpleProxy implements Interface {
private Interface proxied;
public SimpleProxy(Interface proxied) {
this.proxied = proxied;
}
public void doSomething() {
print("SimpleProxy doSomething");
proxied.doSomething();
}
public void somethingElse(String arg) {
print("SimpleProxy somethingElse " + arg);
proxied.somethingElse(arg);
}
}
class SimpleProxyDemo {
public static void consumer(Interface iface) {
iface.doSomething();
iface.somethingElse("bonobo");
}
public static void main(String[] args) {
consumer(new RealObject());
consumer(new SimpleProxy(new RealObject()));
}
} /* Output:
doSomething
somethingElse bonobo
SimpleProxy doSomething
doSomething
SimpleProxy somethingElse bonobo
somethingElse bonobo
*///:~
Java的動態代理比代理的思想更向前邁進一步。因為它可以動態地創建代理并動態地處理對所代理方法的調用。在動態代理上所做的所有調用都會被重定向到單一的調用處理上,它的工作是揭示調用的類型并確定相應的對策。
下面是利用動態代理改寫后的。其中看不懂的類和接口,在下一部分會進行解釋。
//: typeinfo/SimpleDynamicProxy.java
import java.lang.reflect.*;
class DynamicProxyHandler implements InvocationHandler {
private Object proxied;
public DynamicProxyHandler(Object proxied) {
this.proxied = proxied;
}
public Object
invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("**** proxy: " + proxy.getClass() +
", method: " + method + ", args: " + args);
if(args != null)
for(Object arg : args)
System.out.println(" " + arg);
return method.invoke(proxied, args);
}
}
class SimpleDynamicProxy {
public static void consumer(Interface iface) {
iface.doSomething();
iface.somethingElse("bonobo");
}
public static void main(String[] args) {
RealObject real = new RealObject();
consumer(real);
// Insert a proxy and call again:
Interface proxy = (Interface)Proxy.newProxyInstance(
Interface.class.getClassLoader(),
new Class[]{ Interface.class },
new DynamicProxyHandler(real));
consumer(proxy);
}
} /* Output: (95% match)
doSomething
somethingElse bonobo
**** proxy: class $Proxy0, method: public abstract void Interface.doSomething(), args: null
doSomething
**** proxy: class $Proxy0, method: public abstract void Interface.somethingElse(java.lang.String), args: [Ljava.lang.Object;@42e816
bonobo
somethingElse bonobo
*///:~
相關類和接口
動態代理類(簡稱代理類): is a class that implements a list of interfaces specified at runtime when the class is created(不太好翻譯)
代理接口:是代理類實現的一個接口
動態代理類實例:是代理類的一個實例,每一個動態代理類實例都有一個關聯的調用處理程序對象(實現接口 InvocationHandler )。
通過調用一個代理接口的動態代理類實例上的方法調用,將被指派給實例的調用處理程序的 invoke 方法
-
java.lang.reflect.Proxy:Java動態代理機制的主類。提供了一組靜態方法來為一組接口動態地生成代理類及其實例。
// Proxy的靜態方法 // 方法 1: 該方法用于獲取指定代理對象所關聯的調用處理器 static InvocationHandler getInvocationHandler(Object proxy) // 方法 2:該方法用于獲取關聯于指定類裝載器和一組接口的動態代理類的類對象 static Class getProxyClass(ClassLoader loader, Class[] interfaces) // 方法 3:該方法用于判斷指定類對象是否是一個動態代理類 static boolean isProxyClass(Class cl) // 方法 4:該方法用于為指定類裝載器、一組接口及調用處理器生成動態代理類實例 static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
-
java.lang.reflect.InvocationHandler:這是調用處理器接口,它自定義了一個 invoke 方法,用于集中處理在動態代理類實例上的方法調用,通常在該方法中實現對委托類的代理訪問。
// InvocationHandler的核心方法 // 該方法負責集中處理動態代理類上的所有方法調用。第一個參數既是代理類實例,第二個參數是被調用的方法對象 // 第三個方法是調用參數。調用處理器根據這三個參數進行預處理或分派到委托類實例上發射執行 Object invoke(Object proxy, Method method, Object[] args)
每次生成動態代理類實例時都需要指定一個實現了該接口的調用處理器對象。
-
java.lang.ClassLoader:這是類裝載器類,負責將類的字節碼裝載到 Java 虛擬機(JVM)中并為其定義類對象,然后該類才能被使用。Proxy 靜態方法生成代理類同樣需要通過類裝載器來進行裝載才能使用,它與普通類的唯一區別就是其字節碼是由 JVM 在運行時動態生成的而非預存在于任何一個 .class 文件中。
每次生成動態代理類實例時都需要指定一個類裝載器對象
代理機制
四個步驟:
- 通過實現 InvocationHandler 接口創建自己的調用處理器;
- 通過為 Proxy 類指定 ClassLoader 對象和一組 interface 來創建動態代理類;
- 通過反射機制獲得動態代理類的構造函數,其唯一參數類型是調用處理器接口類型;
- 通過構造函數創建動態代理類實例,構造時調用處理器對象作為參數被傳入。
// InvocationHandlerImpl 實現了 InvocationHandler 接口,并能實現方法調用從代理類到委托類的分派轉發
// 其內部通常包含指向委托類實例的引用,用于真正執行分派轉發過來的方法調用
InvocationHandler handler = new InvocationHandlerImpl(..);
// 通過 Proxy 為包括 Interface 接口在內的一組接口動態創建代理類的類對象
Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... });
// 通過反射從生成的類對象獲得構造函數對象
Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });
// 通過構造函數對象創建動態代理類實例
Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });
還有更為簡便的方法,Proxy 的靜態方法 newProxyInstance 已經為我們封裝了步驟 2 到步驟 4 的過程,所以簡化后的過程如下:
// InvocationHandlerImpl 實現了 InvocationHandler 接口,并能實現方法調用從代理類到委托類的分派轉發
InvocationHandler handler = new InvocationHandlerImpl(..);
// 通過 Proxy 直接創建動態代理類實例
Interface proxy = (Interface)Proxy.newProxyInstance( classLoader,
new Class[] { Interface.class },
handler );
下面了解一下,動態生成的代理類的特點:
包:如果所代理的接口都是 public 的,那么它將被定義在頂層包(即包路徑為空),如果所代理的接口中有非 public 的接口(因為接口不能被定義為 protect 或 private,所以除 public 之外就是默認的 package 訪問級別),那么它將被定義在該接口所在包(假設代理了 com.ibm.developerworks 包中的某非 public 接口 A,那么新生成的代理類所在的包就是 com.ibm.developerworks),這樣設計的目的是為了最大程度的保證動態代理類不會因為包管理的問題而無法被成功定義并訪問;
類修飾符:該代理類具有 final 和 public 修飾符,意味著它可以被所有的類訪問,但是不能被再度繼承;
類名:格式是“$ProxyN”,其中 N 是一個逐一遞增的阿拉伯數字,代表 Proxy 類第 N 次生成的動態代理類,值得注意的一點是,并不是每次調用 Proxy 的靜態方法創建動態代理類都會使得 N 值增加,原因是如果對同一組接口(包括接口排列的順序相同)試圖重復創建動態代理類,它會很聰明地返回先前已經創建好的代理類的類對象(進行了緩存),而不會再嘗試去創建一個全新的代理類,這樣可以節省不必要的代碼重復生成,提高了代理類的創建效率。
類繼承關系:該類的繼承關系如圖:
由圖可見,Proxy 類是它的父類,這個規則適用于所有由 Proxy 創建的動態代理類。而且該類還實現了其所代理的一組接口,這就是為什么它能夠被安全地類型轉換到其所代理的某接口的根本原因。
下面了解一下動態代理類實例的一些特點。
每個實例都會關聯一個調用處理器對象,可以通過 Proxy 提供的靜態方法 getInvocationHandler 去獲得代理類實例的調用處理器對象。在代理類實例上調用其代理的接口中所聲明的方法時,這些方法最終都會由調用處理器的 invoke 方法執行,此外,值得注意的是,代理類的根類 java.lang.Object 中有三個方法也同樣會被分派到調用處理器的 invoke 方法執行,它們是 hashCode,equals 和 toString,可能的原因有:一是因為這些方法為 public 且非 final 類型,能夠被代理類覆蓋;二是因為這些方法往往呈現出一個類的某種特征屬性,具有一定的區分度,所以為了保證代理類與委托類對外的一致性,這三個方法也應該被分派到委托類執行。
多代理接口中方法重復:當代理的一組接口有重復聲明的方法且該方法被調用時,代理類總是從排在最前面的接口中獲取方法對象并分派給調用處理器,而無論代理類實例是否正在以該接口(或繼承于該接口的某子接口)的形式被外部引用,因為在代理類內部無法區分其當前的被引用類型。
public interface InterfaceA {
void doSomething();
void doA();
}
public interface InterfaceB {
void doSomething();
void doB();
}
public class RealObject implements InterfaceA, InterfaceB {
public void doSomething() {}
public void doA() {}
public void doB() {}
}
public class InvocationHandlerImpl implements InvocationHandler{
private Object proxied;
public InvocationHandlerImpl(Object proxied) {
this.proxied = proxied;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("*** proxy: " + proxy.getClass() + ", method: " + method + ", args: "+args);
if (args != null)
for (Object arg : args)
System.out.println(" " + arg);
return method.invoke(proxied, args);
}
}
public class SimpleDynamicProxy {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, InterruptedException {
RealObject object = new RealObject();
InvocationHandler handler = new InvocationHandlerImpl(object);
Class clazz = Proxy.getProxyClass(
RealObject.class.getClassLoader(),
new Class[] {InterfaceB.class, InterfaceA.class});
Constructor constructor = clazz.getConstructor(
new Class[] {InvocationHandler.class});
InterfaceA proxy = (InterfaceA) constructor.newInstance(new Object[] {handler});
proxy.doSomething();
}
} /* Output:
*** proxy: class com.sun.proxy.$Proxy0, method: public abstract void com.chaycao.Test.proxy.InterfaceB.doSomething(), args: null
*/
雖然代理類實例 proxy 正以接口 InterfaceA 的形式被外部引用。但獲取的方法對象是 在 getProxyClass 方法 排前面的 InterfaceB 中的。
再談多代理:之前一直不理解為什么會有多代理的存在,一個代理類實例只能以一種接口形式被調用。后來明白,所謂的多代理是針對代理類而言的,代理類的實例可以有多種接口形式的選擇。如下:
public class SimpleDynamicProxy {
public static void consumer(InterfaceB iface) {
iface.doSomething();
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, InterruptedException {
RealObject object = new RealObject();
InvocationHandler handler = new InvocationHandlerImpl(object);
Class clazz = Proxy.getProxyClass(
RealObject.class.getClassLoader(),
new Class[] {InterfaceA.class, InterfaceB.class});
Constructor constructor = clazz.getConstructor(
new Class[] {InvocationHandler.class});
InterfaceA proxy_a = (InterfaceA) constructor.newInstance(new Object[] {handler});
InterfaceB proxy_b = (InterfaceB) constructor.newInstance(new Object[] {handler});
proxy_a.doSomething();
proxy_a.doA();
proxy_b.doSomething();
proxy_b.doB();
}
} /*Output:
*** proxy: class com.sun.proxy.$Proxy0, method: public abstract void com.chaycao.Test.proxy.InterfaceA.doSomething(), args: null
*** proxy: class com.sun.proxy.$Proxy0, method: public abstract void com.chaycao.Test.proxy.InterfaceA.doA(), args: null
*** proxy: class com.sun.proxy.$Proxy0, method: public abstract void com.chaycao.Test.proxy.InterfaceA.doSomething(), args: null
*** proxy: class com.sun.proxy.$Proxy0, method: public abstract void com.chaycao.Test.proxy.InterfaceB.doB(), args: null
*/
下面是被代理的一組接口的特點:
- 要注意不能有重復的接口,以避免動態代理類代碼生成時的編譯錯誤。
- 這些接口對于類裝載器必須可見,否則類裝載器將無法鏈接它們,將會導致類定義失敗。再次,需被代理的所有非 public 的接口必須在同一個包中,否則代理類生成也會失敗。
- 接口的數目不能超過 65535,這是 JVM 設定的限制。
參考資料
- 《Thinking in Java》動態代理
- Java動態代理機制分析及擴展
- Graphic Design Patterns-代理模式
- Java Platform SE 8