[肥朝]Dubbo-SPI和AOP的前世此生

前言

本篇是spi的第四篇,本篇講解的是spi中增長的AOP,仍是和上一篇同樣,咱們先從你們熟悉的spring引出AOP.java

AOP是老生常談的話題了,思想都不會是一蹴而就的.好比架構設計從All in OneSOA也是一個逐步演進的過程,因此本篇也講講這個AOP的思想演進過程.面試

插播面試題

  • 你提到了dubbo中spi也增長了AOP,那你講講這用到了什麼設計模式,dubbo又是如何作的.

直入主題

假如咱們就以AOP最經常使用的場景事務來講,咱們最初的作法是怎麼樣的?spring

簡單作法

public class EmployeeServiceImpl implements IEmployeeService {

    private TransactionManager txManager;

    @Override
    public void save() {
        try {
            txManager.begin();
            System.out.println("保存操做");
            txManager.commit();
        }catch (Exception e){
            txManager.rollback();
            e.printStackTrace();
        }
    }

    @Override
    public void update() {
        try {
            txManager.begin();
            System.out.println("更新操做");
            txManager.commit();
        }catch (Exception e){
            txManager.rollback();
            e.printStackTrace();
        }
    }
}
複製代碼

這些代碼存在的問題就很明顯了,好比設計模式

  • 處理事務的代碼大量重複
  • 根據責任分離思想,在業務方法中,只須要處理業務功能,不應處理事務.

優化代碼咱們第一個想到的是設計模式,那麼咱們進入以下的優化緩存

裝飾設計模式

public class APP {

    @Test
    public void testSave() throws Exception {
        IEmployeeService service = new EmployeeServiceImplWapper(new TransactionManager(),
                new EmployeeServiceImpl());
        service.save();
    }

    @Test
    public void testUpdate() throws Exception {
        IEmployeeService service = new EmployeeServiceImplWapper(new TransactionManager(),
                new EmployeeServiceImpl());
        service.update();
    }
}
複製代碼

經過裝飾設計模式,咱們解決了上面遇到的兩個問題,可是同時也引出了新的問題,在客戶端咱們暴露了真實的對象EmployeeServiceImpl,這樣就很不安全,那麼咱們可不能夠把真實對象隱藏起來,讓使用者看不到呢?那麼咱們進一步優化安全

靜態代理

經過這種方式,真實對象對使用者進行了必定的隱藏,可是又引出了新的問題架構

  • 若是須要代理的方法不少,則每一種都要處理.好比圖中只處理了save方法,萬一有不少方法,則須要處理不少次
  • 接口新增了方法後,除了全部實現類須要實現這個方法外,全部代理類也須要實現此方法(EmployeeServiceImplEmployeeServiceImplProxy都要改動),增長了代碼的維護難度
  • 代理對象的某個接口只服務於某一種類型的對象,好比EmployeeServiceImplProxy是隻給IEmployeeService接口服務的,假如我新增了一個IRoleService,又要搞一個RoleServiceImplProxy,增長了維護難度

鑑於以上問題,咱們可否再優化一下呢?答案是能夠的app

動態代理

動態代理類是在程序運行期間由JVM經過反射等機制動態的生成的,因此不存在代理類的字節碼文件.代理對象和真實對象的關係是在程序運行事情才肯定的.ide

動態代理的方式和區別咱們前面有講過,這裏就簡單演示一下jdk動態代理函數

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class JDKProxyTest {

    @Autowired
    private TransactionManagerInvocationHandle handle;

    @Test
    public void testSave() throws Exception {
        IEmployeeService service = handle.getProxyObject();
        service.save();
    }

    @Test
    public void testUpdate() throws Exception {
        IEmployeeService service = handle.getProxyObject();
        service.update();
    }
}
複製代碼
public class TransactionManagerInvocationHandle implements InvocationHandler {

    @Setter
    private TransactionManager txManager;
    @Setter
    private Object target;//真實對象

