Spring中的事件监听机制

更新于 2019-12-03  约 11 分钟

1.Spring Event

  • 在设计模式中,观察者模式可以算得上是一种非常经典的行为设计模式,事件---事件发布者---事件监听者是事件驱动模型在设计层面的体现.
  • Spring容器中通过ApplicationEvent类和ApplicationListener接口来处理事件的.如果某个bean实现ApplicationListener接口并被部署到容器中,那么每次对应的ApplicationEvent被发布到容器中都会通知该bean,这是典型的观察者模式.
  • Spring的事件默认是同步的,即调用#publishEvent方法发布事件后,它会处于阻塞状态,直到#onApplicationEvent接受到事件并处理返回之后才继续执行下去,这种单线程同步的好处是可以进行事务管理.
  • 系统默认提供的容器事件的异步发布机制参数

image.png


2.Spring提供的事件驱动模型

  • 事件

    • 其继承自JDKEventObject,JDK要求所有的事件都继承它,并通过#resource得到事件源,我们的AWT事件体系也是继承自它.
    • 系统默认提供如下ApplicationEvent事件:

image.png

  • 目标(事件发布者)

    • 具体代表:ApplicationEventPublisherApplicationEventMulticaster,系统提供如下实现:

image.png

  • ApplicationContext接口继承了ApplicationEventPublisher,并在abstractApplicationContext#publishEvent方法实现具体代码,实际执行委托给ApplicationEventMulticaster#multicastEvent方法.
  • ApplicationContext#initApplicationEventMulticaster方法会自动到本地容器里找一个名为ApplicationEventMulticaster的实现,如果没有就new一个SimpleApplicationEventMulticaster.
  • 可以看到如果提供一个executor,它就可以异步支持发布事件,否则为同步发布.

image.png

  • 监听器

    • 具体代表:ApplicationListener

      • 其继承自JDKEventListener,JDK要求所有的监听器将继承它,比如我们的AWT事件体系也是继承自它.
      • ApplicationListener接口:其只提供了#onApplicationEvent方法,我们需要在该方法实现内部判断事件类型来处理,若想提供顺序触发监听器的语义,则可以使用另一个接口:SmartApplicationListener

image.png


3.Spring事件驱动案例

现假设一个用户注册的案例场景.用户注册后,系统需要给用户发送邮件告知用户注册是否成功,需要给用户初始化积分,后续可能会添加其他的操作,如再发一条手机短信等,希望程序具有拓展性符合开闭原则.

  • 如果不使用事件驱动,代码可能会像这个样子:
    image.png

要说代码有什么问题其实也不算,因为大多数人在开发时第一直觉都会这么写,写同步代码.但是这么写,实际上并不是特别符合隐含的设计需求,假设增加更多的注册项Service,我们需要修改#register方法,并让UserService注入对应的Service.而实际上register并不关心这些"额外"的操作,如何将这些代码抽取出去,这时可以考虑Event机制.

1.无序

  • 定义用户注册事件

    • ApplicationEvent是由Spring提供的所有Event类的基类,这里为了简单只传递name.

image.png

  • 定义用户注册服务(事件发布者)

    • 服务交给Spring容器管理.
    • ApplicationEventPublishAware是由Spring提供的用于Service注入ApplicationEventPublisher事件发布器的接口.使用这个接口,我们的Service就拥有发布事件的能力了.
    • 用户注册后,不再是显示调用其他的业务Service,而是发布一个用户注册事件

image.png

  • 定义邮件服务,积分服务,其他服务(事件订阅者)

    • 事件订阅者的服务同样需要托管于Spring容器
    • ApplicationListener<E extends ApplicationEvent>接口是Spring提供的事件订阅者必须实现的接口,我们一般把Service关心的事件作为泛型传入.
    • 事件处理:ApplicationEvent#getSource拿到事件的具体内容,本例中为name.

image.png
image.png


2.有序

  • 当发布多个事件的时候,他们的顺序是无序的.如果要控制顺序,则监听器Service需要实现Order接口或者使用SmartApplicationEventListener.
  • 通过Spring事件驱动模型,我们完成了注册服务和其他服务之间的解耦,这也是事件驱动的最大特性之一,若后续要新增其他操作,只需要添加相应的事件订阅者即可.

image.png

  • supportsEventType:用于指定支持的事件类型,只有支持的才调用#onApplicationEvent方法.
  • supportsSourceType:支持的目标类型,只有支持的才调用#onApplicationEvent方法.
  • getOrder:顺序越小优先级越高,监听器默认优先级为7.

4.Spring对Event的注解支持

  • 注解式的事件发布者:Spring4.2之后,ApplicationEventPublisher自动被注入到容器中,不再需要显示实现Aware接口

image.png

  • 注解式的事件订阅者:@EventListener注解完成了ApplicationListener<E extends ApplicationEvent>接口的使命.

image.png


5.Spring对异步事件机制的支持

  • java配置通过@EnableAsync模块注解开启异步支持,使用@Async注解对需要异步的监听器进行标注.

image.png


6.Spring对事件监听机制的分析

1.Spring事件监听模型

  • 事件(ApplicationEvent):继承JDKEventObjectSpring项目中可以继承ApplicationEvent来定义自己的事件
  • 事件发布者(Application):实现这个接口,就可以使得Spring组件有发布事件的能力,ApplicationContext实现了此接口,因此,发布事件的方式有如下几种:

    • 通过实现ApplicationEventPublisherAware接口,获取注入的ApplicationPublisher对象来进行事件发布.
    • 通过实现ApplicationContextAware接口,获取注入的ApplicationContext来进行事件发布.
    • 通过@autowired注解直接注入ApplicationEventPublisher或者ApplicationContext对象来调用AbstractApplicationContext#publishEvent来委托ApplicationEventMulticaster进行事件发布

image.png

2.Spring事件监听流程分析

  • 通过源码分析,在AbstractApplicationContext类中,定义了针对观察者的add,get,register等方法,通过这一系列的方法向ApplicationEventMulticaster类中维护listener集合Set.改Set存储了该发布者所有的Listener,所以ApplicationContext容器会将注入到Spring中的Listener注册到ApplicationEventMulticaster

image.png

  • 事件通过AbstractApplicationContext#publishEvent方法委托给AbstractApplicationEventMulticaster进行事件发布,其中ApplicationEventMulticaster会先尝试从ConfigurableListableBeanFactroy中加载配置文件的类,如果不存在就会默认new一个SimpleApplicationEventMulticaster.

image.png

  • 具体的事件发布会在AbstractApplicaitonEventMulticaster#multicastEvent中实现,实现流程为:先根据event获取Listener集合,在线程池不为空的情况下,异步发布特定类型的事件,否则同步发布.在#invokeListener方法中最后调用Listener#onApplicationEvnet方法实现了事件的发布.

image.png


7.总结

  • 以上就是关于Spring事件监听机制的分析,其本质上是观察者模式的实现.通过事件监听机制能够将我们代码逻辑进行解耦,提高代码的拓展性,实现开闭原则.
阅读 428更新于 2019-12-03

推荐阅读
目录