1.观察者模式
观察者模式,也被称之为发布订阅者模式,观察者模式关注的点事,当某一个对象被修改/做出某些反应/发布一个信息等,会自动通知依赖它的对象(订阅者)
观察者模式的三大核心是: 观察者,被观察者,订阅者,观察者需要绑定要通知的订阅者。并且要观察指定的主题
2.SpringFramework中设计的观察者模式
SpringFramework中,体现观察者模式的特性就是时间驱动和监听器,监听器 充当订阅者,监听特定的时间,事件源冲淡被观察者的主题,用来发布事件,IOC容器本身也是事件广播器,可以理解成观察者
- 事件源:发布事件的对象
- 事件:事件源发布的信息 / 作出的动作
- 广播器:事件真正广播给监听器的对象【即 ApplicationContext 】
- ApplicationContext 接口有实现 ApplicationEventPublisher 接口,具备事件广播器的发布事件的能力
- ApplicationEventMulticaster 组合了所有的监听器,具备事件广播器的广播事件的能力
- 监听器:监听事件的对象
也许这样理解起来会更容易一些:
3. 快速体会事件与监听器
下面咱先通过一个最简单的实例来体会 SpringFramework 中监听器的使用。
3.1 编写监听器
SpringFramework 中内置的监听器接口是 ApplicationListener ,它还带了一个泛型,代表要监听的具体事件:
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E event);
}
我们要自定义监听器,只需要实现这个 ApplicationListener 接口即可。
为快速体会事件和监听器的功能,下面咱先介绍两个事件:ContextRefreshedEvent 和 ContextClosedEvent ,它们分别代表容器刷新完毕和即将关闭。下面咱编写一个监听器,来监听 ContextRefreshedEvent 事件。
@Component
public class ContextRefreshedApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
System.out.println("ContextRefreshedApplicationListener监听到ContextRefreshedEvent事件!");
}
}
记得用 @Component 注解标注监听器哦,一会要进行包扫描的。监听器必须要注册到 SpringFramework 的 IOC 容器才可以生效。
3.2 编写启动类
写好监听器,就可以编写启动类,驱动 IOC 容器来测试效果了。这个时候可能有小伙伴有点懵:我去,我思想工作还没建设好呢,这就完事了?然而真的就是这么回事,下面代码一写你就恍然大悟了。
public class QuickstartListenerApplication {
public static void main(String[] args) throws Exception {
System.out.println("准备初始化IOC容器。。。");
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
"com.linkedbear.spring.event.a_quickstart");
System.out.println("IOC容器初始化完成。。。");
ctx.close();
System.out.println("IOC容器关闭。。。");
}
}
准备初始化IOC容器。。。 ContextRefreshedApplicationListener监听到ContextRefreshedEvent事件! IOC容器初始化完成。。。 IOC容器关闭。。。
3.3 注解式监听器
除了实现 ApplicationListener 接口之外,还可以使用注解的形式注册监听器。
使用注解式监听器,组件不再需要实现任何接口,而是直接在需要作出事件反应的方法上标注 @EventListener 注解即可:
@Component
public class ContextClosedApplicationListener {
@EventListener
public void onContextClosedEvent(ContextClosedEvent event) {
System.out.println("ContextClosedApplicationListener监听到ContextClosedEvent事件!");
}
}
重新运行 QuickstartListenerApplication 的 main 方法,控制台可以打印出 ContextClosedApplicationListener 监听事件的反应:
准备初始化IOC容器。。。
ContextRefreshedApplicationListener监听到ContextRefreshedEvent事件!
IOC容器初始化完成。。。
ContextClosedApplicationListener监听到ContextClosedEvent事件!
IOC容器关闭。。。
由这两种监听器的 Demo ,可以得出几个结论:
- ApplicationListener 会在容器初始化阶段就准备好,在容器销毁时一起销毁;
- ApplicationListener 也是 IOC 容器中的普通 Bean ;
- IOC 容器中有内置的一些事件供我们监听。
4. SpringFramework中的内置事件
在 SpringFramework 中,已经有事件的默认抽象,以及 4 个默认的内置事件了,下面咱了解一下它们。4.1 ApplicationEvent
很明显,它是事件模型的抽象,它是一个抽象类,里面也没有定义什么东西,只有事件发生时的时间戳。值得关注的是,它是继承自 jdk 原生的观察者模式的事件模型,并且把它声明为抽象类:
关于这个设计,它的文档注释就已经说明了: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
先看一眼源码,从这里面或许就能意识到什么:
public abstract class ApplicationContextEvent extends ApplicationEvent {
public ApplicationContextEvent(ApplicationContext source) {
super(source);
}
public final ApplicationContext getApplicationContext() {
return (ApplicationContext) getSource();
}
}
它在构造时,会把 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 :
/**
* 注册成功的事件
*/
public class RegisterSuccessEvent extends ApplicationEvent {
public RegisterSuccessEvent(Object source) {
super(source);
}
}
5.3 编写监听器
使用上述的两种方式,分别编写发送短信、发送邮件,和发送站内信的监听器:
@Component
public class SmsSenderListener implements ApplicationListener<RegisterSuccessEvent> {
@Override
public void onApplicationEvent(RegisterSuccessEvent event) {
System.out.println("监听到用户注册成功,发送短信。。。");
}
}
@Component
public class EmailSenderListener {
@EventListener
public void onRegisterSuccess(RegisterSuccessEvent event) {
System.out.println("监听到用户注册成功!发送邮件中。。。");
}
}
@Component
public class MessageSenderListener {
@EventListener
public void onRegisterSuccess(RegisterSuccessEvent event) {
System.out.println("监听到用户注册成功,发送站内信。。。");
}
}
5.4 编写注册逻辑业务层
只有事件和监听器还不够,还需要有一个事件源来持有事件发布器,在应用上下文中发布事件。
Service 层中,需要注入 ApplicationEventPublisher 来发布事件,此处选择使用回调注入的方式。
@Service
public class RegisterService implements ApplicationEventPublisherAware {
ApplicationEventPublisher publisher;
public void register(String username) {
// 用户注册的动作。。。
System.out.println(username + "注册成功。。。");
// 发布事件
publisher.publishEvent(new RegisterSuccessEvent(username));
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
}
5.5 编写测试启动类
测试代码都准备好了,下面咱就来写一把启动类,模拟一次用户注册。
public class RegisterEventApplication {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
"com.linkedbear.spring.event.b_registerevent");
RegisterService registerService = ctx.getBean(RegisterService.class);
registerService.register("张大三");
}
}
运行 main 方法,控制台打印出注册动作,以及监听器的触发反应:
张大三注册成功。。。
监听到用户注册成功,发送邮件中。。。
监听到用户注册成功,发送站内信。。。
监听到用户注册成功,发送短信。。。
由此又得出来另外一个结论:注解式监听器的触发时机比接口式监听器早。
5.6 调整监听器的触发顺序
如果业务需要调整,需要先发送站内信,后发送邮件,这个时候就需要配合另外一个注解了:@Order 。标注上这个注解后,默认的排序值为 Integer.MAX_VALUE ,代表最靠后。
按照这个规则,那咱在 MessageSenderListener 的 onRegisterSuccess 方法上标注 @Order(0) ,重新运行启动类的 main 方法,观察控制台的打印:
张大三注册成功。。。
监听到用户注册成功,发送站内信。。。
监听到用户注册成功,发送邮件中。。。
监听到用户注册成功,发送短信。。。
需求得以解决。不过这个时候我们再思考一个问题:如果不标注 @Order 注解,默认的顺序是多少呢?
尝试着把刚才的 @Order 注解中,value 改为 Integer.MAX_VALUE - 1 ,重新运行,发现运行结果还是像上面那样打印,证明默认的排序值是 Integer.MAX_VALUE 。