    //生成代理對象
    //泛型只是爲了調用時不用強轉,若是用Object的話調用時須要強轉
    public <T> T getProxyObject() {
        return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(),//類加載器
                target.getClass().getInterfaces(),//爲哪些接口作代理(攔截什麼方法)
                this);//爲哪一個類監聽加強操做的方法(把這些方法攔截到哪裏處理)
    }

    //如何作加強操做(被攔截的方法在這裏加強處理)
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        Object obj = null;

        try {
            txManager.begin();
            //原封不動調用以前的方法
            obj = method.invoke(target, args);
            txManager.commit();
            return obj;
        } catch (Exception e) {
            e.printStackTrace();
            txManager.rollback();
        }

        return obj;
    }
}
複製代碼

這樣,對於使用者來講,就無需再關心事務的邏輯.固然這個還須要getProxyObject獲取動態代理對象是否是仍是太麻煩,那如何不調用getProxyObject就無聲無息的注入動態代理對象呢?能夠觀看以前的dubbo源碼解析-簡單原理、與spring融合

dubbo-spi-aop

看了這麼多演進的過程,是否是仍是沒有看到dubbo源碼的影子?由於dubbo在作spi的設計的時候,也是有一個演進和優化的過程的.咱們來看看dubbo是怎麼作的

//dubbo spi中的aop
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
複製代碼

下面引用文檔介紹

ExtensionLoader 在加載擴展點時,若是加載到的擴展點有拷貝構造函數,則斷定爲擴展點 Wrapper 類。

Wrapper類內容:

package com.alibaba.xxx;

import com.alibaba.dubbo.rpc.Protocol;

public class XxxProtocolWrapper implemenets Protocol {
    Protocol impl;

    public XxxProtocol(Protocol protocol) { impl = protocol; }

    // 接口方法作一個操做後,再調用extension的方法
    public void refer() {
        //... 一些操做
        impl.refer();
        // ... 一些操做
    }

    // ...
}
複製代碼

經過 Wrapper 類能夠把全部擴展點公共邏輯移至 Wrapper 中。新加的 Wrapper 在全部的擴展點上添加了邏輯,有些相似 AOP,即 Wrapper 代理了擴展點。

看到這裏可能發現,dubbo裏面的spi增長的aop,其實就是裝飾者設計模式.可是從上面的演進中咱們發現,裝飾者設計模式仍是有不少弊端的,後面是逐步演進,最後到達動態代理.那dubbo又是如何處理這個弊端逐步演進的?

dubbo裏面有個概念叫擴展點自適應,也就是給接口注入拓展點是一個Adaptive實例,直到方法執行時,才決定調用的是哪個拓展點的實現.這個在下一篇的Adaptive會詳細介紹,本篇其實也是下一篇的啓蒙篇.

敲黑板劃重點-小技巧

既然本篇提到了spring的aop,那麼這裏插播一個小技巧,Spring的AOP加強方式一共有5種,分別爲

加強類型 應用場景
前置加強 權限控制、記錄調用日誌
後置加強 統計分析結果數據
異常加強 經過日誌記錄方法異常信息
最終加強 釋放資源
環繞加強 緩存、性能、權限、事務管理

面試的時候也會問到5種加強方式,可是不少同窗都是說,我天天都在加班,哪有時間記這些.可是其實若是你理解他的設計思想,那麼就能夠"理解性記憶",之後想忘都忘不掉.

//環繞
try {
    //前置
    System.out.println("=====");
    //後置
}catch (Exception e){
    //異常
}finally {
    //最終
}
複製代碼

其實他這5種方式就是根據try-catch-finally的模型來設計的,只要你記住了這個設計的思想,天然不會忘記這5種方式,這也是我以前反覆強調的,理解透原理和設計思想,不少東西都是一通百通的.

寫在最後

肥朝 是一個專一於 原理、源碼、開發技巧的技術公衆號,號內原創專題式源碼解析、真實場景源碼原理實戰(重點)。掃描下面二維碼關注肥朝,讓本該造火箭的你,再也不擰螺絲!

相關文章
相關標籤/搜索