生活随笔
收集整理的這篇文章主要介紹了
JAVA设计模式-11-代理模式(动态)(一)
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
java的動態代理機制詳解 代理模式原理及實例講解 代理模式詳解包含原理詳解 Java動態代理的實現 JAVA學習篇--靜態代理VS動態代理 深入理解Java Proxy機制
一、什么是動態代理
在靜態代理(Static Proxy)模式中,代理類都是真實存在的,由程序員提前創建好的java類,是靜態的,每一個代理類在編譯之后都會生成一個.class字節碼文件,靜態代理類所實現的接口和所代理的方法早在編譯期都已被固定了。
動態代理(Dynamic Proxy)則不同:動態代理使用字節碼動態生成和加載技術,在系統運行時動態地生成和加載代理類。
與靜態代理相比,動態代理有以下優點:首先,無需再為每一個真實主題寫一個形式上完全一樣的代理類,假如抽象主題接口中的方法很多的話,為每一個接口方法寫一個代理方法也很麻煩,同樣地,如果后期抽象主題接口發生變動,則真實主題和代理類都要修改,不利于系統維護;其次,動態代理可以讓系統根據實際需要來動態創建代理類,同一個代理類能夠代理多個不同的真實主題類,并且可以代理多個不同的方法。
二、Java對動態代理的支持
從JDK 1.3版本開始,Java語言提供了對動態代理的支持,在Java中實現動態代理機制,需要用到 java.lang.reflect 包中的 InvocationHandler 接口和 Proxy 類,我們先來看看java的API幫助文檔是怎么樣對這兩個類進行描述的:
InvocationHandler
[html]?view plaincopy
InvocationHandler?is?the?interface?implemented?by?the?invocation?handler?of?a?proxy?instance.??? Each?proxy?instance?has?an?associated?invocation?handler.?When?a?method?is?invoked?on?a?proxy?instance,?the??? method?invocation?is?encoded?and?dispatched?to?the?invoke?method?of?its?invocation?handler.?? [html]?view plaincopy
InvocationHandler?是代理實例的調用處理程序實現的接口。?? 每個代理實例都具有一個與之關聯的調用處理程序。對代理實例調用方法時,將對方法調用進行編碼并將其指派到它的調用處理程?? 序的?invoke()?方法。?? invoke() 方法形式如下: [java]?view plaincopy
Object?invoke(Object?proxy,?? Method?method,?? Object[]?args)?? throws?Throwable?? InvocationHandler 接口只包含invoke()這唯一一個方法,該方法用于處理對代理類實例的方法調用并返回相應的結果,當一個代理實例中的業務方法被調用時將自動調用該方法。invoke()方法包含三個參數,其中第一個參數proxy表示代理類的實例,第二個參數method表示需要代理的方法,第三個參數args表示代理方法的參數數組。
Proxy?
[html]?view plaincopy
Proxy?provides?static?methods?for?creating?dynamic?proxy?classes?and?instances,?and?it?is?also?the?superclass??? of?all?dynamic?proxy?classes?created?by?those?methods.??? [html]?view plaincopy
Proxy?提供用于創建動態代理類和實例的靜態方法,它還是由這些方法創建的所有動態代理類的超類。?? Proxy提供給我們的靜態方法有以下四個: [html]?view plaincopy
//返回指定代理實例的調用處理程序。?? static?InvocationHandler?getInvocationHandler(Object?proxy)??? //返回代理類的?java.lang.Class?對象,并向其提供類加載器和接口數組。?? static?Class<?><span?style="white-space:pre">???</span>getProxyClass(ClassLoader?loader,?Class<?>[]?interfaces)??? //當且僅當指定的類通過?getProxyClass?方法或?newProxyInstance?方法動態生成為代理類時,返回?true。?? static?boolean<span?style="white-space:pre">??</span>isProxyClass(Class<?>?cl)??? ?? //返回一個指定接口的代理類實例,該接口可以將方法調用指派到指定的調用處理程序。?? //方法中的?ClassLoader?loader?參數用來指定動態代理類的類加載器,Class<?>[]?interfaces用來指定動態代理類要實現的接口。?? //InvocationHandler?h?用來指定與即將生成的動態代理對象相關聯的調用處理程序?? static?Object<span?style="white-space:pre">???</span>newProxyInstance(ClassLoader?loader,?Class<?>[]?interfaces,?InvocationHandler?h)??? 下面我們以為數據庫增加日志記錄功能(為簡單起見,我們僅記錄下所有操作的執行時間及操作執行的結果)為例來看一看如何使用這兩個類實現動態代理:
為了使演示更清晰,在此先定義兩個簡單類,一個User類和一個Document類分別表示數據庫中的用戶記錄和文檔記錄,其代碼如下。
[java]?view plaincopy
public?class?User?{?? ??? ?private?Long?id;?? ??? ?private?String?name;?? ?public?User(Long?id,?String?name)?{?? ??super();?? ??this.id?=?id;?? ??this.name?=?name;?? ?}?? ?public?Long?getId()?{?? ??return?id;?? ?}?? ?public?void?setId(Long?id)?{?? ??this.id?=?id;?? ?}?? ?public?String?getName()?{?? ??return?name;?? ?}?? ?public?void?setName(String?name)?{?? ??this.name?=?name;?? ?}?? }?? [java]?view plaincopy
public?class?Document?{?? ??? ?private?Long?id;?? ??? ?private?String?title;?? ?public?Document(Long?id,?String?title)?{?? ??super();?? ??this.id?=?id;?? ??this.title?=?title;?? ?}?? ?public?Long?getId()?{?? ??return?id;?? ?}?? ?public?void?setId(Long?id)?{?? ??this.id?=?id;?? ?}?? ?public?String?getTitle()?{?? ??return?title;?? ?}?? ?public?void?setTitle(String?title)?{?? ??this.title?=?title;?? ?}?? }?? 另外還需定義一個DataBase類用來扮演數據庫的功能,在此為簡單起見,將數據庫中的用戶記錄和文檔記錄分別存儲在一個Map中,代碼如下。
[java]?view plaincopy
import?java.util.HashMap;?? import?java.util.Map;?? public?class?DataBase?{?? ?private?static?Map<Long,?User>?userMap?=?null;?? ?private?static?Map<Long,?Document>?documentMap?=?null;?? ??? ?private?static?User?currentUser?=?null;?? ?private?DataBase()?{?? ???? ??userMap?=?new?HashMap<Long,?User>();?? ??userMap.put(20160708L,?new?User(20160708L,?"燕凌嬌"));?? ??userMap.put(20160709L,?new?User(20160709L,?"姬如雪"));?? ??userMap.put(20160710L,?new?User(20160710L,?"百里登風"));?? ???? ??documentMap?=?new?HashMap<Long,?Document>();?? ??documentMap.put(30160708L,?new?Document(30160708L,?"C++常用算法手冊"));?? ??documentMap?? ????.put(30160709L,?new?Document(30160709L,?"深入理解Android內核設計思想"));?? ??documentMap.put(30160710L,?new?Document(30160710L,?"Java從入門到放棄"));?? ?}?? ?public?User?getCurrentUser()?{?? ??return?currentUser;?? ?}?? ?public?void?setCurrentUser(User?currentUser)?{?? ??DataBase.currentUser?=?currentUser;?? ?}?? ?public?Map<Long,?User>?getUserMap()?{?? ??return?userMap;?? ?}?? ?public?Map<Long,?Document>?getDocumentMap()?{?? ??return?documentMap;?? ?}?? ?public?static?DataBase?getDataBaseInstance()?{?? ??return?DataBaseHolder.dataBase;?? ?}?? ?public?static?class?DataBaseHolder?{?? ??private?static?DataBase?dataBase?=?new?DataBase();?? ?}?? }?? 數據庫布置完成了,接下來開始寫動態代理相關代碼了,為了與靜態代理進行比較,在此我們來創建兩個接口(抽象主題角色)。
[java]?view plaincopy
public?interface?UserDao?{?? ??? ?String?login(Long?id);?? ??? ?String?logout();?? }?? [java]?view plaincopy
public?interface?DocumentDao?{?? ??? ?String?add(Document?document);?? ??? ?String?delete(Document?document);?? }?? 接下來是兩個真實主題角色,ImpUserDao 類和ImpDocumentDao類,為了使示例結果清晰,此處將接口的執行結果直接以字符串形式返回。
[java]?view plaincopy
public?class?ImpUserDao?implements?UserDao?{?? ?@Override?? ?public?String?login(Long?id)?{?? ??User?user?=?DataBase.getDataBaseInstance().getUserMap().get(id);?? ??if?(null?!=?user)?{?? ????? ???DataBase.getDataBaseInstance().setCurrentUser(user);?? ???return?"用戶["?+?user.getName()?+?"]登陸成功...";?? ??}?else?{?? ????? ???return?"登陸失敗,ID為\""?+?id?+?"\"的用戶不存在!";?? ??}?? ?}?? ?@Override?? ?public?String?logout()?{?? ??User?user?=?DataBase.getDataBaseInstance().getCurrentUser();?? ??if?(null?!=?user)?{?? ????? ???DataBase.getDataBaseInstance().setCurrentUser(null);?? ???return?"用戶["?+?user.getName()?+?"]退出登陸成功...";?? ??}?else?{?? ????? ???return?"退出登陸失敗,當前無登陸用戶!";?? ??}?? ?}?? }?? [java]?view plaincopy
public?class?ImpDocumentDao?implements?DocumentDao?{?? ?@Override?? ?public?String?add(Document?document)?{?? ??User?user?=?DataBase.getDataBaseInstance().getCurrentUser();?? ??if?(null?==?user)?{?? ????? ???return?"保存失敗,未獲取到登陸信息!";?? ??}?else?{?? ???Document?dbDocument?=?DataBase.getDataBaseInstance()?? ?????.getDocumentMap().get(document.getId());?? ???if?(null?!=?dbDocument)?{?? ?????? ????return?"添加文檔《"?+?document.getTitle()?+?"》失敗,文檔已存在!";?? ???}?else?{?? ?????? ????DataBase.getDataBaseInstance().getDocumentMap()?? ??????.put(document.getId(),?document);?? ????return?"添加文檔《"?+?document.getTitle()?+?"》成功...";?? ???}?? ??}?? ?}?? ?@Override?? ?public?String?delete(Document?document)?{?? ??User?user?=?DataBase.getDataBaseInstance().getCurrentUser();?? ??if?(null?==?user)?{?? ????? ???return?"保存失敗,未獲取到登陸信息!";?? ??}?else?{?? ???Document?dbDocument?=?DataBase.getDataBaseInstance()?? ?????.getDocumentMap().get(document.getId());?? ???if?(null?==?dbDocument)?{?? ?????? ????return?"刪除文檔《"?+?document.getTitle()?+?"》失敗,文檔不存在!";?? ???}?else?{?? ?????? ????DataBase.getDataBaseInstance().getDocumentMap()?? ??????.remove(document.getId());?? ????return?"刪除文檔《"?+?document.getTitle()?+?"》成功...";?? ???}?? ??}?? ?}?? }?? 最后,我們就要定義動態代理類了,前面說過,每一個動態代理類都必須要實現 InvocationHandler 這個接口,因此我們這個動態代理類也不例外。
[java]?view plaincopy
import?java.lang.reflect.InvocationHandler;?? import?java.lang.reflect.Method;?? import?java.util.Calendar;?? import?java.util.GregorianCalendar;?? public?class?DataBaseLogHandler?implements?InvocationHandler?{?? ?private?Object?object;?? ?private?Calendar?calendar;?? ?public?DataBaseLogHandler(Object?object)?{?? ??super();?? ??this.object?=?object;?? ?}?? ??? ?@Override?? ?public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?? ???throws?Throwable?{?? ??before(method);?? ?????? ??Object?result?=?method.invoke(object,?args);??? ??after(result);?? ??if?(method.getName().equalsIgnoreCase("logout"))?{?? ???System.out.println("**********************************");?? ???System.out.println("");?? ??}?? ??return?result;?? ?}?? ?public?void?before(Method?method)?{?? ??calendar?=?new?GregorianCalendar();?? ??int?year?=?calendar.get(Calendar.YEAR);?? ??int?month?=?calendar.get(Calendar.MONTH);?? ??int?date?=?calendar.get(Calendar.DATE);?? ??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("北京時間:"?+?year?+?"年"?+?month?+?"月"?+?date?+?"日"?? ????+?time?+?",執行方法\""?+?method.getName()?+?"\"");?? ?}?? ?public?void?after(Object?object)?{?? ??System.out.println("執行結果:"?+?object);?? ?}?? }?? 至此,動態代理所需要的類就算創建完成了,接下來創建一個Client充當客戶端來測試一下。
[java]?view plaincopy
import?java.lang.reflect.Proxy;?? public?class?Client{?? ?public?static?void?main(String[]?args)?{?? ????? ??UserDao?userDao?=?new?ImpUserDao();?? ??DataBaseLogHandler?userHandler?=?new?DataBaseLogHandler(userDao);?? ??DocumentDao?doucumentDao?=?new?ImpDocumentDao();?? ??DataBaseLogHandler?documentHandler?=?new?DataBaseLogHandler(?? ????doucumentDao);?? ??UserDao?userProxy?=?(UserDao)?Proxy.newProxyInstance(?? ????UserDao.class.getClassLoader(),?new?Class[]?{?UserDao.class?},?? ????userHandler);?? ??DocumentDao?documentProxy?=?(DocumentDao)?Proxy.newProxyInstance(?? ????DocumentDao.class.getClassLoader(),?? ????new?Class[]?{?DocumentDao.class?},?documentHandler);?? ???? ??userProxy.login(20160718L);?? ??documentProxy.add(new?Document(30160711L,?"轉角遇見幸福"));?? ??userProxy.logout();?? ???? ??userProxy.login(20160708L);?? ??documentProxy.add(new?Document(30160711L,?"轉角遇見幸福"));?? ??documentProxy.add(new?Document(30160711L,?"轉角遇見幸福"));?? ??userProxy.logout();?? ?}?? }?? 運行程序打印結果如下:
[html]?view plaincopy
北京時間:2016年6月11日19時33分46秒,執行方法"login"?? 執行結果:登陸失敗,ID為"20160718"的用戶不存在!?? 北京時間:2016年6月11日19時33分46秒,執行方法"add"?? 執行結果:保存失敗,未獲取到登陸信息!?? 北京時間:2016年6月11日19時33分46秒,執行方法"logout"?? 執行結果:退出登陸失敗,當前無登陸用戶!?? **********************************?? ?? 北京時間:2016年6月11日19時33分46秒,執行方法"login"?? 執行結果:用戶[燕凌嬌]登陸成功...?? 北京時間:2016年6月11日19時33分46秒,執行方法"add"?? 執行結果:添加文檔《轉角遇見幸福》成功...?? 北京時間:2016年6月11日19時33分46秒,執行方法"add"?? 執行結果:添加文檔《轉角遇見幸福》失敗,文檔已存在!?? 北京時間:2016年6月11日19時33分46秒,執行方法"logout"?? 執行結果:用戶[燕凌嬌]退出登陸成功...?? **********************************?? 從以上程序的最終運行結果可以看出:通過使用JDK自帶的動態代理,我們同時實現了對ImpUserDao和ImpDocumentDao兩個真實主題類的統一代理和集中控制。至于該動態代理類是如何被創建的?將在下一篇文章詳細討論,接下來我們先看看如何使用CGLIB實現動態代理。
三、使用CGLIB實現動態代理
生成動態代理類的方法很多,如上例中JDK自帶的動態代理、CGLIB、Javassist 或者 ASM 庫。JDK 的動態代理使用簡單,它內置在 JDK 中,因此不需要引入第三方 Jar 包,但相對功能比較弱。CGLIB 和 Javassist 都是高級的字節碼生成庫,總體性能比 JDK 自帶的動態代理好,而且功能十分強大。ASM 是低級的字節碼生成工具,使用 ASM 已經近乎于在使用 Java bytecode 編程,對開發人員要求最高,當然,也是性能最好的一種動態代理生成工具。但 ASM 的使用很繁瑣,而且性能也沒有數量級的提升,與 CGLIB 等高級字節碼生成工具相比,ASM 程序的維護性較差,如果不是在對性能有苛刻要求的場合,還是推薦 CGLIB 或者 Javassist。
接下來我們繼續用上面的例子來體驗一下如何使用CGLIB實現動態代理。
首先,使用CGLIB來實現動態代理需引入“asm.jar”(CGLIB的底層是使用ASM實現的)和“cglib.jar”兩個第三方jar包,引入兩個jar包時需注意其版本,若版本有沖突會出現以下異常:Exception in thread "main" java.lang.NoSuchMethodError: org.objectweb.asm.ClassWriter.<init>(I)V
接下來開始寫代碼,延用上例中的:User類、Document類、DataBase類、兩個抽象主題接口UserDao和DocumentDao、兩個真實主題角色類ImpUserDao和ImpDocumentDao,這幾個類無需做任何修改,此處不再重復貼出。
去掉上例中的 DataBaseLogHandler 類,新增一個 CglibProxy 類,該類需實現CGLIB的 MethodInterceptor(方法攔截) 接口。
[java]?view plaincopy
import?java.lang.reflect.Method;?? import?java.util.Calendar;?? import?java.util.GregorianCalendar;?? import?net.sf.cglib.proxy.Enhancer;?? import?net.sf.cglib.proxy.MethodInterceptor;?? import?net.sf.cglib.proxy.MethodProxy;?? ?? public?class?CglibProxy?implements?MethodInterceptor?{?? ?private?Calendar?calendar;?? ?? ? ? ? ? ? ?? ?public?Object?getProxyInstance(Class<?>?clazz)?{?? ??Enhancer?enhancer?=?new?Enhancer();?? ???? ??enhancer.setSuperclass(clazz);?? ???? ??enhancer.setCallback(this);?? ???? ??return?enhancer.create();?? ?}?? ?@Override?? ??? ?public?Object?intercept(Object?obj,?Method?method,?Object[]?args,?? ???MethodProxy?proxy)?throws?Throwable?{?? ??before(method);?? ???? ??Object?result?=?proxy.invokeSuper(obj,?args);?? ??after(result);?? ??if?(method.getName().equalsIgnoreCase("logout"))?{?? ???System.out.println("**********************************");?? ???System.out.println("");?? ??}?? ??return?result;?? ?}?? ?public?void?before(Method?method)?{?? ??calendar?=?new?GregorianCalendar();?? ??int?year?=?calendar.get(Calendar.YEAR);?? ??int?month?=?calendar.get(Calendar.MONTH);?? ??int?date?=?calendar.get(Calendar.DATE);?? ??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("北京時間:"?+?year?+?"年"?+?month?+?"月"?+?date?+?"日"?? ????+?time?+?",執行方法\""?+?method.getName()?+?"\"");?? ?}?? ?public?void?after(Object?object)?{?? ??System.out.println("執行結果:"?+?object);?? ?}?? }?? 對客戶端進行相應修改,如下。
[java]?view plaincopy
public?class?Client{?? ?public?static?void?main(String[]?args)?{?? ???? ??CglibProxy?cglib?=?new?CglibProxy();?? ???? ??UserDao?userProxy?=?(UserDao)?cglib.getProxyInstance(ImpUserDao.class);?? ???? ??DocumentDao?documentProxy?=?(DocumentDao)?cglib?? ????.getProxyInstance(ImpDocumentDao.class);?? ???? ??userProxy.login(20160718L);?? ??documentProxy.add(new?Document(30160711L,?"轉角遇見幸福"));?? ??userProxy.logout();?? ???? ??userProxy.login(20160708L);?? ??documentProxy.add(new?Document(30160711L,?"轉角遇見幸福"));?? ??documentProxy.add(new?Document(30160711L,?"轉角遇見幸福"));?? ??userProxy.logout();?? ?}?? }?? 運行程序結果打印如下,與之前使用JDK自帶動態代理程序運行結果完全相同:
[html]?view plaincopy
北京時間:2016年6月12日20時22分35秒,執行方法"login"?? 執行結果:登陸失敗,ID為"20160718"的用戶不存在!?? 北京時間:2016年6月12日20時22分35秒,執行方法"add"?? 執行結果:保存失敗,未獲取到登陸信息!?? 北京時間:2016年6月12日20時22分35秒,執行方法"logout"?? 執行結果:退出登陸失敗,當前無登陸用戶!?? **********************************?? 北京時間:2016年6月12日20時22分35秒,執行方法"login"?? 執行結果:用戶[燕凌嬌]登陸成功...?? 北京時間:2016年6月12日20時22分35秒,執行方法"add"?? 執行結果:添加文檔《轉角遇見幸福》成功...?? 北京時間:2016年6月12日20時22分35秒,執行方法"add"?? 執行結果:添加文檔《轉角遇見幸福》失敗,文檔已存在!?? 北京時間:2016年6月12日20時22分35秒,執行方法"logout"?? 執行結果:用戶[燕凌嬌]退出登陸成功...?? **********************************?? PS:CGLib創建的動態代理對象性能比JDK創建的動態代理對象的性能高不少,但是CGLib在創建代理對象時所花費的時間卻比JDK多得多,所以對于單例的對象,因為無需頻繁創建對象,用CGLib合適,反之,使用JDK方式要更為合適一些。同時,由于CGLib采用動態創建子類的方法來對被代理的父類的功能進行增強和代理,所以,無法對被聲明為final的類或方法進行代理。
四、動態代理模式的特點
動態代理類使用字節碼動態生成加載技術,在運行時生成并加載代理類。與靜態代理相比,動態代理具有以下優點:
-無需單獨為每一個接口添加一個代理類,使用動態代理可以一次性為多個接口實現代理。
-無需逐個為接口中的所有方法添加實現,使用動態代理可以一次性為多個接口中的所有方法實現代理,在接口方法數量比較多的時候,可以避免出現大量的重復代碼。
動態代理的缺點:
目前,JDK中提供的動態代理只能對接口實現代理,無法代理未實現接口的類。如果需要動態代理未實現接口的類,必須借助第三方工具,如:CGLib(Code Generation Library)等。
總結
以上是生活随笔為你收集整理的JAVA设计模式-11-代理模式(动态)(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。