AVt天堂网 手机版,亚洲va久久久噜噜噜久久4399,天天综合亚洲色在线精品,亚洲一级Av无码毛片久久精品

當前位置:首頁 > 科技  > 軟件

Spring解決泛型擦除的思路不錯,現在它是我的了

來源: 責編: 時間:2024-01-15 09:19:21 207觀看
導讀你好呀,我是歪歪。Spring 的事件監聽機制,不知道你有沒有用過,實際開發過程中用來進行代碼解耦簡直不要太爽。但是我最近碰到了一個涉及到泛型的場景,常規套路下,在這個場景中使用該機制看起來會很傻,但是最終了解到 Spring

你好呀,我是歪歪。o4s28資訊網——每日最新資訊28at.com

Spring 的事件監聽機制,不知道你有沒有用過,實際開發過程中用來進行代碼解耦簡直不要太爽。o4s28資訊網——每日最新資訊28at.com

但是我最近碰到了一個涉及到泛型的場景,常規套路下,在這個場景中使用該機制看起來會很傻,但是最終了解到 Spring 有一個優雅的解決方案,然后去了解了一下,感覺有點意思。o4s28資訊網——每日最新資訊28at.com

和你一起盤一盤。o4s28資訊網——每日最新資訊28at.com

Demo

首先,第一步啥也別說,先搞一個 Demo 出來。o4s28資訊網——每日最新資訊28at.com

需求也很簡單,假設我們有一個 Person 表,每當 Person 表新增或者修改一條數據的時候,給指定服務同步一下。o4s28資訊網——每日最新資訊28at.com

偽代碼非常的簡單:o4s28資訊網——每日最新資訊28at.com

