Java 代理模式與 AOP

本文首發於 jaychen.cc
做者 jaychenjava

最近在學 Spring,研究了下 AOP 和代理模式,寫點心得和你們分享下。sql

AOP

先說下AOP,AOP 全稱 Aspect Oriented Programming,面向切面編程,和 OOP 同樣也是一種編程思想。AOP 出現的緣由是爲了解決 OOP 在處理 侵入性業務上的不足。編程

那麼,什麼是侵入性業務?相似日誌統計、性能分析等就屬於侵入性業務。原本本來的業務邏輯代碼優雅大氣,正常運行,忽然說須要在這段邏輯裏面加上性能分析,因而代碼就變成了下面這個樣子數組

long begin = System.currentTimeMillis(); 

// 本來的業務
doSomething();

long end = System.currentTimeMillis();
long step = end - begin;
System.out.println("執行花費 :" + step);複製代碼

從上面的代碼看到,性能分析的業務代碼和本來的業務代碼混在了一塊兒,好端端的代碼就這麼被糟蹋了。因此,侵入性業務必須有一個更好的解決方案,這個解決方案就是 AOP。ide

那麼,AOP 是如何解決這類問題?函數

代理模式

一般,咱們會使用代理模式來實現 AOP,這就意味着代理模式能夠優雅的解決侵入性業務問題。因此下面來重點分析下代理模式。性能

這個是代理模式的類圖。不少人可能看不懂類圖,可是說實話有時候一圖勝千言,這裏稍微解釋下類圖的含義,尤爲是類圖中存在的幾種連線符。測試

  • 矩形表明一個類,矩形內部的信息有:類名,屬性和方法。
  • 虛線 + 三角空心箭頭爲 is=a 的關係,表示繼承,因此上圖中 TestSQLPerformance 都實現 IDatabase 接口。
  • 實線 + 箭頭爲關聯關係,通常在代碼中以成員變量的形式體現,因此上圖中 Performance 類有一個 TestSQL 的成員變量。

有了類圖,咱們能夠根據類圖直接寫出代理模式的代碼了。這裏代理模式分爲靜態代理和動態代理兩種,咱們分別來看下。this

靜態代理

假設一個場景,咱們須要測試一條 sql query 執行所花費的時間。編碼

若是按照普通的方式,代碼邏輯應該以下

long begin = System.currentTimeMillis(); 

query();

long end = System.currentTimeMillis();
long step = end - begin;
System.out.println("執行花費 :" + step);複製代碼

上面說過了,這種會致使查詢邏輯和性能測試邏輯混淆在一塊,那麼來看看使用代理模式是如何解決這個問題的。

代理模式,代理,意味着有一方代替另外一方完成一件事。這裏,咱們會編寫兩個類:TestSQL 爲query 執行邏輯,Performance 爲性能測試類。這裏 Performance 會代替 TestSQL 去執行 query 邏輯。

要想 Performance 可以代替 TestSQL 執行 query 邏輯,那麼這兩個類應該是有血緣關係的,即這兩個必須實現同一個接口。

// 接口
public interface IDatabase {
    void query();
}


public class TestSQL implements IDatabase {

