Spring帶泛型的ApplicationEvent無法監聽問題分析(轉載)

燃燃火火發表於2024-11-11

1 背景

在開發過程中,經常遇到傳送事件來通知其他模組進行相應的業務處理;筆者實用的是spring自帶的ApplicationEventPublisherEventListener進行事件的發收;
但是開發時遇到一個問題:
如果事件很多,但是事件模式都差不多,就需要定義很多事件類來分別表示各種事件,例如,我們進行資料同步,每同步一條資料都要傳送對應的事件,虛擬碼如下:

//事件類
class RegionEvent {
  private Region region;
  private OperationEnum operation;
}

class UserEvent {
  private User user;
  private OperationEnum operation;
}

//插入一個區域
regionDao.insert(Region region);
//傳送插入區域事件
publisher.publishEvent(new RegionEvent(region, INSERT));

//更新一個使用者
userDao.update(User user);
//傳送更新使用者事件
publisher.publishEvent(new UserEvent(user, UPDATE));

//區域事件監聽器
@EventListener
public void onRegionEvent(RegionEvent event) {
    log.info("receive event: {}", event);
}

//使用者事件監聽器
@EventListener
public void onUserEvent(UserEvent event) {
    log.info("receive event: {}", event);
}

此時,我們發現有太多冗餘的程式碼,因為每插入一種型別的資料,就要對應的建立一個和該型別相關的事件類;自然而然地,我們想到可以使用泛型來簡化以上邏輯。

1 泛型事件遇到的問題

我們定義一種泛型事件,來重新實現以上的邏輯,此時我們發現一個問題:傳送的事件根本監聽不到,虛擬碼如下:

class BaseEvent<T> {
  private T data;
  private OperationEnum operation;
}

//傳送插入區域事件
publisher.publishEvent(new BaseEvent<>(region, INSERT));
//傳送更新使用者事件
publisher.publishEvent(new BaseEvent<>(user, UPDATE));

//區域事件監聽器
@EventListener
public void onRegionEvent(BaseEvent<Region> event) {
    log.info("receive event: {}", event);
}

//使用者事件監聽器
@EventListener
public void onUserEvent(BaseEvent<User> event) {
    log.info("receive event: {}", event);
}

這是由於spring在解析事件型別時,並沒有對事件的泛型進行解析,導致在執行時所有publish的事件都被spring解析成了BaseEvent<?>事件,如果採用如下程式碼,則會監聽到所有事件:

@EventListener
public void onUserEvent(BaseEvent<Object> event) {
    log.info("receive event: {}", event);
}

@EventListener
public void onUserEvent(BaseEvent event) {
    log.info("receive event: {}", event);
}

2 解決方法

查閱了spring的文件後,發現spring已經考慮到這一點,官方文件原文如下:

In certain circumstances, this may become quite tedious if all events follow the same structure. In such a case, you can implement ResolvableTypeProvider to guide the framework beyond what the runtime environment provides. The following event shows how to do so:
大概翻譯一下:
在某些情況下,如果所有事件型別都遵循相同的結構,這會是特別噁心的一件事。在這種情況下,你可以透過實現ResolvableTypeProvider介面,在執行時基於環境提供的資訊來引導框架

我們基於spring提供的方法,對原有的泛型事件進行改造:

public class BaseEvent<T> implements ResolvableTypeProvider {
  private T data;
  private OperationEnum operation;

  @Override
  public ResolvableType getResolvableType() {
      return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forClass(getData().getClass()));
  }
}

此時,使用上文的監聽器就可以監聽到對應的事件了;

3 原理

事件監聽器和事件是透過事件型別進行匹配的,而事件型別的publish原始碼在AbstractApplicationContext類的
protected void publishEvent(Object event, @Nullable ResolvableType eventType)
方法中,如下:

        ApplicationEvent applicationEvent;
        
        if (event instanceof ApplicationEvent) {
            //對於繼承ApplicationEvent的事件,
            applicationEvent = (ApplicationEvent) event;
        }
        else {
            //對於非繼承ApplicationEvent的事件,包裝成PayloadApplicationEvent,
            //然後透過getResolvableType()獲取事件型別
            applicationEvent = new PayloadApplicationEvent<>(this, event);
            if (eventType == null) {
                eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
            }
        }

        // Multicast right now if possible - or lazily once the multicaster is initialized
        if (this.earlyApplicationEvents != null) {
            this.earlyApplicationEvents.add(applicationEvent);
        }
        else {
            getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
        }

然後進入multicastEvent(applicationEvent, eventType)方法:

    @Override
    public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
        //這裡對於ApplicationEvent的子類事件,進行解析事件型別
        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        Executor executor = getTaskExecutor();
        //根據上面解析到的eventType,獲取對應的監聽器,並依次執行回撥方法
        for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
            if (executor != null) {
                executor.execute(() -> invokeListener(listener, event));
            }
            else {
                invokeListener(listener, event);
            }
        }
    }

可以發現,關鍵在於如何解析事件型別,分別進入上文中resolveDefaultEventType()方法和getResolvableType()方法,可以看到解析事件型別的具體細節如下:

//針對PayloadApplicationEvent,透過下面的方法處理,可見
    @Override
    public ResolvableType getResolvableType() {
        return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(getPayload()));
    }
//對於繼承了ApplicationEvent的事件類
    private ResolvableType resolveDefaultEventType(ApplicationEvent event) {
        return ResolvableType.forInstance(event);
    }

上述兩個方法用於根據事件構造事件的ResolvableType,關鍵程式碼在ResolvableType.forInstance():

    public static ResolvableType forInstance(Object instance) {
        Assert.notNull(instance, "Instance must not be null");
        if (instance instanceof ResolvableTypeProvider) {
            ResolvableType type = ((ResolvableTypeProvider) instance).getResolvableType();
            if (type != null) {
                return type;
            }
        }
        return ResolvableType.forClass(instance.getClass());
    }

至此,可以看到,如果事件實現了ResolvableTypeProvider介面,則可以透過呼叫getResolvableType方法獲取事件的帶泛型型別,如果未實現該介面,則只能獲取事件的原始型別,效果如下:

未實現介面的情況下:

實現介面後:

作者:TinyThing
連結:https://www.jianshu.com/p/fd0c358176b9
來源:簡書

相關文章