boolean success = addPerson(person)if(success){    //發送person,add代表新增    sendToServer(person,"add");}

這代碼能用,完全沒有任何問題。o4s28資訊網——每日最新資訊28at.com

但是,你仔細想,“發給指定服務同步一下”這樣的動作按理來說,不應該和用戶新增和更新的行為“耦合”在一起,他們應該是兩個獨立的邏輯。o4s28資訊網——每日最新資訊28at.com

所以從優雅實現的角度出發,我們可以用 Spring 的事件機制進行解耦。o4s28資訊網——每日最新資訊28at.com

比如改成這樣:o4s28資訊網——每日最新資訊28at.com

boolean success = addPerson(person)if(success){    publicAddPersonEvent(person,"add");}

addPerson 成功之后,直接發布一個事件出去,然后“發給指定服務同步一下”這件事情就可以放在事件監聽器去做。o4s28資訊網——每日最新資訊28at.com

對應的代碼也很簡單,新建一個 SpringBoot 工程。o4s28資訊網——每日最新資訊28at.com

首先我們先搞一個 Person 對象:o4s28資訊網——每日最新資訊28at.com

@Datapublic class Person {    private String name;    public Person(String name) {        this.name = name;    }}

由于我們還要告知是新增還是修改,所以還需要搞個對象封裝一層:o4s28資訊網——每日最新資訊28at.com

@Datapublic class PersonEvent {    private Person person;    private String addOrUpdate;    public PersonEvent(Person person, String addOrUpdate) {        this.person = person;        this.addOrUpdate = addOrUpdate;    }}

然后搞一個事件發布器:o4s28資訊網——每日最新資訊28at.com

@Slf4j@RestControllerpublic class TestController {    @Resource    private ApplicationContext applicationContext;    @GetMapping("/publishEvent")    public void publishEvent() {        applicationContext.publishEvent(new PersonEvent(new Person("why"), "add"));    }}

最后來一個監聽器:o4s28資訊網——每日最新資訊28at.com

@Slf4j@Componentpublic class EventListenerService {    @EventListener    public void handlePersonEvent(PersonEvent personEvent) {        log.info("監聽到PersonEvent: {}", personEvent);    }}

Demo 就算是齊活了,你把代碼粘過去,也用不了一分鐘吧。o4s28資訊網——每日最新資訊28at.com

啟動服務跑一把:o4s28資訊網——每日最新資訊28at.com

圖片圖片o4s28資訊網——每日最新資訊28at.com

看起來沒有任何毛病,在監聽器里面直接就監聽到了。o4s28資訊網——每日最新資訊28at.com

這個時候假設,我還有一個對象,叫做 Order,每當 Order 表新增或者修改一條數據的時候,也要給指定服務同步一下。o4s28資訊網——每日最新資訊28at.com

怎么辦?o4s28資訊網——每日最新資訊28at.com

這還不簡單?o4s28資訊網——每日最新資訊28at.com

照葫蘆畫瓢唄。o4s28資訊網——每日最新資訊28at.com

先來一個 Order 對象:o4s28資訊網——每日最新資訊28at.com

@Datapublic class Order {    private String orderName;    public Order(String orderName) {        this.orderName = orderName;    }}

再來一個 OrderEvent 封裝一層:o4s28資訊網——每日最新資訊28at.com

@Datapublic class OrderEvent {        private Order order;    private String addOrUpdate;    public OrderEvent(Order order, String addOrUpdate) {        this.order = order;        this.addOrUpdate = addOrUpdate;    }}

然后再發布一個對應的事件:o4s28資訊網——每日最新資訊28at.com

圖片圖片o4s28資訊網——每日最新資訊28at.com

新增一個對應的事件監聽:o4s28資訊網——每日最新資訊28at.com

圖片圖片o4s28資訊網——每日最新資訊28at.com

發起調用:o4s28資訊網——每日最新資訊28at.com

圖片圖片o4s28資訊網——每日最新資訊28at.com

完美,兩個事件都監聽到了。o4s28資訊網——每日最新資訊28at.com

那么問題又來了,假設我還有一個對象,叫做 Account,每當 Account 表新增或者修改一條數據的時候,也要給指定服務同步一下。o4s28資訊網——每日最新資訊28at.com

或者說,我有幾十張表,對應幾十個對象,都要做類似的同步。o4s28資訊網——每日最新資訊28at.com

請問閣下又該如何應對?o4s28資訊網——每日最新資訊28at.com

你當然可以按照前面處理 Order 的方式,繼續依葫蘆畫瓢。o4s28資訊網——每日最新資訊28at.com

但是這樣勢必會來帶的一個問題是對象的膨脹,你想啊,畢竟每一個對象都需要一個對應的 xxxxEvent 封裝對象。o4s28資訊網——每日最新資訊28at.com

這樣的代碼過于冗余,丑,不優雅。o4s28資訊網——每日最新資訊28at.com

怎么辦?o4s28資訊網——每日最新資訊28at.com

自然而然的我們能想到泛型,畢竟人家干這個事兒是專業的,放一個通配符,管你多少個對象,通通都是“T”,也就是這樣的:o4s28資訊網——每日最新資訊28at.com

@Dataclass BaseEvent<T> {    private T data;    private String addOrUpdate;    public BaseEvent(T data, String addOrUpdate) {        this.data = data;        this.addOrUpdate = addOrUpdate;    }    }

對應的事件發布的地方也可以用 BaseEvent 來代替:o4s28資訊網——每日最新資訊28at.com

圖片圖片o4s28資訊網——每日最新資訊28at.com

這樣用一個 BaseEvent就能代替無數的 xxxEvent,做到通用,這是它的好處。o4s28資訊網——每日最新資訊28at.com

同時對應的監聽器也需要修改:o4s28資訊網——每日最新資訊28at.com

圖片圖片o4s28資訊網——每日最新資訊28at.com

啟動服務,跑一把。o4s28資訊網——每日最新資訊28at.com

發起調用之后你會發現控制臺正常輸出:o4s28資訊網——每日最新資訊28at.com

圖片圖片o4s28資訊網——每日最新資訊28at.com

但是,注意我要說但是了。o4s28資訊網——每日最新資訊28at.com

但是監聽這一坨代碼我感覺不爽,全部都寫在一個方法里面了,需要用非常多的 if 分支去做判斷。o4s28資訊網——每日最新資訊28at.com

而且,假設某些對象在同步之前,還有一些個性化的加工需求,那么都會體現在這一坨代碼中,不夠優雅。o4s28資訊網——每日最新資訊28at.com

怎么辦呢?o4s28資訊網——每日最新資訊28at.com

很簡單,拆開監聽:o4s28資訊網——每日最新資訊28at.com

圖片圖片o4s28資訊網——每日最新資訊28at.com

但是再次重啟服務,發起調用你會發現:控制臺沒有輸出了?怎么回事,怎么監聽不到了呢?o4s28資訊網——每日最新資訊28at.com

圖片圖片o4s28資訊網——每日最新資訊28at.com

官網怎么說?

在 Spring 的官方文檔中,關于泛型類型的事件通知只有寥寥數語,但是提到了兩個解決方案:o4s28資訊網——每日最新資訊28at.com

https://docs.spring.io/spring-framework/reference/core/beans/context-introduction.html#context-functionality-events-genericso4s28資訊網——每日最新資訊28at.com

圖片圖片o4s28資訊網——每日最新資訊28at.com

首先官網給出了這樣的一個泛型對象:EntityCreatedEvento4s28資訊網——每日最新資訊28at.com

然后說比如我們要監聽 Person 這個對象創建時的事件,那么對應的監聽器代碼就是這樣的:o4s28資訊網——每日最新資訊28at.com

@EventListenerpublic void onPersonCreated(EntityCreatedEvent<Person> event) { // ...}

和我們 Demo 里面的代碼結構是一樣的。o4s28資訊網——每日最新資訊28at.com

那么怎么才能觸發這個監聽呢?o4s28資訊網——每日最新資訊28at.com

第一種方式是:o4s28資訊網——每日最新資訊28at.com

class PersonCreatedEvent extends EntityCreatedEvent<Person> { … }).

也就是給這個對象創造一個對應的 xxxCreatedEvent,然后去監聽這個 xxxCreatedEvent。o4s28資訊網——每日最新資訊28at.com

和我們前面提到的 xxxxEvent 封裝對象是一回事。o4s28資訊網——每日最新資訊28at.com

為什么我們必須要這樣做呢?o4s28資訊網——每日最新資訊28at.com

官網上提到了這幾個詞:o4s28資訊網——每日最新資訊28at.com

Due to type erasureo4s28資訊網——每日最新資訊28at.com

圖片圖片o4s28資訊網——每日最新資訊28at.com

type erasure,泛型擦除。o4s28資訊網——每日最新資訊28at.com

因為泛型擦除,所以導致直接監聽 EntityCreatedEvent事件是不生效的,因為在泛型擦除之后,EntityCreatedEvent變成了 EntityCreatedEvent<?>。o4s28資訊網——每日最新資訊28at.com

封裝一個對象繼承泛型對象,通過他們之間一一對應的關系從而繞開泛型擦除這個問題,這個方案確實是可以解決問題。o4s28資訊網——每日最新資訊28at.com

但是,前面說了,不夠優雅。o4s28資訊網——每日最新資訊28at.com

官網也覺得這個事情很傻:o4s28資訊網——每日最新資訊28at.com

圖片圖片o4s28資訊網——每日最新資訊28at.com

它怎么說的呢?o4s28資訊網——每日最新資訊28at.com

In certain circumstances, this may become quite tedious if all events follow the same structure.在某些情況下,如果所有事件都遵循相同的結構,這可能會變得相當 tedious。o4s28資訊網——每日最新資訊28at.com

好,那么 tedious,是什么意思?哪個同學舉手回答一下?o4s28資訊網——每日最新資訊28at.com

這是個四級詞匯,得認識,以后考試的時候要考:o4s28資訊網——每日最新資訊28at.com

圖片圖片o4s28資訊網——每日最新資訊28at.com

quite tedious,相當啰嗦。o4s28資訊網——每日最新資訊28at.com

我們都不希望自己的程序看起來是 tedious 的。o4s28資訊網——每日最新資訊28at.com

所以,官方給出了另外一個解決方案:ResolvableTypeProvider。o4s28資訊網——每日最新資訊28at.com

圖片圖片o4s28資訊網——每日最新資訊28at.com

我也不知道這是在干什么,反正我拿到了代碼樣例,那我們就白嫖一下嘛:o4s28資訊網——每日最新資訊28at.com

@Dataclass BaseEvent<T> implements ResolvableTypeProvider {    private T data;    private String addOrUpdate;    public BaseEvent(T data, String addOrUpdate) {        this.data = data;        this.addOrUpdate = addOrUpdate;    }    @Override    public ResolvableType getResolvableType() {        return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(getData()));    }}

