原文同步發表至我的博客【夜月歸途】html
原文連接:http://www.guitu18.com/se/java/2018-06-29/17.htmljava
本博客關於Java動態代理相關內容直達連接:spring
Java中的動態代理設計模式是很是經典且很是重要的設計模式之一,在感嘆設計者的天才設計至於,咱們想去探究一下這個設計模式是如何來實現的;數據庫
著名的spring框架的AOP的原理就是Java的動態代理機制;編程
在Spring中動態代理是實現有兩種:JDK動態代理和Cglib動態代理,本篇分析的是JDK動態代理的實現;設計模式
查看下篇 [ Cglib動態代理淺析 ]數組
在探究動態代理以前,先看一下靜態代理,這能夠幫助咱們更好的理解動態代理;框架
以公司老闆和員工爲例,如今Boss有一些項目要完成(定義接口)ide
public interface Boss { public void doSomethinig(); public void finishTasks(String name); }
這些工做確定是須要僱員工去作的(去實現老闆定義的接口)post
public class Employee implements Boss { @Override public void doSomethinig() { System.out.println("員工工做中 ..."); } @Override public void finishTasks(String name) { System.out.println("員工正在完成項目 " + name + " ..."); } }
咱們先使用靜態代理,這時候咱們僱一個項目經理負責代理員工完成工做,可能有點不恰當哈,先這麼理解吧;(一樣須要實現老闆的接口,代理員工執行任務)
public class Manager implements Boss { private Employee employee = new Employee(); @Override public void doSomethinig() { // 員工完成工做以前 System.out.println(">>>>>員工完成工做以前"); // 員工正在完成工做 employee.doSomethinig(); // 員工完成工做以後 System.out.println("員工完成工做以後>>>>>"); } @Override public void finishTasks(String name) { // 員工完成工做以前 System.out.println(">>>>>員工完成工做以前"); // 員工正在完成工做 employee.finishTasks(name); // 員工完成工做以後 System.out.println("員工完成工做以後>>>>>"); } }
寫個測試類
public class TestProxy { public static void main(String[] args) { Manager manager = new Manager(); manager.doSomethinig(); manager.finishTasks("Java靜態代理"); } }
剛開始,我也一直很疑惑,員工都已經實現Boss的全部方法了,並且在測試類中也直接new出來員工對象了,直接員工.方法執行不就完了,爲何還要畫蛇添足使用一個代理類來完成執行呢?先看測試執行結果:
>>>>>員工完成工做以前 員工工做中 ... 員工完成工做以後>>>>> >>>>>員工完成工做以前 員工正在完成項目 Java靜態代理 ... 員工完成工做以後>>>>>
能夠看出在項目經理代理類中,咱們實際上是能夠在員工方法執行以前和執行以後作一些操做的,具體能夠作什麼和能作什麼,大家發揮本身想象力;
代理模式應用在實際項目中,是可以幫咱們完成不少事情的,好比個大框架中最多見的日誌記錄,數據庫事務控制,權限驗證等等,就不一一列舉了(固然它們使用的確定不是靜態代理,而是接下來要說的動態代理了);
可是靜態代理又一個很是大的弊端,就是每一個接口都須要一個代理類去實現和執行,隨着業務的數量愈來愈複雜的時候,代理類的代碼量也是十分驚人的,這對於項目來講是很難去管理維護的;而動態代理的出現,正是用來解決這個問題的;
在Java的動態代理機制中,有兩個相當重要的對象:
InvocationHandler 接口
Proxy 類
咱們看看JDK API是怎麼描述InvocationHandler 的(摘自JDK API 1.6.0中文版)
public interface InvocationHandler InvocationHandler 是代理實例的調用處理程序實現的接口。 每一個代理實例都具備一個關聯的調用處理程序。 對代理實例調用方法時,將對方法調用進行編碼並將其指派到它的調用處理程序的 invoke 方法。 invoke方法描述(摘自JDK API 1.6.0中文版) invoke Object invoke(Object proxy,Method method,Object[] args)throws Throwable 在代理實例上處理方法調用並返回結果。 在與方法關聯的代理實例上調用方法時,將在調用處理程序上調用此方法。 參數: proxy - 在其上調用方法的代理實例,指代咱們所代理的那個真實對象 method - 對應於在代理實例上調用的接口方法的 Method 實例。 - Method 對象的聲明類將是在其中聲明方法的接口,該接口能夠是代理類賴以繼承方法的代理接口的超接口。 - 指代的是咱們所要調用真實對象的某個方法的Method對象 args - 包含傳入代理實例上方法調用的參數值的對象數組,若是接口方法不使用參數,則爲 null。 - 基本類型的參數被包裝在適當基本包裝器類(如 java.lang.Integer 或 java.lang.Boolean)的實例中。 - 指代的是調用真實對象某個方法時接受的參數 返回: 從代理實例的方法調用返回的值。 若是接口方法的聲明返回類型是基本類型,則此方法返回的值必定是相應基本包裝對象類的實例; 不然,它必定是可分配到聲明返回類型的類型。 若是此方法返回的值爲 null 而且接口方法的返回類型是基本類型,則代理實例上的方法調用將拋出 NullPointerException。 不然,若是此方法返回的值與上述接口方法的聲明返回類型不兼容,則代理實例上的方法調用將拋出 ClassCastException。
咱們再來看看Proxy類(摘自JDK API 1.6.0中文版)
public class Proxy extends Object implements Serializable Proxy 提供用於建立動態代理類和實例的靜態方法,它仍是由這些方法建立的全部動態代理類的超類。 Proxy.newProxyInstance方法(摘自JDK API 1.6.0中文版) public static Object newProxyInstance( ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException 返回一個指定接口的代理類實例,該接口能夠將方法調用指派到指定的調用處理程序。 - 此方法至關於: Proxy.getProxyClass(loader, interfaces). getConstructor(new Class[] { InvocationHandler.class }). newInstance(new Object[] { handler }); - Proxy.newProxyInstance 拋出 IllegalArgumentException,緣由與 Proxy.getProxyClass 相同。 參數: loader - 定義代理類的類加載器 - ClassLoader對象,定義了由哪一個ClassLoader對象來對生成的代理對象進行加載 interfaces - 代理類要實現的接口列表 - Interface對象的數組,表示的是我將要給我須要代理的對象提供一組什麼接口 - 若是我提供了一組接口給它,那麼這個代理對象就宣稱實現了該接口(多態),這樣我就能調用這組接口中的方法了 h - 指派方法調用的調用處理程序 - InvocationHandler對象,表示的是當我這個動態代理對象在調用方法的時候,會關聯到哪個InvocationHandler對象上 返回: 一個帶有代理類的指定調用處理程序的代理實例,它由指定的類加載器定義,並實現指定的接口
如今,咱們使用動態代理來決絕剛纔的問題,Boss和員工仍是原來的
public interface Boss { public void doSomethinig(); public void finishTasks(String name); }
員工
public class Employee implements Boss { @Override public void doSomethinig() { System.out.println("員工工做中 ..."); } @Override public void finishTasks(String name) { System.out.println("員工正在完成項目 " + name + " ..."); } }
這時候咱們使用動態代理,僱一個領導,來代理全部的事情;
public class Leader implements InvocationHandler { private Object target; // 返回一個代理對象,並綁定被代理對象 public Object getProxyInstance(Object object) { this.target = object; // Proxy的newProxyInstance方法須要三個參數 return Proxy.newProxyInstance( // 1.一個類加載器,一般能夠從已經被加載的對象中獲取類加載器 object.getClass().getClassLoader(), // 2.但願代理實現的接口列表 object.getClass().getInterfaces(), // 3.一個InvocationHandler接口的實現 this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 這裏咱們簡單輸出一下invoke的第二第三個參數 System.out.println("method : " + method); System.out.println("args : " + args); if (args != null) { System.out.print("arg : "); for (Object arg : args) { System.out.print(arg + " "); } System.out.println(); } // 員工完成工做以前 System.out.println(">>>>>員工完成工做以前"); // 員工正在完成工做 Object invoke = method.invoke(target, args); // 員工完成工做以後 System.out.println("員工完成工做以後>>>>>"); return invoke; } }
測試類:
public class LeaderTest { public static void main(String[] args) { Employee employee = new Employee(); Leader leader = new Leader(); Boss boss = (Boss) leader.getProxyInstance(employee); boss.doSomethinig(); boss.finishTasks("Java動態代理"); } }
控制檯輸出:
Method : public abstract void com.guitu18.Boss.doSomethinig() args : null >>>>>員工完成工做以前 員工工做中 ... 員工完成工做以後>>>>> Method : public abstract void com.guitu18.Boss.finishTasks(java.lang.String) args : [Ljava.lang.Object;@1c7c054 arg : Java動態代理 >>>>>員工完成工做以前 員工正在完成項目 Java動態代理 ... 員工完成工做以後>>>>>
咱們在方法中打印invoke方法的第二個參數Method method,從控制檯看出,咱們在調用方法的時候,實際上使用的是代理對象來調用真實對象的方法的:
Method : public abstract void com.guitu18.Boss.doSomethinig() Method : public abstract void com.guitu18.Boss.finishTasks(java.lang.String)
而打印invoke方法的第三個參數Object[] args時咱們發現,該數組其實就是真實方法接收的參數,若是沒有則爲null;
args : null args : [Ljava.lang.Object;@1c7c054 arg : Java動態代理
如今,咱們能夠在method.invoke方法的先後都加上本身的一些操做;
在各大框架中,使用動態代理的比比皆是,好比如著名的Spring框架的AOP原理,好比Shiro的權限驗證,攔截器等等;
從以上代碼中,咱們也能夠看出,使用動態代理以後,代理類的代碼量已經被固定下來了,不會隨着業務的複雜和龐大而變得愈來愈多;
其實在這裏,咱們已經能夠把代碼寫成代理工廠模式了,簡化一些操做:
public class ProxyFactory { public static Object getProxyInstance(final Object target) { return 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("開始事務..."); // 執行方法 Object invoke = method.invoke(target, args); // 方法執行後 System.out.println("提交/回滾事務..."); return invoke; } }); } }
在ProxyFactory裏,提供了一個靜態方法,我麼僅須要傳遞進來一個代理類的具體實現,就可以得到一個代理對象,在這個方法中,Proxy.newProxyInstance須要的第三個參數,是以匿名內部類的方式去提供和定義的,更加簡潔和靈活;
編寫測試類:
public class ProxyFactoryTest { public static void main(String[] args) { Boss boss = (Boss) ProxyFactory.getProxyInstance(new Employee()); boss.doSomethinig(); boss.finishTasks("Java動態代理"); } }
控制檯輸出:
開始事務... 員工工做中 ... 提交/回滾事務... 開始事務... 員工正在完成項目 Java動態代理 ... 提交/回滾事務...
在動態代理技術裏,因爲無論用戶調用代理對象的什麼方法,都是調用開發人員編寫的處理器的invoke方法(這至關於invoke方法攔截到了代理對象的方法調用)。
而且,開發人員經過invoke方法的參數,還能夠在攔截的同時,知道用戶調用的是什麼方法,所以利用這兩個特性,就能夠實現一些特殊需求;
例如:
數據庫事務的操做,在方法執行前開啓事務,執行完畢提交事務,若是方法執行出錯,回滾事務; 攔截用戶的訪問請求,以檢查用戶是否有訪問權限,有權限繼續執行,沒權限不執行作其餘操做; 加強,動態爲某個對象添加額外的功能;
尾巴: