1.观察者模式

观察者模式,也被称之为发布订阅者模式,观察者模式关注的点事,当某一个对象被修改/做出某些反应/发布一个信息等,会自动通知依赖它的对象(订阅者)

观察者模式的三大核心是: 观察者,被观察者,订阅者,观察者需要绑定要通知的订阅者。并且要观察指定的主题

2.SpringFramework中设计的观察者模式

SpringFramework中,体现观察者模式的特性就是时间驱动和监听器,监听器 充当订阅者,监听特定的时间,事件源冲淡被观察者的主题,用来发布事件,IOC容器本身也是事件广播器,可以理解成观察者

  • 事件源:发布事件的对象
  • 事件:事件源发布的信息 / 作出的动作
  • 广播器:事件真正广播给监听器的对象【即 ApplicationContext
    • ApplicationContext 接口有实现 ApplicationEventPublisher 接口,具备事件广播器的发布事件的能力
    • ApplicationEventMulticaster 组合了所有的监听器,具备事件广播器的广播事件的能力
  • 监听器:监听事件的对象

也许这样理解起来会更容易一些:
image.png

3. 快速体会事件与监听器

下面咱先通过一个最简单的实例来体会 SpringFramework 中监听器的使用。

3.1 编写监听器

SpringFramework 中内置的监听器接口是 ApplicationListener ,它还带了一个泛型,代表要监听的具体事件:

  1. @FunctionalInterface
  2. public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
  3. void onApplicationEvent(E event);
  4. }

我们要自定义监听器,只需要实现这个 ApplicationListener 接口即可。
为快速体会事件和监听器的功能,下面咱先介绍两个事件:ContextRefreshedEvent 和 ContextClosedEvent ,它们分别代表容器刷新完毕即将关闭。下面咱编写一个监听器,来监听 ContextRefreshedEvent 事件。

  1. @Component
  2. public class ContextRefreshedApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
  3. @Override
  4. public void onApplicationEvent(ContextRefreshedEvent event) {
  5. System.out.println("ContextRefreshedApplicationListener监听到ContextRefreshedEvent事件!");
  6. }
  7. }

记得用 @Component 注解标注监听器哦,一会要进行包扫描的。监听器必须要注册到 SpringFramework 的 IOC 容器才可以生效。

3.2 编写启动类

写好监听器,就可以编写启动类,驱动 IOC 容器来测试效果了。这个时候可能有小伙伴有点懵:我去,我思想工作还没建设好呢,这就完事了?然而真的就是这么回事,下面代码一写你就恍然大悟了。

  1. public class QuickstartListenerApplication {
  2. public static void main(String[] args) throws Exception {
  3. System.out.println("准备初始化IOC容器。。。");
  4. AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
  5. "com.linkedbear.spring.event.a_quickstart");
  6. System.out.println("IOC容器初始化完成。。。");
  7. ctx.close();
  8. System.out.println("IOC容器关闭。。。");
  9. }
  10. }

准备初始化IOC容器。。。 ContextRefreshedApplicationListener监听到ContextRefreshedEvent事件! IOC容器初始化完成。。。 IOC容器关闭。。。

3.3 注解式监听器

除了实现 ApplicationListener 接口之外,还可以使用注解的形式注册监听器。
使用注解式监听器,组件不再需要实现任何接口,而是直接在需要作出事件反应的方法上标注 @EventListener 注解即可:

  1. @Component
  2. public class ContextClosedApplicationListener {
  3. @EventListener
  4. public void onContextClosedEvent(ContextClosedEvent event) {
  5. System.out.println("ContextClosedApplicationListener监听到ContextClosedEvent事件!");
  6. }
  7. }

重新运行 QuickstartListenerApplication 的 main 方法,控制台可以打印出 ContextClosedApplicationListener 监听事件的反应:

  1. 准备初始化IOC容器。。。
  2. ContextRefreshedApplicationListener监听到ContextRefreshedEvent事件!
  3. IOC容器初始化完成。。。
  4. ContextClosedApplicationListener监听到ContextClosedEvent事件!
  5. IOC容器关闭。。。