再次啟動服務,你會發現,監聽器又好使了:o4s28資訊網——每日最新資訊28at.com

圖片圖片o4s28資訊網——每日最新資訊28at.com

那么問題又來了。o4s28資訊網——每日最新資訊28at.com

這是為什么呢?o4s28資訊網——每日最新資訊28at.com

為什么?

我也不知道為什么,但是我知道源碼之下無秘密。o4s28資訊網——每日最新資訊28at.com

所以,先打上斷點再說。o4s28資訊網——每日最新資訊28at.com

關于 @EventListener 注解的原理和源碼解析,我之前寫過一篇相關的文章:《扯下@EventListener這個注解的神秘面紗?!?span style="display:none">o4s28資訊網——每日最新資訊28at.com

有興趣的可以看看這篇文章,然后再試著按照文章中的方式去找對應的源碼。o4s28資訊網——每日最新資訊28at.com

我這篇文章就不去抽絲剝繭的一點點找源碼了,直接就是一個大力出奇跡。o4s28資訊網——每日最新資訊28at.com

因為我們已知是 ResolvableTypeProvider 這個接口在搞事情,所以我只需要看看這個接口在代碼中被使用的地方有哪些:o4s28資訊網——每日最新資訊28at.com

圖片圖片o4s28資訊網——每日最新資訊28at.com

