1.引子
代理,咱們都很熟悉,非要舉一個例子的話,好比說生活中的各類中介,好比說明星藝人的經紀人,都是代理,生活中的代理。java
延伸到程序員的世界中也有代理,咱們都很是熟悉代理設計模式。好比說提到spring框架,你必定會想起IOC,和AOP。IOC叫作控制反轉,是工廠設計模式和依賴注入的應用。AOP叫作面向切面編程,相應的應用了代理設計模式。程序員
這麼看來代理兩個字還有許多講究,那麼你有想過代理設計模式,到底解決了什麼問題嗎?或者說日常咱們租房、買房爲何非要找中介呢?spring
在代理這種設計模式中,咱們能夠嘗試問這麼一些問題編程
-
誰是代理對象?設計模式
-
誰是被代理(目標對象)?安全
-
若是沒有代理對象,目標對象會怎麼樣?框架
舉個例子,某歌星須要開一場演唱會,須要作許多準備工做。好比說搭臺、商務簽約、行程安排、最後開唱。咱們常說術業有專攻,對於一個歌手,他擅長於也僅擅長於唱歌,別的不會。jvm
那怎麼辦?請經紀人吶!像商務簽約、行程安排都交給經紀人來作,歌星只須要到點登臺唱歌便可。你看這就是代理,咱們能夠用正式一點的話語來總結描述一下ide
-
代理是一種機制,一種用於控制訪問目標對象的機制性能
-
代理,能夠實現對目標對象的保護(要找歌星簽名,先得過經紀人這一關)
-
代理,能夠實現對目標對象的加強(歌星只須要關心唱歌,別的事情交給經紀人)
到這裏,你應該可以充分理解代理,及代理設計模式了。
那到底什麼是代理設計模式呢?
-
代理設計模式,它是一種結構型設計模式
-
有代理對象,被代理對象(目標對象)
-
經過代理對象,能夠實現對目標對象的訪問控制,增長目標對象安全性
-
經過代理對象,能夠實如今不改變目標對象的的狀況下,加強目標對象能力
2.案例
在引子部分,咱們搞清楚了什麼是代理設計模式,接下來我將經過案例實現,給你演示一下代理設計模式的應用。
關於代理設計模式,你須要留意它有兩種實現方式,分別是
-
靜態代理
-
動態代理
另外在實現的過程當中,一般須要代理對象,與被代理對象實現相同的接口,即它們是同類。咱們具體看代碼案例吧,相信看到代碼你就明白了。
2.1.靜態代理
一般在項目中,咱們將功能分爲
-
業務功能(好比說用戶、訂單的增刪改查)
-
非業務功能(好比說事務控制、記錄日誌、性能耗時統計)
對於業務開發工程師來講,只須要關注業務功能的開發。而非業務功能,在每一個項目中都差很少,具有必定的通用性,徹底能夠經過代理設計模式來支持實現。
接下里我將給你模擬一個這樣的案例,咱們以保存用戶接口爲例,在保存用戶的同時進行接口耗時統計。
2.1.1.接口
/** * 用戶接口 * * @author ThinkPad * @version 1.0 * @date 2021/3/7 16:09 */ public interface UserService { /** * 保存用戶接口 * @param name */ void saveUser(String name); }
2.1.2.被代理類
/** * 用戶接口實現類 * * @author ThinkPad * @version 1.0 * @date 2021/3/7 16:10 */ public class UserServiceImpl implements UserService { /** * 保存用戶接口 * @param name */ @Override public void saveUser(String name) { System.out.println("保存用戶:" + name); } }
2.1.3.代理類
/** * 用戶接口代理,與目標對象實現相同的接口 * * @author ThinkPad * @version 1.0 * @date 2021/3/7 16:12 */ public class UserProxy implements UserService{ private UserService target; public UserProxy(UserService target){ this.target = target; } /** * 保存用戶接口(代理) * @param name */ @Override public void saveUser(String name) { // 統計耗時開始(加強的功能) System.out.println("統計保存用戶接口耗時.start:" + System.currentTimeMillis()); // 調用目標對象 target.saveUser(name); // 統計耗時結束(加強的功能) System.out.println("統計保存用戶接口耗時.end:" + System.currentTimeMillis()); } }
2.1.4.測試類
public static void main(String[] args) { // 建立目標對象 UserService target = new UserServiceImpl(); // 建立代理對象 UserService proxy = new UserProxy(target); // 經過代理對象,實現保存用戶的時候,統計耗時 proxy.saveUser("小明"); } #執行結果 統計保存用戶接口耗時.start:1615105035485 保存用戶:小明 統計保存用戶接口耗時.end:1615105035485 Process finished with exit code 0
2.1.5.總結分析
UserProxy類有兩個特色
-
實現UserService接口,與目標對象實現相同的接口
-
持有UserSerivceImpl實例,經過組合實現目標對象的能力加強
-
你須要關注UserProxy中的saveUser方法,在保存用戶target.saveUser(name)先後,進行耗時統計
最後經過執行結果,咱們看到在不修改UserSerivceImpl類代碼的狀況下,實現了保存用戶的同時,進行接口耗時統計能力的加強。你看這就是代理設計模式,代碼實現比較簡單。
不過你須要注意,在實際項目中,靜態代理使用很是少,緣由是靜態代理方式,每個目標類都須要相應定義一個代理類,致使類的數量會膨脹,另外代碼維護性比較差。
那麼針對靜態代理存在的問題,咱們該如何解決呢?答案是動態代理。
2.2.動態代理
經過靜態代理示例代碼,咱們比較直觀的看到了代理設計模式的實現。不過咱們說靜態代理存在兩個問題
-
每個目標類,都須要一個相應的代理類,類的數量膨脹問題
-
靜態代理,代碼是在編譯時實現,代碼維護性比較差
針對靜態代理的問題,jdk提供了動態代理的語法支持,接下來咱們一塊兒來看動態代理示例代碼,你須要關注兩個類
-
Proxy,建立代理對象入口類
-
InvocationHandler,具體實現代理加強的接口類
2.2.1.經過動態代理,建立代理類
/** * 動態代理示例 * 1.Proxy,建立代理對象入口類 * 2.InvocationHandler,具體實現代理加強的接口類 * * @author ThinkPad * @version 1.0 * @date 2021/3/7 16:33 */ public class DynamicUserProxy { public static void main(String[] args) { // 建立目標對象 UserService target = new UserServiceImpl(); // 建立代理對象 UserService proxy = (UserService)Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 統計耗時開始 System.out.println("統計保存用戶接口耗時.start:" + System.currentTimeMillis()); // 調用目標對象 Object result = method.invoke(target, "小明"); // 統計耗時結束 System.out.println("統計保存用戶接口耗時.end:" + System.currentTimeMillis()); return result; } } ); // 經過代理對象,實現保存用戶的時候,統計耗時 proxy.saveUser("小明"); } } #執行結果 統計保存用戶接口耗時.start:1615106267280 保存用戶:小明 統計保存用戶接口耗時.end:1615106267280 Process finished with exit code 0
2.2.2.總結分析
對比動態代理,靜態代理
-
執行結果是同樣的,都在不修改目標類UserServiceImpl的狀況下,實現了接口耗時統計能力的加強
-
代碼結構都遵循了三個步驟
-
建立目標對象
// 建立目標對象 UserService target = new UserServiceImpl();
-
建立代理對象
#靜態代理 // 建立代理對象 UserService proxy = new UserProxy(target); #動態代理 // 建立代理對象 UserService proxy = (UserService)Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 統計耗時開始 System.out.println("統計保存用戶接口耗時.start:" + System.currentTimeMillis()); // 調用目標對象 Object result = method.invoke(target, "小明"); // 統計耗時結束 System.out.println("統計保存用戶接口耗時.end:" + System.currentTimeMillis()); return result; } } );
-
經過代理對象,實現功能加強
// 經過代理對象,實現保存用戶的時候,統計耗時 proxy.saveUser("小明");
-
-
差別最大的地方,是在建立代理對象。動態代理中,咱們經過Proxy的newProxyInstance方法,建立代理對象,它有三個參數
-
參數1:類加載器,動態代理的本質是運行時實現加強,即在運行的時候jvm動態生成代理類字節碼Class,從而實例化代理對象。所以,須要類加載器
-
參數2:目標對象實現的接口列表,符合代理設計模式定義,代理類,與目標類實現相同的接口
-
參數3:InvocationHandler,用於實現加強的接口,關心接口中的invoke方法,該方法中實現代理邏輯
-
經過上面動態代理,靜態代理的對比,而且我詳細給你解釋了動態代理相關的代碼細節,相信你能夠很好的理解代理設計模式、靜態代理、動態代理了。
關於代理模式,是一個很重要,且用的比較多的設計模式。最後我想拋出一個知識點,基於jdk提供的Proxy動態代理實現,咱們叫作基於接口的動態代理,它的意思是說目標類必需要實現接口。那麼若是目標類在沒有實現任何接口的狀況下,如何實現動態代理呢?答案是cglib,拋磚引玉指望你能夠去了解一下。