Java 代理模式與 AOP

本文首發(fā)于 https://jaychen.cc
作者 jaychen

最近在學(xué) Spring,研究了下 AOP 和代理模式譬重,寫點(diǎn)心得和大家分享下拒逮。

AOP

先說下AOP,AOP 全稱 Aspect Oriented Programming臀规,面向切面編程滩援,和 OOP 一樣也是一種編程思想。AOP 出現(xiàn)的原因是為了解決 OOP 在處理 侵入性業(yè)務(wù)上的不足塔嬉。

那么玩徊,什么是侵入性業(yè)務(wù)?類似日志統(tǒng)計(jì)谨究、性能分析等就屬于侵入性業(yè)務(wù)恩袱。本來原本的業(yè)務(wù)邏輯代碼優(yōu)雅大氣,正常運(yùn)行胶哲,突然說需要在這段邏輯里面加上性能分析畔塔,于是代碼就變成了下面這個(gè)樣子


long begin = System.currentTimeMillis(); 

// 原本的業(yè)務(wù)
doSomething();

long end = System.currentTimeMillis();
long step = end - begin;
System.out.println("執(zhí)行花費(fèi) :" + step);

從上面的代碼看到,性能分析的業(yè)務(wù)代碼和原本的業(yè)務(wù)代碼混在了一起鸯屿,好端端的代碼就這么被糟蹋了澈吨。所以,侵入性業(yè)務(wù)必須有一個(gè)更好的解決方案碾盟,這個(gè)解決方案就是 AOP棚辽。

那么,AOP 是如何解決這類問題冰肴?

代理模式

通常屈藐,我們會(huì)使用代理模式來實(shí)現(xiàn) AOP榔组,這就意味著代理模式可以優(yōu)雅的解決侵入性業(yè)務(wù)問題。所以下面來重點(diǎn)分析下代理模式联逻。

這個(gè)是代理模式的類圖搓扯。很多人可能看不懂類圖,但是說實(shí)話有時(shí)候一圖勝千言包归,這里稍微解釋下類圖的含義锨推,尤其是類圖中存在的幾種連線符。

  • 矩形代表一個(gè)類公壤,矩形內(nèi)部的信息有:類名换可,屬性和方法。
  • 虛線 + 三角空心箭頭為 is=a 的關(guān)系厦幅,表示繼承沾鳄,所以上圖中 TestSQLPerformance 都實(shí)現(xiàn) IDatabase 接口。
  • 實(shí)線 + 箭頭為關(guān)聯(lián)關(guān)系确憨,一般在代碼中以成員變量的形式體現(xiàn)译荞,所以上圖中 Performance 類有一個(gè) TestSQL 的成員變量。

有了類圖休弃,我們可以根據(jù)類圖直接寫出代理模式的代碼了吞歼。這里代理模式分為靜態(tài)代理和動(dòng)態(tài)代理兩種,我們分別來看下塔猾。

靜態(tài)代理

假設(shè)一個(gè)場(chǎng)景篙骡,我們需要測(cè)試一條 sql query 執(zhí)行所花費(fèi)的時(shí)間。

如果按照普通的方式桥帆,代碼邏輯應(yīng)該如下

long begin = System.currentTimeMillis(); 

query();

long end = System.currentTimeMillis();
long step = end - begin;
System.out.println("執(zhí)行花費(fèi) :" + step);

上面說過了医增,這種會(huì)導(dǎo)致查詢邏輯和性能測(cè)試邏輯混淆在一塊,那么來看看使用代理模式是如何解決這個(gè)問題的老虫。

代理模式叶骨,代理,意味著有一方代替另一方完成一件事祈匙。這里忽刽,我們會(huì)編寫兩個(gè)類:TestSQL 為query 執(zhí)行邏輯,Performance 為性能測(cè)試類夺欲。這里 Performance 會(huì)代替 TestSQL 去執(zhí)行 query 邏輯跪帝。