除去一些注釋和包導入的地方,整個項目中只有 ResolvableType 和 MultipartHttpMessageWriter 這個兩個中用到了。o4s28資訊網——每日最新資訊28at.com

直覺告訴我,應該是在 ResolvableType 用到的地方打斷點,因為另外一個類看起來是 Http 相關的,和我的 Demo 沒啥關系。o4s28資訊網——每日最新資訊28at.com

所以我直接在這里打上斷點,然后發起調用,程序果然就停在了斷點處:o4s28資訊網——每日最新資訊28at.com

org.springframework.core.ResolvableType#forInstanceo4s28資訊網——每日最新資訊28at.com

圖片圖片o4s28資訊網——每日最新資訊28at.com

我們觀察一下,發現這幾行代碼核心就干一個事兒:判斷 instance 是不是 ResolvableTypeProvider 的子類。o4s28資訊網——每日最新資訊28at.com

如果是則返回一個 type,如果不是則返回 forClass(instance.getClass())。o4s28資訊網——每日最新資訊28at.com

通過 Debug 我們發現 instance 是 BaseEvent:o4s28資訊網——每日最新資訊28at.com

圖片圖片o4s28資訊網——每日最新資訊28at.com

巧了,這就是 ResolvableTypeProvider 的子類,所以返回的 type 是這樣式兒的:o4s28資訊網——每日最新資訊28at.com

com.example.elasticjobtest.BaseEvent<com.example.elasticjobtest.Person>o4s28資訊網——每日最新資訊28at.com

圖片圖片o4s28資訊網——每日最新資訊28at.com

是帶具體的類型的,而這個類型就是通過 getResolvableType 方法拿到的。o4s28資訊網——每日最新資訊28at.com