    @Override
    public void query() {
        System.out.println("執行 query。。。。");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


// 代理類
public class PerformanceMonitor implements IDatabase {
    TestSQL sql;

    public PerformanceMonitor(TestSQL sql) {
        this.sql = sql;
    }


    @Override
    public void query() {
        long begin = System.currentTimeMillis();

        // 業務邏輯。
        sql.query();

        long end = System.currentTimeMillis();
        long step = end - begin;
        System.out.println("執行花費 : " + step);
    }
}


// 測試代碼
public class Main {
    public static void main(String[] strings) {
        TestSQL sql = new TestSQL();

        PerformanceMonitor performanceMonitor = new PerformanceMonitor(sql);
        // 由 Performance 代替 testSQL 執行
        performanceMonitor.query();
    }
}複製代碼

從上面的示例代碼能夠分析出來代理模式是如何運做的,這裏咱們能夠很明顯看出代理模式的優越性,TestSQL 的邏輯很純粹,沒有混入其餘無關的業務代碼。

動態代理

回顧靜態代理的代碼,發現代理類 Performance 必須實現 IDatabase 接口。若是有不少業務須要用到代理來實現,那麼每一個業務都須要定義一個代理類,這會致使類迅速膨脹,爲了不這點,Java 提供了動態代理。

爲什麼稱之爲動態代理,動態代理底層是使用反射實現的,是在程序運行期間動態的建立接口的實現。在靜態代理中,咱們須要在編碼的時候編寫 Performance 類實現 IDatabase 接口。而使用動態代理,咱們沒必要編寫 Performance 實現 IDatabase 接口,而是 JDK 在底層經過反射技術動態建立一個 IDatabase 接口的實現。

使用動態代理須要使用到 InvocationHandlerProxy 這兩個類。

// 代理類,再也不實現 IDatabase 接口,而是實現 InvocationHandler 接口
public class Performance implements InvocationHandler {

    private TestSQL sql;

    public Performance(TestSQL sql) {
        this.sql = sql;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long begin = System.currentTimeMillis();

        // method.invoke 實際上就是調用 sql.query()
        Object object = method.invoke(sql, args);

        long end = System.currentTimeMillis();
        long step = end - begin;
        System.out.println("執行花費 :" + step);
        return object;
    }
}



public class Main {
    public static void main(String[] strings) {
        TestSQL sql = new TestSQL();
        Performance performance = new Performance(sql);

        IDatabase proxy = (IDatabase) Proxy.newProxyInstance(
                sql.getClass().getClassLoader(),
                sql.getClass().getInterfaces(),
                performance
        );
        proxy.query();
    }
}複製代碼

先來看看 newProxyInstance 函數,這個函數的做用就是用來動態建立一個代理對象的類,這個函數須要三個參數:

  • 第一個參數爲類加載器,若是不懂是什麼玩意,先套着模板寫,等我寫下一篇文章拯救你。
  • 第二個參數爲要代理的接口,在這個例子裏面就是 IDatabase 接口。
  • 第三個參數爲實現 InvocationHandler 接口的對象。

執行 newProxyInstance 以後,Java 會在底層自動生成一個代理類,其代碼大概以下:

public final class $Proxy1 extends Proxy implements IDatabase{
    private InvocationHandler h;

    private $Proxy1(){}

    public $Proxy1(InvocationHandler h){
        this.h = h;
    }

    public void query(){
        ////建立method對象
        Method method = Subject.class.getMethod("query");
        //調用了invoke方法
        h.invoke(this, method, new Object[]{}); 
    }
}複製代碼

你會發現,這個類很像在靜態代理中的 Performance 類,是的,動態代理其本質是 Java 自動爲咱們生成了一個 $Proxy1 代理類。在 mian 函數中 newProxyInstance 的返回值就是該類的一個實例。而且,$Proxy1 中的 h 屬性就是 newProxyInstance 的第三個參數。因此,當咱們在 main 函數中執行 proxy.query(),其實是調用 $proxy1#query 方法,進而再調用 Performance#invoke 方法。而在 Performance#invoke 經過 Object object = method.invoke(sql, args); 調用了 TestSQL#query 方法。

回顧上面的流程,理解動態代理的核心在於理解 Java 自動生成的代理類。這裏還有一點要說明,JDK 的動態代理有一個不足:它只能爲接口建立代理實例。這句話體如今代碼上就是 newProxyInstance 的第二個參數是一個接口數組。爲何會存在這個不足?其實看 $Proxy1 代理類就知道了,這個由 JDK 生成的代理類須要繼承 Proxy 類,而 Java 只支持單繼承,因此就限制了 JDK 的動態代理只能爲接口建立代理。

相關文章
相關標籤/搜索