動態代理的實際應用

crossoverJie發表於2020-03-30

原文連結

前言

最近在用 PythonSQLAlchemy 庫時(一個類似於 HibernateORM 框架),發現它的 Events 事件還挺好用。

簡單說就是當某張表的資料發生變化(曾、刪、改)時會有一個事件回撥,這樣一些埋點之類的需求都可以實現在這裡,同時和業務程式碼完全解耦,維護起來也很方便。

例如當訂單狀態發生變化需要發非同步通知這樣的需求也可以利用這個實現。

根據我之前使用 Mybatis 的經驗,好像沒怎麼注意有這個功能,查閱了下發現 Hibernate 是支援的,只是我用得也少,所以也沒怎麼在意。

逐漸偏離主題。。。

說這些的主要原因是我打算為之前寫的 cicada (輕量的 http 框架)加一個資料庫操作包,也實現類似的功能。

示例

最終的使用效果如下:

第一版本還比較粗糙,但功能都具備。

動態代理的實際應用

第一步:需要實現一個初始化介面,該介面會在應用初始化的時候執行。


緊接著我們需要定義一個 Model

@Data
@OriginName("user")
@ToString
public class User extends Model {
    @PrimaryId
    private Integer id ;
    private String name ;
    private String password ;

    @FieldName(value = "city_id")
    private Integer cityId ;

    private String description ;

}
複製程式碼

它所對應的表結構如下:

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) DEFAULT NULL,
  `password` varchar(100) DEFAULT NULL,
  `description` varchar(100) DEFAULT NULL,
  `roleId` int(11) DEFAULT NULL COMMENT '角色ID',
  `city_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
)
複製程式碼

當需要查詢資料時:

動態代理的實際應用
動態代理的實際應用

便可以這樣訪問資料庫。


當需要更新資料時:

動態代理的實際應用
動態代理的實際應用

在初始化 DBHandle 時指定一個回撥介面(也就是這裡的 UserUpdateListener),便可以在修改資料的時候拿到本次修改的資料實體。

@Slf4j
public class UserUpdateListener implements DataChangeListener {
    @Override
    public void listener(Object obj) {
        log.info("user update data={}", obj.toString());
    }
}
複製程式碼

同時我們可以在控制檯看到資料修改時的回撥結果:

動態代理的實際應用

這樣就實現了文初所提到的功能,便可以實現一些資料變化後需要執行的業務邏輯。

實現

下面重點來看看這個功能的實現過程;其實通過生成 DBHandle(資料庫增刪改的介面)例項的 API 便可以看出些端倪。

DBHandle handle = (DBHandle) new HandleProxy(DBHandle.class).getInstance(new UserSaveListener());
複製程式碼

DBHandel 雖然是個介面,但是它並不是使用一個實現類來實現的,而是通過代理生成。

那通過代理生成比直接例項化實現類有啥好處呢?

舉個例子,比如現在你想買一個新手機。

動態代理的實際應用

第一種方式可以直接在官方旗艦店買一個標配的手機,沒有額外的東西只有一個手機。

當然你也可以在某些第三方經銷商那裡購買帶套餐的,比如套餐一在標配的基礎上多了保護殼、貼膜之類的附加屬性。

這個經銷商就類似於我們這裡的代理類,他可以在原有實現的基礎上新增一些東西,至於新增什麼全看你自己的需要了。

而之所以叫動態代理,也是因為這個代理類是在程式執行過程中動態建立的,在編譯過程中並不能確定這個類的全限定名。


下面來看看這個代理類是如何生成的:

動態代理的實際應用
主要利用 JDK 自帶的 API 實現的,具體引數可以直接參考官方文件: docs.oracle.com/javase/8/do…

總之這樣便可以建立一個 DBHandler 介面的代理物件,而真正的代理過程是在 InvocationHandler#invoke() 函式中實現的:

動態代理的實際應用

這裡的實現也是非常簡單,在實現完代理物件的業務邏輯後便回撥我們傳入的事件介面,其中的引數便是當前的資料庫 Model 實體物件。

不過需要注意的是,這個事件回撥和業務執行緒是同一個,所以寫在這裡的邏輯建議都為非同步(Hibernate 和 SQLAlchemy 都存在這個情況)。

總結

以上便是整個動態代理實現 ORM 監聽機制的全過程,其實可以看出並沒有它名稱那樣看起來高大上,當然本身實現也比較簡單。

同時也不止這一種實現方式,例如:

  • cglib
  • javassist
  • ASM

etc..

他們的具體實現及優劣就不在本文探討了,感興趣的後續我會將這個功能用這幾種方式實現一遍。

同時動態代理的應用也不止於此,比如:

  • RPC 中無感知的遠端呼叫。
  • Spring 中的 AOP、攔截器等。

後續會繼續完善這個 ORM 庫,甚至可以獨立出來作為一個小巧的資料庫工具也未嘗不可。

相關原始碼見此處: github.com/TogetherOS/…

你的點贊與分享是對我最大的支援

公眾號名片底部.jpg

相關文章