友情提示:本文略有難度,讀者需具有代理模式相關基礎知識,
。java
一般狀況下,代理模式中的每個代理類在編譯以後都會生成一個class文件,代理類所實現的接口和所代理的方法都被固定,這種代理被稱之爲靜態代理(Static Proxy)。那麼有沒有一種機制可以讓系統在運行時動態建立代理類?答案就是本文將要介紹的動態代理(Dynamic Proxy)。動態代理是一種較爲高級的代理模式,它在事務管理、AOP(Aspect-OrientedProgramming,面向方面編程)等領域都發揮了重要的做用。web
在傳統的代理模式中,客戶端經過Proxy類調用RealSubject類的request()方法,同時還能夠在代理類中封裝其餘方法(如preRequest()和postRequest()等)。若是按照這種方法使用代理模式,那麼代理類和真實主題類都應該是事先已經存在的,代理類的接口和所代理方法都已明確指定,若是須要爲不一樣的真實主題類提供代理類或者代理一個真實主題類中的不一樣方法,都須要增長新的代理類,這將致使系統中的類個數急劇增長,所以須要想辦法減小系統中類的個數。動態代理可讓系統可以根據實際須要來動態建立代理類,讓同一個代理類可以代理多個不一樣的真實主題類並且能夠代理不一樣的方法。編程
從JDK 1.3開始,Java語言提供了對動態代理的支持,Java語言實現動態代理時須要用到位於java.lang.reflect包中的一些類,現簡要說明以下:數組
(1) Proxy類框架
Proxy類提供了用於建立動態代理類和實例對象的方法,它是所建立的動態代理類的父類,它最經常使用的方法以下:函數
- public static Class<?> getProxyClass(ClassLoader loader,Class<?>... interfaces):該方法用於返回一個Class類型的代理類,在參數中須要提供類加載器並須要指定代理的接口數組(與真實主題類的接口列表一致)。
- public static Object newProxyInstance(ClassLoader loader, Class<?>[]interfaces, InvocationHandler h):該方法用於返回一個動態建立的代理類的實例,方法中第一個參數loader表示代理類的類加載器,第二個參數interfaces表示代理類所實現的接口列表(與真實主題類的接口列表一致),第三個參數h表示所指派的調用處理程序類。
(2) InvocationHandler接口工具
InvocationHandler接口是代理處理程序類的實現接口,該接口做爲代理實例的調用處理者的公共父類,每個代理類的實例均可以提供一個相關的具體調用處理者(InvocationHandler接口的子類)。在該接口中聲明瞭以下方法:post
- public Object invoke(Objectproxy, Method method, Object[] args):該方法用於處理對代理類實例的方法調用並返回相應的結果,當一個代理實例中的業務方法被調用時將自動調用該方法。invoke()方法包含三個參數,其中第一個參數proxy表示代理類的實例,第二個參數method表示須要代理的方法,第三個參數args表示代理方法的參數數組。
動態代理類須要在運行時指定所代理真實主題類的接口,客戶端在調用動態代理對象的方法時,調用請求會將請求自動轉發給InvocationHandler對象的invoke()方法,由invoke()方法來實現對請求的統一處理。性能
下面經過一個簡單實例來學習如何使用動態代理模式:學習
Sunny軟件公司欲爲公司OA系統數據訪問層DAO增長方法調用日誌,記錄每個方法被調用的時間和調用結果,現使用動態代理進行設計和實現。 |
本實例完整代碼以下所示:
- import java.lang.reflect.Proxy;
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.InvocationTargetException;
- import java.lang.reflect.Method;
- import java.util.Calendar;
- import java.util.GregorianCalendar;
-
- interface AbstractUserDAO {
- public Boolean findUserById(String userId);
- }
-
- interface AbstractDocumentDAO {
- public Boolean deleteDocumentById(String documentId);
- }
-
- class UserDAO implements AbstractUserDAO {
- public Boolean findUserById(String userId) {
- if (userId.equalsIgnoreCase("張無忌")) {
- System.out.println("查詢ID爲" + userId + "的用戶信息成功!");
- return true;
- }
- else {
- System.out.println("查詢ID爲" + userId + "的用戶信息失敗!");
- return false;
- }
- }
- }
-
- class DocumentDAO implements AbstractDocumentDAO {
- public Boolean deleteDocumentById(String documentId) {
- if (documentId.equalsIgnoreCase("D001")) {
- System.out.println("刪除ID爲" + documentId + "的文檔信息成功!");
- return true;
- }
- else {
- System.out.println("刪除ID爲" + documentId + "的文檔信息失敗!");
- return false;
- }
- }
- }
-
- class DAOLogHandler implements InvocationHandler {
- private Calendar calendar;
- private Object object;
-
- public DAOLogHandler() {
- }
-
-
- public DAOLogHandler(Object object) {
- this.object = object;
- }
-
-
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- beforeInvoke();
- Object result = method.invoke(object, args);
- afterInvoke();
- return null;
- }
-
-
- public void beforeInvoke(){
- calendar = new GregorianCalendar();
- int hour = calendar.get(Calendar.HOUR_OF_DAY);
- int minute = calendar.get(Calendar.MINUTE);
- int second = calendar.get(Calendar.SECOND);
- String time = hour + ":" + minute + ":" + second;
- System.out.println("調用時間:" + time);
- }
-
- public void afterInvoke(){
- System.out.println("方法調用結束!" );
- }
- }
編寫以下客戶端測試代碼:
- class Client {
- public static void main(String args[]) {
- InvocationHandler handler = null;
-
- AbstractUserDAO userDAO = new UserDAO();
- handler = new DAOLogHandler(userDAO);
- AbstractUserDAO proxy = null;
-
- proxy = (AbstractUserDAO)Proxy.newProxyInstance(AbstractUserDAO. class.getClassLoader(), new Class[]{AbstractUserDAO.class}, handler);
- proxy.findUserById("張無忌");
-
- System.out.println("------------------------------");
-
- AbstractDocumentDAO docDAO = new DocumentDAO();
- handler = new DAOLogHandler(docDAO);
- AbstractDocumentDAO proxy_new = null;
- proxy_new = (AbstractDocumentDAO)Proxy.newProxyInstance(Abstract DocumentDAO.class.getClassLoader(), new Class[]{AbstractDocumentDAO.class}, handler);
- proxy_new.deleteDocumentById("D002");
- }
- }
編譯並運行程序,輸出結果以下:
調用時間:13:47:14 查詢ID爲張無忌的用戶信息成功! 方法調用結束! ------------------------------ 調用時間:13:47:14 刪除ID爲D002的文檔信息失敗! 方法調用結束! |
經過使用動態代理,咱們能夠實現對多個真實主題類的統一代理和集中控制。
注:JDK中提供的動態代理只能代理一個或多個接口,若是須要動態代理具體類或抽象類,可使用CGLib(Code Generation Library)等工具,CGLib是一個功能較爲強大、性能和質量也較好的代碼生成包,在許多AOP框架中都得以普遍應用,你們能夠自行查閱相關資料來學習CGLib。