淺談JDK動態代理<轉載>

一個小需求:給原有方法添加日誌打印

假設如今咱們有一個類Calculator,表明一個計算器,它能夠進行加減乘除操做html

public class Calculator {

    //加
    public int add(int a, int b) {
        int result = a + b;
        return result;
    }

    //減
    public int subtract(int a, int b) {
        int result = a - b;
        return result;
    }

    //乘法、除法...
}

現有一個需求:在每一個方法執行先後打印日誌。你有什麼好的方案?java

直接修改ide

不少人最直觀的想法是直接修改Calculator類:this

public class Calculator {

    //加
    public int add(int a, int b) {
        System.out.println("add方法開始...");
        int result = a + b;
        System.out.println("add方法結束...");
        return result;
    }

    //減
    public int subtract(int a, int b) {
        System.out.println("subtract方法開始...");
        int result = a - b;
        System.out.println("subtract方法結束...");
        return result;
    }

    //乘法、除法...
}

上面的方案是有問題的:編碼

  1. 直接修改源程序,不符合開閉原則。應該對擴展開放,對修改關閉
  2. 若是Calculator有幾十個、上百個方法,修改量太大
  3. 存在重複代碼(都是在覈心代碼先後打印日誌)
  4. 日誌打印硬編碼在代理類中,不利於後期維護:好比你花了一上午終於寫完了,組長告訴你這個功能取消,因而你又要打開Calculator花十分鐘刪除日誌打印的代碼!

因此,此種方案PASS!spa


靜態代理實現日誌打印

「靜態代理」四個字包含了兩個概念:靜態、代理。咱們先來了解什麼叫「代理」,至於何爲「靜態」,須要和「動態」對比着講。3d

代理是一種模式,提供了對目標對象的間接訪問方式,即經過代理訪問目標對象。如此便於在目標實現的基礎上增長額外的功能操做,前攔截,後攔截等,以知足自身的業務需求。
引用博客: Java靜態代理和動態代理 - 紀煜楷 - 博客園

引用自:Java靜態代理和動態代理 - 紀煜楷 - 博客園代理

經常使用的代理方式能夠粗分爲:靜態代理和動態代理。日誌

靜態代理的實現比較簡單:編寫一個代理類,實現與目標對象相同的接口,並在內部維護一個目標對象的引用。經過構造器塞入目標對象,在代理對象中調用目標對象的同名方法,並添加前攔截,後攔截等所需的業務功能。code

按上面的描述,代理類和目標類須要實現同一個接口,因此我打算這樣作:

  • 將Calculator抽取爲接口
  • 建立目標類CalculatorImpl實現Calculator
  • 建立代理類CalculatorProxy實現Calculator

接口

/**
 * Calculator接口
 */
public interface Calculator {
    int add(int a, int b);
    int subtract(int a, int b);
}

目標對象實現類

/**
 * 目標對象實現類,實現Calculator接口
 */
public class CalculatorImpl implements Calculator {

    //加
    public int add(int a, int b) {
        int result = a + b;
        return result;
    }

    //減
    public int subtract(int a, int b) {
        int result = a - b;
        return result;
    }

    //乘法、除法...
}

代理對象實現類

/**
 * 代理對象實現類,實現Calculator接口
 */
public class CalculatorProxy implements Calculator {
        //代理對象內部維護一個目標對象引用
    private Calculator target;
        
        //構造方法,傳入目標對象
    public CalculatorProxy(Calculator target) {
        this.target = target;
    }

        //調用目標對象的add,並在先後打印日誌
    @Override
    public int add(int a, int b) {
        System.out.println("add方法開始...");
        int result = target.add(a, b);
        System.out.println("add方法結束...");
        return result;
    }

        //調用目標對象的subtract,並在先後打印日誌
    @Override
    public int subtract(int a, int b) {
        System.out.println("subtract方法開始...");
        int result = target.subtract(a, b);
        System.out.println("subtract方法結束...");
        return result;
    }

    //乘法、除法...
}

使用代理對象完成加減乘除,而且打印日誌

public class Test {
    public static void main(String[] args) {
        //把目標對象經過構造器塞入代理對象
        Calculator calculator = new CalculatorProxy(new CalculatorImpl());
        //代理對象調用目標對象方法完成計算,並在先後打印日誌
        calculator.add(1, 2);
        calculator.subtract(2, 1);
    }
}

靜態代理示意圖

靜態代理的優勢:能夠在不修改目標對象的前提下,對目標對象進行功能的擴展和攔截。可是它也僅僅解決了上一種方案4大缺點中的第1點:

  1. 直接修改源程序,不符合開閉原則。應該對擴展開放,對修改關閉 √
  2. 若是Calculator有幾十個、上百個方法,修改量太大 ×
  3. 存在重複代碼(都是在覈心代碼先後打印日誌) ×
  4. 日誌打印硬編碼在代理類中,不利於後期維護:好比你花了一上午終於寫完了,組長告訴你這個功能取消,因而你又要打開Calculator花十分鐘刪除所有新增代碼!×
    • *

靜態代理的問題

上面案例中,代理類是咱們事先編寫的,並且要和目標對象類實現相同接口。因爲CalculatorImpl(目標對象)須要日誌功能,咱們即編寫了CalculatorProxy(代理對象),並經過構造器傳入CalculatorImpl(目標對象),調用目標對象同名方法的同時添加加強代碼。

可是這裏有個問題!代理對象構造器的參數類型是Calculator,這意味着它只能接受Calculator的實現類對象,亦即咱們寫的代理類CalculatorProxy只能給Calculator作代理,它們綁定死了!

若是如今咱們系統須要全面改造,給其餘類也添加日誌打印功能,就得爲其餘幾百個接口都各自寫一份代理類...

本身手動寫一個類並實現接口實在太麻煩了。仔細一想,咱們其實想要的並非代理類,而是代理對象!那麼,可否讓JVM根據接口自動生成代理對象呢?

好比,有沒有一個方法,我傳入接口,它就給我自動返回代理對象呢?

答案是確定的。

相關文章
相關標籤/搜索