前面我們在實現 ResolvableTypeProvider 的時候,就重寫了 getResolvableType 方法,調用了 ResolvableType.forClassWithGenerics,然后用 data 對應的真正的 T 對象實例的類型,作為返回值,這樣泛型對應的真正的對象類型,就在運行期被動態的獲取到了,從而解決了編譯階段泛型擦除的問題。o4s28資訊網——每日最新資訊28at.com

如果沒有實現 ResolvableTypeProvider 接口,那么這個方法返回的就是 BaseEvent<?>:o4s28資訊網——每日最新資訊28at.com

com.example.elasticjobtest.BaseEvent<?>o4s28資訊網——每日最新資訊28at.com

圖片圖片o4s28資訊網——每日最新資訊28at.com

看到這里你也就猜到個七七八八了。o4s28資訊網——每日最新資訊28at.com

都已經拿到具體的泛型對象了,后面再發起對應的事件監聽,那不是順理成章的事情嗎?o4s28資訊網——每日最新資訊28at.com

好,現在你在第一個斷點處就收獲到了一個這么關鍵的信息,接下來怎么辦呢?o4s28資訊網——每日最新資訊28at.com

接著斷點處往下調試,然后把整個鏈路都梳理清楚唄。o4s28資訊網——每日最新資訊28at.com

再往下走,你會來到這個地方:o4s28資訊網——每日最新資訊28at.com

org.springframework.context.event.AbstractApplicationEventMulticaster#getApplicationListenerso4s28資訊網——每日最新資訊28at.com

圖片圖片o4s28資訊網——每日最新資訊28at.com

從 cache 里面獲取到了一個 null。o4s28資訊網——每日最新資訊28at.com

因為這個緩存里面放的就是在項目啟動過程中已經觸發過的框架自帶的 listener 對象:o4s28資訊網——每日最新資訊28at.com

圖片圖片o4s28資訊網——每日最新資訊28at.com

調用的時候,如果能從緩存中拿到對應的 listener,則直接返回。而我們 Demo 中的自定義 listener 是第一次觸發,所以肯定是沒有的。o4s28資訊網——每日最新資訊28at.com

因此關鍵邏輯就這個方法的最后一行:retrieveApplicationListeners 方法里面o4s28資訊網——每日最新資訊28at.com

org.springframework.context.event.AbstractApplicationEventMulticaster#retrieveApplicationListenerso4s28資訊網——每日最新資訊28at.com

圖片圖片o4s28資訊網——每日最新資訊28at.com

這個地方再往下寫,就是我前面我提到的這篇文章中我寫過的內容了《扯下@EventListener這個注解的神秘面紗。》。o4s28資訊網——每日最新資訊28at.com

和泛型擦除的關系已經不大了,我就不再寫一次了。o4s28資訊網——每日最新資訊28at.com

只是給大家看一下這個方法在我們的 Demo 中,最終返回的 allListeners 就是我們自定義的這個事件監聽器:o4s28資訊網——每日最新資訊28at.com

com.example.elasticjobtest.EventListenerService#handlePersonEvento4s28資訊網——每日最新資訊28at.com

圖片圖片o4s28資訊網——每日最新資訊28at.com

為什么是這個?o4s28資訊網——每日最新資訊28at.com

因為我當前發布的事件的主角就是 Person 對象:o4s28資訊網——每日最新資訊28at.com

圖片圖片o4s28資訊網——每日最新資訊28at.com

同理,當 Order 對象的事件過來的時候,這里肯定就是對應的 handleOrderEvent 方法:o4s28資訊網——每日最新資訊28at.com

圖片圖片o4s28資訊網——每日最新資訊28at.com

如果我們把 BaseEvent 的 ResolvableTypeProvider 接口拿掉,那么你再看對應的 allListeners,你就會發現找不到我們對應的自定義 Listener 了:o4s28資訊網——每日最新資訊28at.com

圖片圖片o4s28資訊網——每日最新資訊28at.com

為什么?o4s28資訊網——每日最新資訊28at.com

因為當前事件對應的 ResolvableType 是這樣的:o4s28資訊網——每日最新資訊28at.com