要想 Performance 能夠代替 TestSQL 執(zhí)行 query 邏輯,那么這兩個(gè)類應(yīng)該是有血緣關(guān)系的些阅,即這兩個(gè)必須實(shí)現(xiàn)同一個(gè)接口伞剑。

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


public class TestSQL implements IDatabase {

    @Override
    public void query() {
        System.out.println("執(zhí)行 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();

        // 業(yè)務(wù)邏輯。
        sql.query();

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


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

        PerformanceMonitor performanceMonitor = new PerformanceMonitor(sql);
        // 由 Performance 代替 testSQL 執(zhí)行
        performanceMonitor.query();
    }
}

從上面的示例代碼可以分析出來代理模式是如何運(yùn)作的抒倚,這里我們可以很明顯看出代理模式的優(yōu)越性褐着,TestSQL 的邏輯很純粹,沒有混入其他無關(guān)的業(yè)務(wù)代碼托呕。

動(dòng)態(tài)代理

回顧靜態(tài)代理的代碼含蓉,發(fā)現(xiàn)代理類 Performance 必須實(shí)現(xiàn) IDatabase 接口。如果有很多業(yè)務(wù)需要用到代理來實(shí)現(xiàn)项郊,那么每個(gè)業(yè)務(wù)都需要定義一個(gè)代理類馅扣,這會(huì)導(dǎo)致類迅速膨脹,為了避免這點(diǎn)呆抑,Java 提供了動(dòng)態(tài)代理岂嗓。

為何稱之為動(dòng)態(tài)代理,動(dòng)態(tài)代理底層是使用反射實(shí)現(xiàn)的鹊碍,是在程序運(yùn)行期間動(dòng)態(tài)的創(chuàng)建接口的實(shí)現(xiàn)。在靜態(tài)代理中食绿,我們需要在編碼的時(shí)候編寫 Performance 類實(shí)現(xiàn) IDatabase 接口侈咕。而使用動(dòng)態(tài)代理,我們不必編寫 Performance 實(shí)現(xiàn) IDatabase 接口器紧,而是 JDK 在底層通過反射技術(shù)動(dòng)態(tài)創(chuàng)建一個(gè) IDatabase 接口的實(shí)現(xiàn)耀销。

使用動(dòng)態(tài)代理需要使用到 InvocationHandlerProxy 這兩個(gè)類。

// 代理類,不再實(shí)現(xiàn) IDatabase 接口铲汪,而是實(shí)現(xiàn) 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 實(shí)際上就是調(diào)用 sql.query()
        Object object = method.invoke(sql, args);

        long end = System.currentTimeMillis();
        long step = end - begin;
        System.out.println("執(zhí)行花費(fèi) :" + 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 函數(shù)熊尉,這個(gè)函數(shù)的作用就是用來動(dòng)態(tài)創(chuàng)建一個(gè)代理對(duì)象的類,這個(gè)函數(shù)需要三個(gè)參數(shù):

  • 第一個(gè)參數(shù)為類加載器掌腰,如果不懂是什么玩意狰住,先套著模板寫,等我寫下一篇文章拯救你齿梁。
  • 第二個(gè)參數(shù)為要代理的接口催植,在這個(gè)例子里面就是 IDatabase 接口。
  • 第三個(gè)參數(shù)為實(shí)現(xiàn) InvocationHandler 接口的對(duì)象勺择。

執(zhí)行 newProxyInstance 之后创南,Java 會(huì)在底層自動(dòng)生成一個(gè)代理類,其代碼大概如下:

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

    private $Proxy1(){}

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

    public void query(){
        ////創(chuàng)建method對(duì)象
        Method method = Subject.class.getMethod("query");
        //調(diào)用了invoke方法
        h.invoke(this, method, new Object[]{}); 
    }
}

你會(huì)發(fā)現(xiàn)省核,這個(gè)類很像在靜態(tài)代理中的 Performance 類稿辙,是的,動(dòng)態(tài)代理其本質(zhì)是 Java 自動(dòng)為我們生成了一個(gè) $Proxy1 代理類气忠。在 mian 函數(shù)中 newProxyInstance 的返回值就是該類的一個(gè)實(shí)例邻储。并且赋咽,$Proxy1 中的 h 屬性就是 newProxyInstance 的第三個(gè)參數(shù)。所以芥备,當(dāng)我們?cè)?main 函數(shù)中執(zhí)行 proxy.query()冬耿,實(shí)際上是調(diào)用 $proxy1#query 方法,進(jìn)而再調(diào)用 Performance#invoke 方法萌壳。而在 Performance#invoke 通過 Object object = method.invoke(sql, args); 調(diào)用了 TestSQL#query 方法亦镶。

