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