org.springframework.context.PayloadApplicationEvent<com.example.elasticjobtest.BaseEvent<?>>o4s28資訊網——每日最新資訊28at.com

圖片圖片o4s28資訊網——每日最新資訊28at.com

而我們并沒有自定義一個這樣的 Listener:o4s28資訊網——每日最新資訊28at.com

@EventListenerpublic void handleAllEvent(BaseEvent<?> orderEvent) {    log.info("監聽到Event: {}", orderEvent);}

所以,這個事件發布了,但是沒有對應的消費。o4s28資訊網——每日最新資訊28at.com

大概就是這么個意思。o4s28資訊網——每日最新資訊28at.com

核心邏輯就在 ResolvableTypeProvider 接口里面,重寫了 getResolvableType 方法,在運行期動態的獲取泛型對應的真正的對象類型,從而解決了編譯階段泛型擦除的問題。o4s28資訊網——每日最新資訊28at.com

很好,現在摸清楚了,是個很簡單的思路,之前是 Spring 的,現在它是我的了。o4s28資訊網——每日最新資訊28at.com

為什么需要發布訂閱模式 ?

既然寫到 Spring 的事件通知機制了,那么就順便聊聊這個發布訂閱模式。o4s28資訊網——每日最新資訊28at.com

也許在看的過程中,你會冒出這樣一個問題:為什么要搞這么麻煩?把這些事件監聽的業務邏輯直接寫在對應的數據庫操作語句之后不行么?o4s28資訊網——每日最新資訊28at.com

要回答這個問題,我們可以先總結一下事件通知機制的使用場景。o4s28資訊網——每日最新資訊28at.com

  1. 數據變化之后同步清除緩存,這是一種簡單可靠的緩存更新方式。只有在清除失敗,或者數據庫主從同步間隙被臟讀才有可能出現緩存臟數據,概率比較小,一般業務上也是可以接受的。
  2. 通過某種方式告訴下游系統數據變化,比如往消息隊列里面扔消息。
  3. 數據的統計、監控、異步觸發等場景。當然這動作似乎用 AOP 也可以做,但是實際上在某些業務場景下,做切面統計,反而沒有通過發布訂閱機制來得直接,靈活度也更好。

除了上面這些外,肯定還有一些其他的場景,但是這些場景都有一個共同點:與核心業務關系不大,但是又具備一定的普適性。o4s28資訊網——每日最新資訊28at.com

比如完成用戶注冊之后給用戶發一個短信,或者發個郵件啥的。這個事情用發布訂閱機制來做是再合適不過的了。o4s28資訊網——每日最新資訊28at.com

編碼過程中牢記單一職責原則,要知道一個類該干什么不該干什么,這是面向對象編程 的關鍵點之一。o4s28資訊網——每日最新資訊28at.com

當你一個類中注入了大量的 Service 的時候,你就要考慮考慮,是不是有什么做的不合適的地方了,是不是有些 Service 其實不應該注入進來的。o4s28資訊網——每日最新資訊28at.com

是不是該用用發布訂閱了?o4s28資訊網——每日最新資訊28at.com

另外,當你的項目中真的出現了文章最開始說的,各種各樣的 xxxEvent 事件對應的封裝的時候,任何一個來開發的人都覺得這樣寫是不是有點冗余的時候,你就應該考慮一下是不是有更加優雅的解決方案。o4s28資訊網——每日最新資訊28at.com

假設這個方案由于某些原因不能使用或者不敢使用是一回事。o4s28資訊網——每日最新資訊28at.com

但是知不知道這個方案,是另一回事。o4s28資訊網——每日最新資訊28at.com

好啦,本文的技術部分就到這里了。o4s28資訊網——每日最新資訊28at.com

本文鏈接:http://www.tebozhan.com/showinfo-26-60912-0.htmlSpring解決泛型擦除的思路不錯,現在它是我的了

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: Swift 數組、字典和集合

下一篇: Java、Spring和Dubbo三種SPI機制,到底誰更好?

標簽:
  • 熱門焦點
Top