由这两种监听器的 Demo ,可以得出几个结论:

  • ApplicationListener 会在容器初始化阶段就准备好,在容器销毁时一起销毁;
  • ApplicationListener 也是 IOC 容器中的普通 Bean ;
  • IOC 容器中有内置的一些事件供我们监听。

    4. SpringFramework中的内置事件

    在 SpringFramework 中,已经有事件的默认抽象,以及 4 个默认的内置事件了,下面咱了解一下它们。

    4.1 ApplicationEvent

    很明显,它是事件模型的抽象,它是一个抽象类,里面也没有定义什么东西,只有事件发生时的时间戳。值得关注的是,它是继承自 jdk 原生的观察者模式的事件模型,并且把它声明为抽象类:
    1. public abstract class ApplicationEvent extends EventObject
    关于这个设计,它的文档注释就已经说明了:

    Class to be extended by all application events. Abstract as it doesn’t make sense for generic events to be published directly. 由所有应用程序事件扩展的类。它被设计为抽象的,因为直接发布一般事件没有意义

如果说只是有这么一个派生,那看上去没什么太大的意义,所以 SpringFramework 中又给这个 ApplicationEvent 进行了一次扩展。

4.2 ApplicationContextEvent

先看一眼源码,从这里面或许就能意识到什么:

  1. public abstract class ApplicationContextEvent extends ApplicationEvent {
  2. public ApplicationContextEvent(ApplicationContext source) {
  3. super(source);
  4. }
  5. public final ApplicationContext getApplicationContext() {
  6. return (ApplicationContext) getSource();
  7. }
  8. }

它在构造时,会把 IOC 容器一起传进去,这意味着事件发生时,可以通过监听器直接取到 ApplicationContext 而不需要做额外的操作,这才是 SpringFramework 中事件模型扩展最值得的地方。下面列举的几个内置的事件,都是基于这个 ApplicationContextEvent 扩展的。

4.3 ContextRefreshedEvent&ContextClosedEvent

这两个是一对,分别对应着 IOC 容器刷新完毕但尚未启动,以及 IOC 容器已经关闭但尚未销毁所有 Bean 。这个时机可能记起来有点小困难,小伙伴们可以不用记很多,只通过字面意思能知道就 OK ,至于这些事件触发的真正时机,在我的 SpringBoot 源码小册第 16 章中有提到,感兴趣的小伙伴可以去看一看。在后面的 IOC 原理篇中,这部分也会略有涉及。

4.4 ContextStartedEvent&ContextStoppedEvent

这一对跟上面的时机不太一样了。ContextRefreshedEvent 事件的触发是所有单实例 Bean 刚创建完成后,就发布的事件,此时那些实现了 Lifecycle 接口的 Bean 还没有被回调 start 方法。当这些 start 方法被调用后,ContextStartedEvent 才会被触发。同样的,ContextStoppedEvent 事件也是在 ContextClosedEvent 触发之后才会触发,此时单实例 Bean 还没有被销毁,要先把它们都停掉才可以释放资源,销毁 Bean 。

5. 自定义事件开发

上面咱了解了 SpringFramework 中内置的事件,如果我们想自己在合适的时机发布一些事件,让指定的监听器来以此作出反应,执行特定的逻辑,那就需要自定义事件了。下面咱模拟一个场景,来体会自定义事件的开发过程。
本节可能会使人产生不适感,请做好心理准备再继续往下阅读 -.-

5.1 场景概述

论坛应用,当新用户注册成功后,会同时发送短信、邮件、站内信,通知用户注册成功,并且发放积分。
在这个场景中,用户注册成功后,广播一个“用户注册成功”的事件,将用户信息带入事件广播出去,发送短信、邮件、站内信的监听器监听到注册成功的事件后,会分别执行不同形式的通知动作。

