美文网首页
Spring如何使用带泛型的事件

Spring如何使用带泛型的事件

作者: TinyThing | 来源:发表于2020-03-11 15:26 被阅读0次

0x1 背景

在开发过程中,经常遇到发送事件来通知其他模块进行相应的业务处理;笔者实用的是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);
}

此时,我们发现有太多冗余的代码,因为每插入一种类型的数据,就要对应的建立一个和该类型相关的事件类;自然而然地,我们想到可以使用泛型来简化以上逻辑。

0x1 泛型事件遇到的问题

我们定义一种泛型事件,来重新实现以上的逻辑,此时我们发现一个问题:发送的事件根本监听不到,伪代码如下:

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);
}

0x2 解决方法

查阅了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()));
  }
}

此时,使用上文的监听器就可以监听到对应的事件了;

0x3 原理

事件监听器和事件是通过事件类型进行匹配的,而事件类型的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方法获取事件的带泛型类型,如果未实现该接口,则只能获取事件的原始类型,效果如下:

未实现接口的情况下:


image.png

实现接口后:


image.png

相关文章

  • Spring如何使用带泛型的事件

    0x1 背景 在开发过程中,经常遇到发送事件来通知其他模块进行相应的业务处理;笔者实用的是spring自带的App...

  • Java泛型

    本文介绍的知识点 泛型是什么? 泛型的使用在反射中使用泛型在集合类中使用泛型 关于泛型擦除如何理解?如何避免泛型擦...

  • Objective-C 泛型 协变 逆变

    为什么要使用泛型 如何使用泛型 限制泛型 协变 逆变 为什么要使用泛型 在使用NSArray, NSSet, NS...

  • Dart匿名函数+泛型

    匿名函数使用 无参数的匿名函数 带参数的匿名函数 函数体只有一行时,简写 泛型的基本使用 泛型集合 泛型类 泛型方法

  • Java泛型详解

    一,打破砂锅问到底 泛型存在的意义?泛型类,泛型接口,泛型方法如何定义?如何限定类型变量?泛型中使用的约束和局限性...

  • java入门学习提升第十五篇:Java泛型再探—泛型通配符及上下

    上篇文章中介绍了泛型是什么,为什么要使用泛型以及如何使用泛型,相信大家对泛型有了一个基本的了解,本篇将继续讲解泛型...

  • 泛型

    泛型的使用 jdk 5.0新增的特性 在集合中使用泛型 ① 集合接口或集合类在jdk5.0时都修改为带泛型的结构。...

  • Java-泛型

    - 1.泛型定义 - 2.泛型使用 - 3.泛型上下边界 - 4.JVM如何实现的泛型?   1、定义 Jdk1....

  • Java-API-集合框架(三)-泛型

    泛型的由来和基本使用 泛型的擦除 泛型类的使用 泛型方法的使用 泛型接口 泛型通配符(?) 通配符? 在api中的...

  • java 泛型解析

    Java 泛型 1、泛型的精髓是什么 2、泛型方法如何使用 概述: 泛型在java中具有重要地位,在面向对象编程模...

网友评论

      本文标题:Spring如何使用带泛型的事件

      本文链接:https://www.haomeiwen.com/subject/alfcjhtx.html