設計模式篇的第一個模式,我選整理動態代理模式的相關內容。由於動態代理模式如今常常接觸到,是java程序員成長中必需要掌握的一個模式。像開源技術Spring,其賴以成名的AOP(面向切面編程)的背後原理就是動態代理模式。AOP應用的一個典型實例就是日誌,咱們都知道,在系統運行過程當中若是系統出了問題,第一排查手段就是查看系統日誌。那麼系統是怎麼記錄日誌的?用過log4j的都知道如何顯式的去打印日誌,可還有更多的日誌是系統來幫咱們打印出來的,它到底是怎麼完成的?是在須要打印日誌的地方像咱們本身編程同樣去顯式調用日誌打印方法嗎?還有有其餘更好的辦法?答案看上去更像是後者,由於咱們更相信這些開源技術的設計者(大牛)們在兼容性、擴展性等方面比咱們考慮的更深刻。java
進入正題,討論什麼是動態代理模式。代理咱們都清楚,常常聽到「代理人」一說,其實明星的經紀人也是代理人的一種,好比某影視公司找明星拍一部電影,一般都是先找到他的經紀人進行初步協商,談好後再由明星本人出面完成實際電影拍攝,經紀人代理了拍攝前的一些事物,但拍攝過程仍是由被代理人(明星)本身來完成。下面用程序來模擬一下上面的內容:git
首先建立一個接口,裏面含有一個方法:程序員
/** * 影視拍攝 */ public interface ShootingWork { /** * 拍攝影視 */ void shooting(); }
而後建立一個電影明星,電影明星能夠拍攝電影,所以由它實現上面的接口:github
/** * 電影明星 */ public class FilmStar implements ShootingWork{ @Override public void shooting() { System.out.println("電影明星準備拍攝"); System.out.println("電影明星拍攝中"); System.out.println("電影明星拍攝完成"); } }
而後在實現一個代理人,它負責對外代理電影明星的電影拍攝事務:編程
** * 經紀人 */ public class FilmProxy implements ShootingWork { private ShootingWork star = new FilmStar(); @Override public void shooting() { /** * 處理一些事務後,調用實際執行人的方法 */ System.out.println("經紀人協商合同"); System.out.println("籤合同。。。"); this.star.shooting(); } }
最後咱們來完成整個過程的模擬,建立一個客戶端:設計模式
/** * 客戶端 */ public class ProxyClient { @Test public void filming(){ ShootingWork film = new FilmProxy(); film.shooting(); } }
控制檯輸出結果:bash
經紀人協商合同 籤合同。。。 電影明星準備拍攝 電影明星拍攝中 電影明星拍攝完成 Process finished with exit code 0
能夠看到,經過簡單的幾步,一個代理的模式就實現了,其實這是一個典型的靜態代理。咱們分析上面模式,發現一個代理只能對於一個實際委託人(明星),並且代碼中必須明確指定代理的委託人。也就是編碼中就必須肯定代理人、委託人的關係。而實際生活中的代理遠比這個模式複雜。咱們再來舉一個NBA的例子,NBA球員跟球隊協商合同時,每每也是經過經紀人(經紀公司)的方式,並且一個經紀人每每明星會簽約(代理)多個NBA球員。 這樣,當球隊找到經紀人時,事前(假想)並不知道這個球隊要簽約我名下的哪名球員,只有進一步溝通後才能肯定。咱們把這種方式稱之爲動態代理(如理解有誤,請指出)。咱們仍是以明星這個爲例編寫demo,只不過如今一個經紀人代理多個明星,影視公司找人拍攝影視時,並不知道是找哪位,具體詳談後才能肯定下來。按照這個思路咱們來看JDK中動態代理如何實現:ide
首先實現JDK的InvocationHandler接口,做爲動態代理人:this
/** * 實現調用類 */ public class ShootInvocationHandler implements InvocationHandler { private ShootingWork star; /** * 構造方法中動態傳入對象 * * @param star */ public ShootInvocationHandler(ShootingWork star) { this.star = star; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("動態代理人開始協商合同"); System.out.println("動態代理人開始籤合同"); return method.invoke(star, args); } }
建立個簡單的工廠類(工廠模式後面再補)實現實例的獲取:編碼
/** * 工廠類 */ public class StarFactory { public static ShootingWork getStarInstance(String type) { if ("film".equals(type)) { return new FilmStar(); } return null; } }
最後咱們看客戶端執行時的變化:
/** * 客戶端 */ public class ProxyClient { /** * 靜態代理 */ @Test public void filming() { ShootingWork film = new FilmProxy(); film.shooting(); } /** * 動態代理 */ @Test public void dyProxy() { ShootingWork film = StarFactory.getStarInstance("film"); ShootingWork subject = (ShootingWork) Proxy.newProxyInstance(getClass().getClassLoader(), new Class<?>[] { ShootingWork.class }, new ShootInvocationHandler(film)); subject.shooting(); } }
執行結果:
動態代理人開始協商合同 動態代理人開始籤合同 電影明星準備拍攝 電影明星拍攝中 電影明星拍攝完成 Process finished with exit code 0
這樣一個動態代理就實現了。咱們試着分析下好處在哪,當再代理一個電視明星、歌手、曲藝明星時,只須要實現ShootWork的接口,而後修改工廠裏面的方法便可,而不須要去從新建立代理類了。這是動態代理JDK自帶的實現方式。
還有一種實現動態代理的方式,那就是cglib。從上面jdk原生的動態代理實現方式能夠看出,使用時委託對象須要實現一個或多個接口才能實現動態代理的機制,這也算是這種方式的一個缺點吧。若是想被代理的對象(委託對象)不用去實現任何接口,就能夠被動態代理怎麼辦?答案就是cglib的動態代理方式。咱們來看如何使用cglib來實現一個動態代理:
首先定義一個委託對象:
/** * 一名演員 */ public class ActualActor { public String doSomething(String something) { return "ActualActor is doing " + something; } }
實現cglib提供的MethodInterceptor接口,這裏只作簡單實現便可:
/** * CGlib提供的接口實現 */ public class DoMethodIterceptor implements MethodInterceptor { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { return methodProxy.invokeSuper(o, objects); } }
最後看客戶端如何實現動態代理調用:
public class CglibClient { @Test public void testCglib() { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(ActualActor.class); enhancer.setCallback(new DoMethodIterceptor()); ActualActor actualActor = (ActualActor) enhancer.create(); System.out.println(actualActor.doSomething("拍電影")); System.out.println(actualActor.doSomething("拍電視")); } }
咱們看下執行結果:
ActualActor is doing 拍電影 ActualActor is doing 拍電視 Process finished with exit code 0
比較JDK原生的動態代理實現方式,cglib的方式彷佛更簡便一些,代碼更清晰。在實際工做中,能夠根據實際須要去選擇實現方式。
源碼獲取方式:
github地址:https://github.com/walker0819/designpattern