動態(tài)代理的實際應(yīng)用

最近在用 Python 的 SQLAlchemy 庫時(一個類似于 Hibernate 的 ORM 框架),發(fā)現(xiàn)它的 Events 事件還挺好用。
簡單說就是當某張表的數(shù)據(jù)發(fā)生變化(曾、刪、改)時會有一個事件回調(diào),這樣一些埋點之類的需求都可以實現(xiàn)在這里,同時和業(yè)務(wù)代碼完全解耦,維護起來也很方便。
例如當訂單狀態(tài)發(fā)生變化需要發(fā)異步通知這樣的需求也可以利用這個實現(xiàn)。
根據(jù)我之前使用 Mybatis 的經(jīng)驗,好像沒怎么注意有這個功能,查閱了下發(fā)現(xiàn) Hibernate 是支持的,只是我用得也少,所以也沒怎么在意。
逐漸偏離主題。。。
說這些的主要原因是我打算為之前寫的 cicada (輕量的 http 框架)加一個數(shù)據(jù)庫操作包,也實現(xiàn)類似的功能。
示例最終的使用效果如下:
第一版本還比較粗糙,但功能都具備。

第一步:需要實現(xiàn)一個初始化接口,該接口會在應(yīng)用初始化的時候執(zhí)行。
緊接著我們需要定義一個 Model:
@Data@OriginName("user")@ToStringpublicclassUserextendsModel{@PrimaryIdprivateInteger id ;privateString name ;privateString password ;@FieldName(value ="city_id")privateInteger cityId ;privateString description ;}
它所對應(yīng)的表結(jié)構(gòu)如下:
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`))
當需要查詢數(shù)據(jù)時:


便可以這樣訪問數(shù)據(jù)庫。
當需要更新數(shù)據(jù)時:


在初始化 DBHandle 時指定一個回調(diào)接口(也就是這里的 UserUpdateListener),便可以在修改數(shù)據(jù)的時候拿到本次修改的數(shù)據(jù)實體。
@Slf4jpublicclassUserUpdateListenerimplementsDataChangeListener{@Overridepublicvoid listener(Object obj){log.info("user update data={}", obj.toString());}}
同時我們可以在控制臺看到數(shù)據(jù)修改時的回調(diào)結(jié)果:

這樣就實現(xiàn)了文初所提到的功能,便可以實現(xiàn)一些數(shù)據(jù)變化后需要執(zhí)行的業(yè)務(wù)邏輯。
實現(xiàn)下面重點來看看這個功能的實現(xiàn)過程;其實通過生成 DBHandle(數(shù)據(jù)庫增刪改的接口)實例的 API 便可以看出些端倪。
DBHandle handle =(DBHandle)newHandleProxy(DBHandle.class).getInstance(newUserSaveListener());
DBHandel 雖然是個接口,但是它并不是使用一個實現(xiàn)類來實現(xiàn)的,而是通過代理生成。
那通過代理生成比直接實例化實現(xiàn)類有啥好處呢?
舉個例子,比如現(xiàn)在你想買一個新手機。

第一種方式可以直接在官方旗艦店買一個標配的手機,沒有額外的東西只有一個手機。
當然你也可以在某些第三方經(jīng)銷商那里購買帶套餐的,比如 套餐一在標配的基礎(chǔ)上多了 保護殼、貼膜之類的附加屬性。
這個經(jīng)銷商就類似于我們這里的代理類,他可以在原有實現(xiàn)的基礎(chǔ)上新增一些東西,至于新增什么全看你自己的需要了。
而之所以叫動態(tài)代理,也是因為這個代理類是在程序運行過程中動態(tài)創(chuàng)建的,在編譯過程中并不能確定這個類的全限定名。
下面來看看這個代理類是如何生成的:

主要利用 JDK 自帶的 API 實現(xiàn)的,具體參數(shù)可以直接參考官方文檔:https://docs.oracle.com/javase/8/docs/technotes/guides/reflection/proxy.html
總之這樣便可以創(chuàng)建一個 DBHandler 接口的代理對象,而真正的代理過程是在 InvocationHandler#invoke() 函數(shù)中實現(xiàn)的:

這里的實現(xiàn)也是非常簡單,在實現(xiàn)完代理對象的業(yè)務(wù)邏輯后便回調(diào)我們傳入的事件接口,其中的參數(shù)便是當前的數(shù)據(jù)庫 Model 實體對象。
總結(jié)不過需要注意的是,這個事件回調(diào)和業(yè)務(wù)線程是同一個,所以寫在這里的邏輯建議都為異步(Hibernate 和 SQLAlchemy 都存在這個情況)。
以上便是整個動態(tài)代理實現(xiàn) ORM 監(jiān)聽機制的全過程,其實可以看出并沒有它名稱那樣看起來高大上,當然本身實現(xiàn)也比較簡單。
同時也不止這一種實現(xiàn)方式,例如:
cglib
javassist
ASM
etc..
他們的具體實現(xiàn)及優(yōu)劣就不在本文探討了,感興趣的后續(xù)我會將這個功能用這幾種方式實現(xiàn)一遍。
同時動態(tài)代理的應(yīng)用也不止于此,比如:
RPC?中無感知的遠程調(diào)用。Spring?中的?AOP、攔截器等。
后續(xù)會繼續(xù)完善這個 ORM 庫,甚至可以獨立出來作為一個小巧的數(shù)據(jù)庫工具也未嘗不可。
相關(guān)源碼見此處:https://github.com/TogetherOS/cicada
推薦閱讀:
喜歡我可以給我設(shè)為星標哦

好文章,我?在看?