5.2 自定义用户注册成功事件

SpringFramework 中的自定义事件的方式就是通过继承 ApplicationEvent :

  1. /**
  2. * 注册成功的事件
  3. */
  4. public class RegisterSuccessEvent extends ApplicationEvent {
  5. public RegisterSuccessEvent(Object source) {
  6. super(source);
  7. }
  8. }

5.3 编写监听器

使用上述的两种方式,分别编写发送短信、发送邮件,和发送站内信的监听器:

  1. @Component
  2. public class SmsSenderListener implements ApplicationListener<RegisterSuccessEvent> {
  3. @Override
  4. public void onApplicationEvent(RegisterSuccessEvent event) {
  5. System.out.println("监听到用户注册成功,发送短信。。。");
  6. }
  7. }
  1. @Component
  2. public class EmailSenderListener {
  3. @EventListener
  4. public void onRegisterSuccess(RegisterSuccessEvent event) {
  5. System.out.println("监听到用户注册成功!发送邮件中。。。");
  6. }
  7. }
  1. @Component
  2. public class MessageSenderListener {
  3. @EventListener
  4. public void onRegisterSuccess(RegisterSuccessEvent event) {
  5. System.out.println("监听到用户注册成功,发送站内信。。。");
  6. }
  7. }

5.4 编写注册逻辑业务层

只有事件和监听器还不够,还需要有一个事件源来持有事件发布器,在应用上下文中发布事件。
Service 层中,需要注入 ApplicationEventPublisher 来发布事件,此处选择使用回调注入的方式。

  1. @Service
  2. public class RegisterService implements ApplicationEventPublisherAware {
  3. ApplicationEventPublisher publisher;
  4. public void register(String username) {
  5. // 用户注册的动作。。。
  6. System.out.println(username + "注册成功。。。");
  7. // 发布事件
  8. publisher.publishEvent(new RegisterSuccessEvent(username));
  9. }
  10. @Override
  11. public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
  12. this.publisher = publisher;
  13. }
  14. }

5.5 编写测试启动类

测试代码都准备好了,下面咱就来写一把启动类,模拟一次用户注册。

  1. public class RegisterEventApplication {
  2. public static void main(String[] args) throws Exception {
  3. AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
  4. "com.linkedbear.spring.event.b_registerevent");
  5. RegisterService registerService = ctx.getBean(RegisterService.class);
  6. registerService.register("张大三");
  7. }
  8. }

运行 main 方法,控制台打印出注册动作,以及监听器的触发反应:

  1. 张大三注册成功。。。
  2. 监听到用户注册成功,发送邮件中。。。
  3. 监听到用户注册成功,发送站内信。。。
  4. 监听到用户注册成功,发送短信。。。

由此又得出来另外一个结论:注解式监听器的触发时机比接口式监听器早

5.6 调整监听器的触发顺序

如果业务需要调整,需要先发送站内信,后发送邮件,这个时候就需要配合另外一个注解了:@Order 。标注上这个注解后,默认的排序值为 Integer.MAX_VALUE ,代表最靠后
按照这个规则,那咱在 MessageSenderListener 的 onRegisterSuccess 方法上标注 @Order(0) ,重新运行启动类的 main 方法,观察控制台的打印:

  1. 张大三注册成功。。。
  2. 监听到用户注册成功,发送站内信。。。
  3. 监听到用户注册成功,发送邮件中。。。
  4. 监听到用户注册成功,发送短信。。。

需求得以解决。不过这个时候我们再思考一个问题:如果不标注 @Order 注解,默认的顺序是多少呢?
尝试着把刚才的 @Order 注解中,value 改为 Integer.MAX_VALUE - 1 ,重新运行,发现运行结果还是像上面那样打印,证明默认的排序值是 Integer.MAX_VALUE