回顧上面的流程,理解動(dòng)態(tài)代理的核心在于理解 Java 自動(dòng)生成的代理類袱瓮。這里還有一點(diǎn)要說明缤骨,JDK 的動(dòng)態(tài)代理有一個(gè)不足:它只能為接口創(chuàng)建代理實(shí)例。這句話體現(xiàn)在代碼上就是 newProxyInstance 的第二個(gè)參數(shù)是一個(gè)接口數(shù)組尺借。為什么會(huì)存在這個(gè)不足绊起?其實(shí)看 $Proxy1 代理類就知道了,這個(gè)由 JDK 生成的代理類需要繼承 Proxy 類燎斩,而 Java 只支持單繼承虱歪,所以就限制了 JDK 的動(dòng)態(tài)代理只能為接口創(chuàng)建代理。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末栅表,一起剝皮案震驚了整個(gè)濱河市笋鄙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌怪瓶,老刑警劉巖萧落,帶你破解...
    沈念sama閱讀 218,036評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異洗贰,居然都是意外死亡找岖,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門敛滋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來许布,“玉大人,你說我怎么就攤上這事矛缨〉ⅲ” “怎么了?”我有些...
    開封第一講書人閱讀 164,411評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵箕昭,是天一觀的道長(zhǎng)灵妨。 經(jīng)常有香客問我,道長(zhǎng)落竹,這世上最難降的妖魔是什么泌霍? 我笑而不...
    開封第一講書人閱讀 58,622評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上朱转,老公的妹妹穿的比我還像新娘蟹地。我一直安慰自己,他們只是感情好藤为,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評(píng)論 6 392
  • 文/花漫 我一把揭開白布怪与。 她就那樣靜靜地躺著,像睡著了一般缅疟。 火紅的嫁衣襯著肌膚如雪分别。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,521評(píng)論 1 304
  • 那天存淫,我揣著相機(jī)與錄音耘斩,去河邊找鬼。 笑死桅咆,一個(gè)胖子當(dāng)著我的面吹牛括授,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播岩饼,決...
    沈念sama閱讀 40,288評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼荚虚,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了籍茧?” 一聲冷哼從身側(cè)響起曲管,我...
    開封第一講書人閱讀 39,200評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎硕糊,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體腊徙,經(jīng)...
    沈念sama閱讀 45,644評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡简十,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了撬腾。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片螟蝙。...
    茶點(diǎn)故事閱讀 39,953評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖民傻,靈堂內(nèi)的尸體忽然破棺而出胰默,到底是詐尸還是另有隱情,我是刑警寧澤漓踢,帶...
    沈念sama閱讀 35,673評(píng)論 5 346
  • 正文 年R本政府宣布牵署,位于F島的核電站,受9級(jí)特大地震影響喧半,放射性物質(zhì)發(fā)生泄漏奴迅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評(píng)論 3 329
  • 文/蒙蒙 一挺据、第九天 我趴在偏房一處隱蔽的房頂上張望取具。 院中可真熱鬧脖隶,春花似錦、人聲如沸暇检。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)块仆。三九已至构蹬,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間榨乎,已是汗流浹背怎燥。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蜜暑,地道東北人铐姚。 一個(gè)月前我還...
    沈念sama閱讀 48,119評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像肛捍,于是被迫代替她去往敵國(guó)和親隐绵。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容