今天講一個比較深層的知識點:JDK動態代理,這是個可以讓小白在大咖面前裝B的神器,順便送你一個代理模式的溫習機會。
代理模式場景為了引出動態代理的用法,我們先看看代理設計模式,這能讓你了解JDK動態代理的應用場景的同時,讓你記憶深刻。代理模式是通過代理物件作為中間人來訪問目標物件,這樣可以完美的遵循開閉原則,從而避免修改目標物件來滿足需求而降低可維護性。
現實生活中比較常見的各種中間商、經紀人都是活生生的代理模式例子。咱們拿明星經紀人來說
上述關係現實中還是非常複雜的,比如什麼乾爹啦、劈腿啦、潛規則啦,巴拉巴拉的.....剝繭抽絲後,其實流程可簡化這樣
老闆不直接接觸明星,而是直接和經紀人商談,畢竟經紀人無論在經驗和精力上都有優勢。這裡經紀人其實就是代理物件,明星就是目標物件,老闆就是呼叫方。轉換為程式碼流程如下
這裡需要認真的強調一點:代理模式側重於控制訪問,代理物件不會改變目標物件的職責和能力,它提供與目標物件相同的介面,但會增加相應的邏輯來控制訪問目標物件。
有些網友提出代理模式是為了在目標物件的基礎上增強功能,如果較真的話小編並不不認同此種說法,因為增強基類的功能那是裝飾模式乾的活;代理模式和委託模式也有區別,後者是為了提供統一的介面服務,便於方便切換底層實現。
代理模式實現代理模式的演示實現如下(為了方便觀眾觀看,我會把程式碼集中在App.java中,專案中不允許)
public class App{ public static void main( String[] args ) { //代理物件(經紀人) Broker broker = new Broker(); System.out.println("A老闆的唱歌演出需求"); broker.doSing(); System.out.println("---------------"); System.out.println("B老闆的跳舞演出需求"); broker.doDance(); }}/** * 經紀人 */class Broker{ /** * 唱歌演出 */ public void doSing(){ //演出前業務處理 System.out.println("1.檢查節目是否和明星的調性匹配"); System.out.println("2.出場費是否滿足"); //演出 new Star().doSing(); //演出後業務處理 System.out.println("1.出場費尾款"); System.out.println("2.粉絲維護"); } /** * 跳舞演出 */ public void doDance(){ //演出前業務處理 System.out.println("1.檢查節目是否和明星的調性匹配"); System.out.println("2.出場費是否滿足"); //演出 new Star().doDance(); //演出後業務處理 System.out.println("1.出場費尾款"); System.out.println("2.粉絲維護"); }}/** * 明星 */class Star{ /** * 唱歌技能 */ public void doSing(){ System.out.println("唱歌"); } /** * 跳舞技能 */ public void doDance(){ System.out.println("跳舞"); }}
老闆不直接接觸明星,而是通過經紀人滿足業務需求。細心的同學會發現,經紀人的業務處理中存在大量重複程式碼,當然你可以把演出前後的業務封裝成方法呼叫如
/** * 唱歌演出 */public void doSing(){ //演出前業務處理 beforeDo(); //演出 new Star().doSing(); //演出後業務處理 afterDo();}
但依然不美,試想如果能把經紀人的業務技能直接一一匹配到明星的技能(doSing,doDance)就方便了,於是引出了JDK動態代理。
用JDK動態代理重構1. 為了實現“動態”需要使用面向介面的程式設計思想,把明星和經紀人抽象出一個共同的介面
/** * 明星和經紀人的介面 */interface IAct { void doSing(); void doDance();}
2. 通過實現InvocationHandler介面來做代理業務
/** * 經紀人 */class Broker implements InvocationHandler { //目標物件 private Object target; public Broker(Object target){ this.target=target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //演出前業務處理 System.out.println("1.檢查節目是否和明星的調性匹配"); System.out.println("2.出場費是否滿足"); //明星演出技能 Object object=method.invoke(target,args); //演出後業務處理 System.out.println("1.出場費尾款"); System.out.println("2.粉絲維護"); return object; }}
3. 動態建立代理物件並處理業務
public static void main( String[] args ){ //目標物件 IAct star=new Star(); //使用newProxyInstance建立動態代理物件 IAct broker=(IAct) Proxy.newProxyInstance( IAct.class.getClassLoader(), star.getClass().getInterfaces(), new Broker(star) ); //業務處理 System.out.println("A老闆的唱歌演出需求"); broker.doSing(); System.out.println("---------------"); System.out.println("B老闆的跳舞演出需求"); broker.doDance();}
經紀人執行doSing,經過業務邏輯處理後直接對映到明星的doSing,這樣就減少了很多重複的程式碼,提高了可維護性。
總結JDK實現代理模式流程如下
1. 抽象出目標物件的介面
2. 實現介面InvocationHandler建立代理業務
3. 使用newProxyInstance建立代理物件
4. 業務處理
全部程式碼如下
package com.zhaiqianfeng;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;public class App{ public static void main( String[] args ){ //目標物件 IAct star=new Star(); //使用newProxyInstance建立動態代理 IAct broker=(IAct) Proxy.newProxyInstance( IAct.class.getClassLoader(), star.getClass().getInterfaces(), new Broker(star) ); //業務處理 System.out.println("A老闆的唱歌演出需求"); broker.doSing(); System.out.println("---------------"); System.out.println("B老闆的跳舞演出需求"); broker.doDance(); }}/** * 明星和經紀人的介面 */interface IAct { void doSing(); void doDance();}/** * 經紀人 */class Broker implements InvocationHandler { //目標物件 private Object target; public Broker(Object target){ this.target=target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //演出前業務處理 System.out.println("1.檢查節目是否和明星的調性匹配"); System.out.println("2.出場費是否滿足"); //明星演出技能 Object object=method.invoke(target,args); //演出後業務處理 System.out.println("1.出場費尾款"); System.out.println("2.粉絲維護"); return object; }}/** * 明星 */class Star implements IAct { /** * 唱歌技能 */ public void doSing(){ System.out.println("唱歌"); } /** * 跳舞技能 */ public void doDance(){ System.out.println("跳舞"); }}
Java8 lambda表示式重構如果只是臨時業務處理,可以使用匿名類或Java8的lambda表示式可以更優
public static void main(String[] args) { //目標物件 IAct star = new Star(); //使用newProxyInstance建立動態代理 IAct broker = (IAct) Proxy.newProxyInstance( IAct.class.getClassLoader(), star.getClass().getInterfaces(), (proxy, method, args2) -> { //演出前業務處理 System.out.println("1.檢查節目是否和明星的調性匹配"); System.out.println("2.出場費是否滿足"); //明星演出技能 Object object = method.invoke(star, args2); //演出後業務處理 System.out.println("1.出場費尾款"); System.out.println("2.粉絲維護"); return object; } ); //業務處理 System.out.println("A老闆的唱歌演出需求"); broker.doSing(); System.out.println("---------------"); System.out.println("B老闆的跳舞演出需求"); broker.doDance();}
寫在最後JDK動態代理實際上是在執行時通過反射的方式來實現的,將代理的方法呼叫轉到到目標物件上,最終將目標物件生成的任何結果返回給呼叫方。由於這是個鏈式呼叫,所以很方便代理在目標物件方法呼叫前後增加處理邏輯。根據這種思路可以在多種設計模式中使用JDK的動態代理比如代理模式、Facade、Decorator等。
在面向方面程式設計(AOP)也應用廣泛,如事務管理、日誌記錄、資料校驗等,主要是將橫切關注點從業務邏輯中分離出來,所以一通百通。
補充一點,由於JDK的不斷優化,到JDK8的時候JDK的動態代理不比CGLIB效率低,大家可以做些實驗。
相關閱讀