日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > asp.net >内容正文

asp.net

JAVA设计模式--代理模式(动态)(一)

發(fā)布時間:2023/12/16 asp.net 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JAVA设计模式--代理模式(动态)(一) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

目錄

一、什么是動態(tài)代理

二、Java對動態(tài)代理的支持

三、使用CGLIB實現(xiàn)動態(tài)代理

四、動態(tài)代理模式的特點

?參考文章


一、什么是動態(tài)代理

在靜態(tài)代理(Static Proxy)模式中,代理類都是真實存在的,由程序員提前創(chuàng)建好的java類,是靜態(tài)的,每一個代理類在編譯之后都會生成一個.class字節(jié)碼文件,靜態(tài)代理類所實現(xiàn)的接口和所代理的方法早在編譯期都已被固定了。

動態(tài)代理(Dynamic Proxy)則不同:動態(tài)代理使用字節(jié)碼動態(tài)生成和加載技術(shù),在系統(tǒng)運行時動態(tài)地生成和加載代理類。

與靜態(tài)代理相比,動態(tài)代理有以下優(yōu)點:首先,無需再為每一個真實主題寫一個形式上完全一樣的代理類,假如抽象主題接口中的方法很多的話,為每一個接口方法寫一個代理方法也很麻煩,同樣地,如果后期抽象主題接口發(fā)生變動,則真實主題和代理類都要修改,不利于系統(tǒng)維護;其次,動態(tài)代理可以讓系統(tǒng)根據(jù)實際需要來動態(tài)創(chuàng)建代理類,同一個代理類能夠代理多個不同的真實主題類,并且可以代理多個不同的方法。

二、Java對動態(tài)代理的支持

從JDK 1.3版本開始,Java語言提供了對動態(tài)代理的支持,在Java中實現(xiàn)動態(tài)代理機制,需要用到 java.lang.reflect 包中的 InvocationHandler 接口和 Proxy 類,我們先來看看java的API幫助文檔是怎么樣對這兩個類進行描述的:

InvocationHandler?

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. InvocationHandler 是代理實例的調(diào)用處理程序?qū)崿F(xiàn)的接口。 每個代理實例都具有一個與之關(guān)聯(lián)的調(diào)用處理程序。對代理實例調(diào)用方法時,將對方法調(diào)用進行編碼并將其指派到它的調(diào)用處理程 序的 invoke() 方法。

invoke() 方法形式如下:??

Object invoke(Object proxy,Method method,Object[] args) throws Throwable

InvocationHandler 接口只包含invoke()這唯一一個方法,該方法用于處理對代理類實例的方法調(diào)用并返回相應的結(jié)果,當一個代理實例中的業(yè)務方法被調(diào)用時將自動調(diào)用該方法。invoke()方法包含三個參數(shù),其中第一個參數(shù)proxy表示代理類的實例,第二個參數(shù)method表示需要代理的方法,第三個參數(shù)args表示代理方法的參數(shù)數(shù)組。?

Proxy?

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. Proxy 提供用于創(chuàng)建動態(tài)代理類和實例的靜態(tài)方法,它還是由這些方法創(chuàng)建的所有動態(tài)代理類的超類。

Proxy提供給我們的靜態(tài)方法有以下四個:??

//返回指定代理實例的調(diào)用處理程序。 static InvocationHandler getInvocationHandler(Object proxy) //返回代理類的 java.lang.Class 對象,并向其提供類加載器和接口數(shù)組。 static Class<?> getProxyClass(ClassLoader loader, Class<?>[] interfaces) //當且僅當指定的類通過 getProxyClass 方法或 newProxyInstance 方法動態(tài)生成為代理類時,返回 true。 static boolean isProxyClass(Class<?> cl) //返回一個指定接口的代理類實例,該接口可以將方法調(diào)用指派到指定的調(diào)用處理程序。 //方法中的 ClassLoader loader 參數(shù)用來指定動態(tài)代理類的類加載器,Class<?>[] interfaces用來指定動態(tài)代理類要實現(xiàn)的接口。 //InvocationHandler h 用來指定與即將生成的動態(tài)代理對象相關(guān)聯(lián)的調(diào)用處理程序 static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

下面我們以為數(shù)據(jù)庫增加日志記錄功能(為簡單起見,我們僅記錄下所有操作的執(zhí)行時間及操作執(zhí)行的結(jié)果)為例來看一看如何使用這兩個類實現(xiàn)動態(tài)代理:

為了使演示更清晰,在此先定義兩個簡單類,一個User類和一個Document類分別表示數(shù)據(jù)庫中的用戶記錄和文檔記錄,其代碼如下。??

public class User {// 用戶在數(shù)據(jù)庫中的IDprivate 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;} } public class Document {// 文檔在數(shù)據(jù)庫中的IDprivate 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類用來扮演數(shù)據(jù)庫的功能,在此為簡單起見,將數(shù)據(jù)庫中的用戶記錄和文檔記錄分別存儲在一個Map中,代碼如下。?

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() {// 數(shù)據(jù)初始化,為數(shù)據(jù)庫中增加幾條用戶記錄。。。userMap = new HashMap<Long, User>();userMap.put(20160708L, new User(20160708L, "燕凌嬌"));userMap.put(20160709L, new User(20160709L, "姬如雪"));userMap.put(20160710L, new User(20160710L, "百里登風"));// 數(shù)據(jù)初始化,為數(shù)據(jù)庫中增加幾條文檔記錄。。。documentMap = new HashMap<Long, Document>();documentMap.put(30160708L, new Document(30160708L, "C++常用算法手冊"));documentMap.put(30160709L, new Document(30160709L, "深入理解Android內(nèi)核設(shè)計思想"));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();} }

數(shù)據(jù)庫布置完成了,接下來開始寫動態(tài)代理相關(guān)代碼了,為了與靜態(tài)代理進行比較,在此我們來創(chuàng)建兩個接口(抽象主題角色)。?

public interface UserDao {// 登陸數(shù)據(jù)庫,為了演示方便將字符串作為執(zhí)行結(jié)果返回String login(Long id);// 退出數(shù)據(jù)庫,為了演示方便將字符串作為執(zhí)行結(jié)果返回String logout(); } public interface DocumentDao {// 新增文檔,為了演示方便將字符串作為執(zhí)行結(jié)果返回String add(Document document);// 刪除文檔,為了演示方便將字符串作為執(zhí)行結(jié)果返回String delete(Document document); }

接下來是兩個真實主題角色,ImpUserDao 類和ImpDocumentDao類,為了使示例結(jié)果清晰,此處將接口的執(zhí)行結(jié)果直接以字符串形式返回。

public class ImpUserDao implements UserDao {@Overridepublic String login(Long id) {User user = DataBase.getDataBaseInstance().getUserMap().get(id);if (null != user) {// 數(shù)據(jù)庫有此用戶的信息,則允許登陸...DataBase.getDataBaseInstance().setCurrentUser(user);return "用戶[" + user.getName() + "]登陸成功...";} else {// 數(shù)據(jù)庫沒有此用戶信息,則不讓登陸...return "登陸失敗,ID為\"" + id + "\"的用戶不存在!";}}@Overridepublic String logout() {User user = DataBase.getDataBaseInstance().getCurrentUser();if (null != user) {// 若當前有用戶登陸,則退出成功...DataBase.getDataBaseInstance().setCurrentUser(null);return "用戶[" + user.getName() + "]退出登陸成功...";} else {// 若當前無用戶登陸,則退出失敗...return "退出登陸失敗,當前無登陸用戶!";}} } public class ImpDocumentDao implements DocumentDao {@Overridepublic 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) {// 若數(shù)據(jù)庫中已經(jīng)存在該ID的文檔,則新增文檔失敗...return "添加文檔《" + document.getTitle() + "》失敗,文檔已存在!";} else {// 若該ID的文檔在數(shù)據(jù)庫不存在,則新增文檔成功...DataBase.getDataBaseInstance().getDocumentMap().put(document.getId(), document);return "添加文檔《" + document.getTitle() + "》成功...";}}}@Overridepublic 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) {// 若數(shù)據(jù)庫中該文檔不存在,則刪除文檔失敗...return "刪除文檔《" + document.getTitle() + "》失敗,文檔不存在!";} else {// 若數(shù)據(jù)庫中該文檔存在,則刪除文檔成功...DataBase.getDataBaseInstance().getDocumentMap().remove(document.getId());return "刪除文檔《" + document.getTitle() + "》成功...";}}} }

最后,我們就要定義動態(tài)代理類了,前面說過,每一個動態(tài)代理類都必須要實現(xiàn) InvocationHandler 這個接口,因此我們這個動態(tài)代理類也不例外。?

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;}// invoke()方法用于處理對代理類實例的方法調(diào)用并返回相應的結(jié)果@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {before(method);// 繼續(xù)轉(zhuǎn)發(fā)請求給內(nèi)部真實主題角色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 + ",執(zhí)行方法\"" + method.getName() + "\"");}public void after(Object object) {System.out.println("執(zhí)行結(jié)果:" + object);} }

至此,動態(tài)代理所需要的類就算創(chuàng)建完成了,接下來創(chuàng)建一個Client充當客戶端來測試一下。

import java.lang.reflect.Proxy;public class MainClass {public static void main(String[] args) {// 此處來創(chuàng)建了兩個動態(tài)代理類對象...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);// 先輸入一個不存在的用戶Id登陸試試...userProxy.login(20160718L);documentProxy.add(new Document(30160711L, "轉(zhuǎn)角遇見幸福"));userProxy.logout();// 再用一個真實用戶Id登陸試試...userProxy.login(20160708L);documentProxy.add(new Document(30160711L, "轉(zhuǎn)角遇見幸福"));documentProxy.add(new Document(30160711L, "轉(zhuǎn)角遇見幸福"));userProxy.logout();} }

運行程序打印結(jié)果如下:?

北京時間:2016年6月11日19時33分46秒,執(zhí)行方法"login" 執(zhí)行結(jié)果:登陸失敗,ID為"20160718"的用戶不存在! 北京時間:2016年6月11日19時33分46秒,執(zhí)行方法"add" 執(zhí)行結(jié)果:保存失敗,未獲取到登陸信息! 北京時間:2016年6月11日19時33分46秒,執(zhí)行方法"logout" 執(zhí)行結(jié)果:退出登陸失敗,當前無登陸用戶! **********************************北京時間:2016年6月11日19時33分46秒,執(zhí)行方法"login" 執(zhí)行結(jié)果:用戶[燕凌嬌]登陸成功... 北京時間:2016年6月11日19時33分46秒,執(zhí)行方法"add" 執(zhí)行結(jié)果:添加文檔《轉(zhuǎn)角遇見幸福》成功... 北京時間:2016年6月11日19時33分46秒,執(zhí)行方法"add" 執(zhí)行結(jié)果:添加文檔《轉(zhuǎn)角遇見幸福》失敗,文檔已存在! 北京時間:2016年6月11日19時33分46秒,執(zhí)行方法"logout" 執(zhí)行結(jié)果:用戶[燕凌嬌]退出登陸成功... **********************************

從以上程序的最終運行結(jié)果可以看出:通過使用JDK自帶的動態(tài)代理,我們同時實現(xiàn)了對ImpUserDao和ImpDocumentDao兩個真實主題類的統(tǒng)一代理和集中控制。至于該動態(tài)代理類是如何被創(chuàng)建的?將在下一篇文章詳細討論,接下來我們先看看如何使用CGLIB實現(xiàn)動態(tài)代理。??

三、使用CGLIB實現(xiàn)動態(tài)代理

生成動態(tài)代理類的方法很多,如上例中JDK自帶的動態(tài)代理、CGLIB、Javassist 或者 ASM 庫。JDK 的動態(tài)代理使用簡單,它內(nèi)置在 JDK 中,因此不需要引入第三方 Jar 包,但相對功能比較弱。CGLIB 和 Javassist 都是高級的字節(jié)碼生成庫,總體性能比 JDK 自帶的動態(tài)代理好,而且功能十分強大。ASM 是低級的字節(jié)碼生成工具,使用 ASM 已經(jīng)近乎于在使用 Java bytecode 編程,對開發(fā)人員要求最高,當然,也是性能最好的一種動態(tài)代理生成工具。但 ASM 的使用很繁瑣,而且性能也沒有數(shù)量級的提升,與 CGLIB 等高級字節(jié)碼生成工具相比,ASM 程序的維護性較差,如果不是在對性能有苛刻要求的場合,還是推薦 CGLIB 或者 Javassist。

接下來我們繼續(xù)用上面的例子來體驗一下如何使用CGLIB實現(xiàn)動態(tài)代理。

首先,使用CGLIB來實現(xiàn)動態(tài)代理需引入“asm.jar”(CGLIB的底層是使用ASM實現(xiàn)的)和“cglib.jar”兩個第三方jar包,引入兩個jar包時需注意其版本,若版本有沖突會出現(xiàn)以下異常:Exception in thread "main" java.lang.NoSuchMethodError: org.objectweb.asm.ClassWriter.<init>(I)V

接下來開始寫代碼,延用上例中的:User類、Document類、DataBase類、兩個抽象主題接口UserDao和DocumentDao、兩個真實主題角色類ImpUserDao和ImpDocumentDao,這幾個類無需做任何修改,此處不再重復貼出。

去掉上例中的 DataBaseLogHandler 類,新增一個 CglibProxy 類,該類需實現(xiàn)CGLIB的 MethodInterceptor(方法攔截) 接口。?

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;/*** 創(chuàng)建動態(tài)代理類對象** @param clazz* 需要創(chuàng)建子類代理的父類的類型* * @return*/public Object getProxyInstance(Class<?> clazz) {Enhancer enhancer = new Enhancer();// 設(shè)置要創(chuàng)建的動態(tài)代理類的父類enhancer.setSuperclass(clazz);// 設(shè)置回調(diào)的對象,此句會導致調(diào)用動態(tài)代理類對象的方法會被指派到CglibProxy對象的intercept()方法enhancer.setCallback(this);// 通過字節(jié)碼技術(shù)動態(tài)創(chuàng)建動態(tài)代理類實例return enhancer.create();}@Override// 回調(diào)方法public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {before(method);// 通過動態(tài)子類代理實例調(diào)用父類的方法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 + ",執(zhí)行方法\"" + method.getName() + "\"");}public void after(Object object) {System.out.println("執(zhí)行結(jié)果:" + object);} }

對客戶端進行相應修改,如下。?

public class MainClass {public static void main(String[] args) {// 創(chuàng)建一個CglibProxy代理類對象,用來創(chuàng)建子類代理實例CglibProxy cglib = new CglibProxy();// 為ImpUserDao類添加一個動態(tài)代理類對象,即子類代理對象UserDao userProxy = (UserDao) cglib.getProxyInstance(ImpUserDao.class);// 為ImpDocumentDao類添加一個動態(tài)代理類對象,即子類代理對象DocumentDao documentProxy = (DocumentDao) cglib.getProxyInstance(ImpDocumentDao.class);// 先輸入一個不存在的用戶Id登陸試試...userProxy.login(20160718L);documentProxy.add(new Document(30160711L, "轉(zhuǎn)角遇見幸福"));userProxy.logout();// 再用一個真實用戶Id登陸試試...userProxy.login(20160708L);documentProxy.add(new Document(30160711L, "轉(zhuǎn)角遇見幸福"));documentProxy.add(new Document(30160711L, "轉(zhuǎn)角遇見幸福"));userProxy.logout();} }

運行程序結(jié)果打印如下,與之前使用JDK自帶動態(tài)代理程序運行結(jié)果完全相同:?

北京時間:2016年6月12日20時22分35秒,執(zhí)行方法"login" 執(zhí)行結(jié)果:登陸失敗,ID為"20160718"的用戶不存在! 北京時間:2016年6月12日20時22分35秒,執(zhí)行方法"add" 執(zhí)行結(jié)果:保存失敗,未獲取到登陸信息! 北京時間:2016年6月12日20時22分35秒,執(zhí)行方法"logout" 執(zhí)行結(jié)果:退出登陸失敗,當前無登陸用戶! ********************************** 北京時間:2016年6月12日20時22分35秒,執(zhí)行方法"login" 執(zhí)行結(jié)果:用戶[燕凌嬌]登陸成功... 北京時間:2016年6月12日20時22分35秒,執(zhí)行方法"add" 執(zhí)行結(jié)果:添加文檔《轉(zhuǎn)角遇見幸福》成功... 北京時間:2016年6月12日20時22分35秒,執(zhí)行方法"add" 執(zhí)行結(jié)果:添加文檔《轉(zhuǎn)角遇見幸福》失敗,文檔已存在! 北京時間:2016年6月12日20時22分35秒,執(zhí)行方法"logout" 執(zhí)行結(jié)果:用戶[燕凌嬌]退出登陸成功... **********************************

PS:CGLib創(chuàng)建的動態(tài)代理對象性能比JDK創(chuàng)建的動態(tài)代理對象的性能高不少,但是CGLib在創(chuàng)建代理對象時所花費的時間卻比JDK多得多,所以對于單例的對象,因為無需頻繁創(chuàng)建對象,用CGLib合適,反之,使用JDK方式要更為合適一些。同時,由于CGLib采用動態(tài)創(chuàng)建子類的方法來對被代理的父類的功能進行增強和代理,所以,無法對被聲明為final的類或方法進行代理。??

四、動態(tài)代理模式的特點

動態(tài)代理類使用字節(jié)碼動態(tài)生成加載技術(shù),在運行時生成并加載代理類。與靜態(tài)代理相比,動態(tài)代理具有以下優(yōu)點:?

-無需單獨為每一個接口添加一個代理類,使用動態(tài)代理可以一次性為多個接口實現(xiàn)代理。
-無需逐個為接口中的所有方法添加實現(xiàn),使用動態(tài)代理可以一次性為多個接口中的所有方法實現(xiàn)代理,在接口方法數(shù)量比較多的時候,可以避免出現(xiàn)大量的重復代碼。

動態(tài)代理的缺點:
? ? ? ? ? ?目前,JDK中提供的動態(tài)代理只能對接口實現(xiàn)代理,無法代理未實現(xiàn)接口的類。如果需要動態(tài)代理未實現(xiàn)接口的類,必須借助第三方工具,如:CGLib(Code Generation Library)等。

參考文章

《java的動態(tài)代理機制詳解》

《代理模式原理及實例講解》

《代理模式詳解包含原理詳解》

《Java動態(tài)代理的實現(xiàn)》

《JAVA學習篇--靜態(tài)代理VS動態(tài)代理 》

《深入理解Java Proxy機制》

總結(jié)

以上是生活随笔為你收集整理的JAVA设计模式--代理模式(动态)(一)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。