JDK動態代理淺析

原文同步發表至我的博客【夜月歸途】html

原文連接:http://www.guitu18.com/se/java/2018-06-29/17.htmljava

本博客關於Java動態代理相關內容直達連接:spring

  1. JDK動態代理淺析
  2. Cglib動態代理淺析
  3. JDK動態代理深刻理解分析並手寫簡易JDK動態代理(上)
  4. JDK動態代理深刻理解分析並手寫簡易JDK動態代理(下)

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方法的參數,還能夠在攔截的同時,知道用戶調用的是什麼方法,所以利用這兩個特性,就能夠實現一些特殊需求;

例如:

數據庫事務的操做,在方法執行前開啓事務,執行完畢提交事務,若是方法執行出錯,回滾事務; 攔截用戶的訪問請求,以檢查用戶是否有訪問權限,有權限繼續執行,沒權限不執行作其餘操做; 加強,動態爲某個對象添加額外的功能;

尾巴:

  1. 動態代理是十分強大的設計模式,代理類的代碼量被固定下來,不會由於業務的逐漸龐大而龐大;
  2. 能夠實現AOP編程,實際上靜態代理也能夠實現,總的來講,AOP能夠算做是代理模式的一個典型應用;
  3. 解耦,經過參數就能夠判斷真實類,不須要事先實例化,更加靈活多變;
相關文章
相關標籤/搜索