Java代理模式實(shí)例詳解【靜態(tài)代理與動(dòng)態(tài)代理】
本文實(shí)例講述了Java代理模式。分享給大家供大家參考,具體如下:
即Proxy Pattern,23種java常用設(shè)計(jì)模式之一。代理模式的定義:對其他對象提供一種代理以控制對這個(gè)對象的訪問。
Java的代理模式是Java中比較常用的設(shè)計(jì)模式,分為2中代理:靜態(tài)代理與動(dòng)態(tài)代理(JDK動(dòng)態(tài)代理和cglib動(dòng)態(tài)代理)
優(yōu)點(diǎn):
- 職責(zé)清晰 真實(shí)角色只需關(guān)注業(yè)務(wù)邏輯的實(shí)現(xiàn),非業(yè)務(wù)邏輯部分,后期通過代理類完成即可。
- 高擴(kuò)展性 不管真實(shí)角色如何變化,由于接口是固定的,代理類無需做任何改動(dòng)。
缺點(diǎn):
- 很明顯的一點(diǎn)就是反射機(jī)制,沒有高安全性,性能也相對來講低一些。
代理模式這種設(shè)計(jì)模式是一種使用代理對象來執(zhí)行目標(biāo)對象的方法并在代理對象中增強(qiáng)目標(biāo)對象方法的一種設(shè)計(jì)模式。代理對象代為執(zhí)行目標(biāo)對象的方法,并在此基礎(chǔ)上進(jìn)行相應(yīng)的擴(kuò)展??雌饋硎怯悬c(diǎn)拗口,首先介紹一個(gè)原則:開閉原則(對擴(kuò)展開放,對修改關(guān)閉)。一種好的設(shè)計(jì)模式甚至是架構(gòu),都是在不修改原有形態(tài)的基礎(chǔ)上擴(kuò)展出新的功能。
事例場景:
小張是一個(gè)普普通通的碼農(nóng),每天勤勤懇懇地碼代碼。某天中午小張剛要去吃飯,一個(gè)電話打到了他的手機(jī)上。“是XX公司的小張嗎?我是YY公司的王AA”?!芭叮峭蹩偘?,有什么事情嗎?”。溝通過后,小張弄明白了,原來客戶有個(gè)需求,剛好負(fù)責(zé)這方面開發(fā)的是小張,客戶就直接找到了他。不過小張卻沒有答應(yīng)客戶的請求,而是讓客戶找產(chǎn)品經(jīng)理小李溝通。
是小張著急去吃面而甩鍋嗎?并不是,只是為了使故事可以套到代理模式上。我們先看一下代理模式的定義: * 為其他對象提供一種代理,以控制對這個(gè)對象的訪問。(Provide a surrogate or placeholder for another object to control access to it)
對照定義,碼農(nóng)小張可以映射為其他對象,產(chǎn)品經(jīng)理小李為小張的代理。我們通過JAVA代碼,表述上面事例。
一、靜態(tài)代理
什么是靜態(tài)代理:靜態(tài)代理就是在程序運(yùn)行前就已經(jīng)確定代理類與代理對象的代理模式
靜態(tài)代理模式就是如上圖所示,構(gòu)造三個(gè)類實(shí)現(xiàn)他們的關(guān)系。
首先會(huì)思考的一點(diǎn)就是為什么需要實(shí)現(xiàn)同一個(gè)接口,如果不實(shí)現(xiàn)同一個(gè)接口,一樣可以“代理”功能,所以為什么非要實(shí)現(xiàn)同一個(gè)接口。我個(gè)人認(rèn)為不實(shí)現(xiàn)統(tǒng)一接口的話應(yīng)該叫做聚合而不是代理;然后,實(shí)現(xiàn)統(tǒng)一接口能夠使代理類與被代理類之間的聯(lián)系,提高代碼的復(fù)用性又能解耦。
package staticProxy; /** *接口 */ public interface DAOInterface { public void add(); public void delete(); public void update(); public void query(); }
package staticProxy; /** *被代理類 */ public class UserDao implements DAOInterface{ @Override public void add() { System.out.println("在目標(biāo)對象中執(zhí)行add"); } @Override public void delete() { System.out.println("在目標(biāo)對象中執(zhí)行delete"); } @Override public void update() { System.out.println("在目標(biāo)對象中執(zhí)行update"); } @Override public void query() { System.out.println("在目標(biāo)對象中執(zhí)行query"); } }
package staticProxy; /** * 代理類 * */ public class UserDaoProxy implements DAOInterface{ UserDao userDao = null; public UserDaoProxy(UserDao userDao){ this.userDao = userDao; } @Override public void add() { userDao.add(); System.out.println("記錄日志add"); } @Override public void delete() { userDao.delete(); System.out.println("記錄日志delete"); } @Override public void update() { userDao.update(); System.out.println("記錄日志update"); } @Override public void query() { userDao.query(); System.out.println("記錄日志query"); } }
靜態(tài)代理就是寫死了在代理對象中執(zhí)行這個(gè)方法前后執(zhí)行添加功能的形式,每次要在接口中添加一個(gè)新方法,則需要在目標(biāo)對象中實(shí)現(xiàn)這個(gè)方法,并且在代理對象中實(shí)現(xiàn)相應(yīng)的代理方法;利用Java的反射機(jī)制,動(dòng)態(tài)的調(diào)用生成代理對象,就能完成動(dòng)態(tài)代理的需求。
二、動(dòng)態(tài)代理
1、JDK動(dòng)態(tài)代理
在代理類管理器的新建代理實(shí)例方法中,必須要獲得類的加載器、類所實(shí)現(xiàn)的接口、還有一個(gè)攔截方法的句柄。
在句柄的invoke中如果不調(diào)用method.invoke則方法不會(huì)執(zhí)行。在invoke前后添加通知,就是對原有類進(jìn)行功能擴(kuò)展了。創(chuàng)建好代理對象之后,proxy可以調(diào)用接口中定義的所有方法,因?yàn)樗鼈儗?shí)現(xiàn)了同一個(gè)接口,并且接口的方法實(shí)現(xiàn)類的加載器已經(jīng)被反射框架獲取到了。
package JDKAgency; /** * DAO接口 */ public interface DAO { void add(); void update(); void delete(); void query(); }
package JDKAgency; /** * DAO的實(shí)現(xiàn)類 */ public class DAOImpl implements DAO { @Override public void add() { System.out.println("添加的方法"); } @Override public void update() { System.out.println("更新的方法"); } @Override public void delete() { System.out.println("刪除的方法"); } @Override public void query() { System.out.println("查詢的方法"); } }
package JDKAgency; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 代理類管理器 */ public class ProxyManager implements InvocationHandler {//動(dòng)態(tài)代理的核心處理器接口 private Object object; public ProxyManager(Object object) { this.object = object; } public Object createNewInstance() { Object o = Proxy.newProxyInstance(object.getClass().getClassLoader(),//真實(shí)對象的類加載器 object.getClass().getInterfaces(),//真實(shí)對象的所有接口 this);//代理對象 return o; } @Override //代理對象 執(zhí)行業(yè)務(wù)的方法 參數(shù) public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before......權(quán)限檢測");//前置通知 Object invoke = method.invoke(object, args);//動(dòng)態(tài)調(diào)用執(zhí)行方法 System.out.println("after......日志監(jiān)控");//后置通知 return invoke; } }
package JDKAgency; /** * 測試 */ public class JTest { public static void main(String[] args) { //創(chuàng)建真實(shí)對象 DAO dao = new DAOImpl(); DAO o =(DAO) manager.createNewInstance(); //JDK動(dòng)態(tài)代理是代理的接口,因此強(qiáng)制轉(zhuǎn)換應(yīng)該轉(zhuǎn)換為接口,而不是實(shí)現(xiàn)類,若強(qiáng)制轉(zhuǎn)換實(shí)現(xiàn)類就會(huì)拋出ClassCastException,好比ArrayList與LinkedList實(shí)現(xiàn)統(tǒng)一接口List,兩者也不能相互轉(zhuǎn)換,但都可以向上轉(zhuǎn)型。 o.add(); o.query(); } }
注意:JDK動(dòng)態(tài)代理是代理的接口,因此強(qiáng)制轉(zhuǎn)換應(yīng)該轉(zhuǎn)換為接口,而不是實(shí)現(xiàn)類,若強(qiáng)制轉(zhuǎn)換實(shí)現(xiàn)類就會(huì)拋出ClassCastException,好比ArrayList與LinkedList實(shí)現(xiàn)統(tǒng)一接口List,兩者也不能相互轉(zhuǎn)換,但都可以向上轉(zhuǎn)型。
補(bǔ)充:
JavaJDK動(dòng)態(tài)代理報(bào)錯(cuò)java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to *。
javaJDK動(dòng)態(tài)代理是Java原生代理模式。
注意:JDK動(dòng)態(tài)代理是代理的接口,因此強(qiáng)制轉(zhuǎn)換應(yīng)該轉(zhuǎn)換為接口,而不是實(shí)現(xiàn)類,若強(qiáng)制轉(zhuǎn)換實(shí)現(xiàn)類就會(huì)拋出ClassCastException,好比ArrayList與LinkedList實(shí)現(xiàn)統(tǒng)一接口List,兩者也不能相互轉(zhuǎn)換,但都可以向上轉(zhuǎn)型。
正確的轉(zhuǎn)型方案:
//創(chuàng)建處理器對象 ProxyManager manager = new ProxyManager(dao); //生成動(dòng)態(tài)代理對象 // DAO o = (DAO) Proxy.newProxyInstance(dao.getClass().getClassLoader(), dao.getClass().getInterfaces(), manager);
2、cglib動(dòng)態(tài)代理
cglib動(dòng)態(tài)代理是web應(yīng)用框架常用的一種動(dòng)態(tài)代理方式。cglib是動(dòng)態(tài)生成被代理類的子類,注意:是子類。
他需要先引入asm與cglib的jar包。如下圖:
接著廢話不多說,看代碼分析:
package CGlibAgency; /** * 被代理類 */ public class User { public void saveUser(){ System.out.println("保存對象。"); } public void updateUser(){ System.out.println("修改對象。"); } }
package CGlibAgency; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** * 代理類管理器 */ public class Interceptor implements MethodInterceptor {//cgLib需要實(shí)現(xiàn)MethodInterceptor接口,cgLib是基于類的,動(dòng)態(tài)的生成代理類(被代理類的子類) private Enhancer enhancer = new Enhancer();//Enhancer是Cglib代理的重要對象 public Object createProxy(Class superClass){ enhancer.setSuperclass(superClass);//獲取父類的Class enhancer.setCallback(this);//設(shè)置方法的回調(diào),類似于JDK動(dòng)態(tài)代理中的Proxy與InvocationHandler實(shí)現(xiàn)類的綁定回調(diào) return enhancer.create();//返回代理類的對象 } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("'權(quán)限驗(yàn)證'"); Object aSuper = methodProxy.invokeSuper(o, objects);//通過虛擬的代理對象的代理方法調(diào)用父類的方法,參數(shù)中一個(gè)是父類,一個(gè)是所有的子類對象數(shù)組(??) System.out.println("'日志收集'"); return aSuper; } }
package CGlibAgency; /** * 測試類 */ public class JTest { public static void main(String[] args) { Interceptor interceptor = new Interceptor();//創(chuàng)建代理管理對象 Object proxy = interceptor.createProxy(User.class);//創(chuàng)建一個(gè)代理類 System.out.println(User.class.getTypeName());//查看代理類的類型 User user = (User) proxy;//轉(zhuǎn)型,子類自動(dòng)向上轉(zhuǎn)型 user.saveUser(); user.updateUser(); } }
總結(jié):
代理模式不僅可以降低模塊兒之間的耦合,還能做到高復(fù)用,簡化代碼等。spring的AOP模塊就是最直觀的代理模式,使用了動(dòng)態(tài)代理來實(shí)現(xiàn),在spring中的許多模塊中都具有動(dòng)態(tài)代理的影子。
更多java相關(guān)內(nèi)容感興趣的讀者可查看本站專題:《Java面向?qū)ο蟪绦蛟O(shè)計(jì)入門與進(jìn)階教程》、《Java數(shù)據(jù)結(jié)構(gòu)與算法教程》、《Java操作DOM節(jié)點(diǎn)技巧總結(jié)》、《Java文件與目錄操作技巧匯總》和《Java緩存操作技巧匯總》
希望本文所述對大家java程序設(shè)計(jì)有所幫助。
相關(guān)文章
java多線程實(shí)現(xiàn)同步鎖賣票實(shí)戰(zhàn)項(xiàng)目
本文主要介紹了java多線程實(shí)現(xiàn)同步鎖賣票實(shí)戰(zhàn)項(xiàng)目,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01Spring實(shí)現(xiàn)動(dòng)態(tài)切換多數(shù)據(jù)源的解決方案
這篇文章主要給大家介紹了Spring實(shí)現(xiàn)動(dòng)態(tài)切換多數(shù)據(jù)源的解決方案,文中給出了詳細(xì)的介紹和示例代碼,相信對大家的理解和學(xué)習(xí)具有一定的參考借鑒價(jià)值,有需要的朋友可以參考學(xué)習(xí),下面來一起看看吧。2017-01-01java返回的List進(jìn)行add操作報(bào)錯(cuò)
本文主要介紹了java返回的List進(jìn)行add操作報(bào)錯(cuò),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06詳解使用JavaMailSender給曾經(jīng)心愛的她再中秋發(fā)送一封特別的郵件
網(wǎng)站的服務(wù)端向用戶發(fā)郵件時(shí),郵件中往往需要攜帶圖片,鏈接等內(nèi)容。所以為了方便起見,我們一般發(fā)送HTML格式的郵件,那么怎么去拼一個(gè)HTML格式的郵件呢?——Thymeleaf。開始之前,先新建一個(gè)SpringBoot項(xiàng)目,并添加需要用到的依賴。然后就可以繼續(xù)往下了2022-09-09SpringBoot3整合Druid監(jiān)控功能的項(xiàng)目實(shí)踐
Druid連接池作為一款強(qiáng)大的數(shù)據(jù)庫連接池,提供了豐富的監(jiān)控和管理功能,成為很多Java項(xiàng)目的首選,本文主要介紹了SpringBoot3整合Druid監(jiān)控功能的項(xiàng)目實(shí)踐,感興趣的可以了解一下2024-01-01Java Web項(xiàng)目創(chuàng)建并實(shí)現(xiàn)前后端交互
本文主要介紹了Java Web項(xiàng)目創(chuàng)建并實(shí)現(xiàn)前后端交互,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07java如何用反射將一個(gè)對象復(fù)制給另一個(gè)對象
這篇文章主要介紹了java如何用反射將一個(gè)對象復(fù)制給另一個(gè)對象問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09