前言
前两篇博客【观察者设计模式的演进】和【JDK观察者模式探究】深度讲了观察者模式的实现和作用,相信很多同学都知道SpringBoot是基于Spring Framework的一套东西做出来的。之所以SpringBoot能够做到非常简便的启动,很大一部分功劳应该属于Spring Framework中提供的Listener-Event功能。所以这篇博客就是要看下Spring的Listener-Event到底是有什么功能,以及它在SpringBoot的启动中扮演了什么角色。更多Spring内容进入【Spring解读系列目录】。
Spring Events
Spring当中的事件通过org.springframework.context.ApplicationEvent
实例来表示。这个抽象类继承扩展了java.util.EventObject
,通过使用EventObject
中的getSource()
方法,可以很容易地获得所发生的给定事件的对象。SpringBoot中事件大致有两种类型:
第一种:与ApplicationContext相关联
所有这种类型的事件都继承自org.springframework.context.event.ApplicationContextEvent
类。凡是继承了ApplicationContextEvent
类都可以被应用于由org.springframework.context.ApplicationContext
引发的事件(简言之,就是那些构造函数传入的是ApplicationContext
类型的参数)。通过这种方式就可以直接通过应用程序上下文的生命周期来得到所发生的事件。本质上来说SpringBoot一共有四个分支事件。
• ContextStartedEvent:在上下文启动时被启动。
• ContextStoppedEvent:当上下文停止时启动。
• ContextRefreshedEvent:当上下文被刷新时被启动。
• ContextClosedEvent:当上下文被关闭时被启动。
第二种:与request 请求相关联
由org.springframework.web.context.support.RequestHandledEvent
实例来表示,当在ApplicationContext
中处理请求时,这些实例就会被启动。
本篇将会主要对与ApplicationContext相关联的应用事件进行一个举例说明。
事件的定义
之前的例子里,我们举例开花这个事件,其实可以抽象出来作为一个事件类,所有Flower能够表示的行为都实现这个事件类。因为Flower不仅可以开花,还可以凋零,还可以生虫等等。而两个主人翁则对不同的事件有不同的反应。那么SpringBoot中就是对这些事件做了一个抽象,凡是事件都必须继承自ApplicationEvent
。
public abstract class ApplicationEvent extends EventObject {
private static final long serialVersionUID = 7099057708183571937L;
private final long timestamp;
public ApplicationEvent(Object source) {
super(source);
this.timestamp = System.currentTimeMillis();
}
public final long getTimestamp() {
return this.timestamp;
}
}
从源码来看这个类需要接收一个参数Object source
,这个参数就是所谓的事件源。何为事件源呢?就是发生事件的东西,也就是我们上述的Flower。我们可以通过其父类EventObject
提供的getSource()
方法去获取这个数据源。
public class EventObject implements java.io.Serializable {
/**注:这个类是JDK提供的**/
private static final long serialVersionUID = 5516075349620653480L;
protected transient Object source;
public EventObject(Object source) {
if (source == null)
throw new IllegalArgumentException("null source");
this.source = source;
}
//这个方法就是上面Spring Events标题里说的getSource()方法
public Object getSource() {
return source;
}
public String toString() {
return getClass().getName() + "[source=" + source + "]";
}
}
SpringBoot启动事件的应用
根据上面说的,SpringBoot的上下文事件有四个,这四个的事件源自然都是ApplicationContext
类以及其子类。为了说明SpringBoot事件是如何起作用的,将会写个例子给SpringBoot添加一个新的启动事件。在扩展之前我们先看下是怎么用的,以ContextStartedEvent
为例。首先我们要添加一个监听器SpringStartListener
实现ApplicationListener<ContextStartedEvent>
,加上应该监听的泛型ContextStartedEvent
。如果一切正常,当调用到Spring的start()方法的时候,实现方法就应该进行执行。
SpringStartListener类:
@Repository
public class SpringStartListener implements ApplicationListener<ContextStartedEvent> {
@Override
public void onApplicationEvent(ContextStartedEvent event) {
System.out.println("My listener"); //事件的处理逻辑
}
}
测试类:
public class SpringEventTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext anno=new AnnotationConfigApplicationContext(AppConfig.class);
anno.start(); //此时触发ContextStartedEvent事件
}
}
输出结果:
My listener
那么基于上面的结果,可以得到这样一个结论。SpringBoot在启动的时候会一直监听start()
,一旦发现运行就立刻通知监听器执行应该执行的动作,这个事件就叫做ContextStartedEvent
。当外部添加一个Listener监听start()
方法的时候,start()
就被两个监听器监听了,然后同时执行Spring的ContextStartedEvent
事件和我们扩展的ContextStartedEvent
事件。
SpringBoot启动事件的扩展
既然已经分析出SpringBoot的事件触发机制,那么就可以在这个基础上扩展一个事件出来。那么就添加第五个事件ContextMyEvent
,这个事件类当然也必须继承自ApplicationEvent
。做了事件以后就要添加一个监听器SpringMyListener
,实现ApplicationListener
然后在泛型里配上我们自己写的事件类ContextMyEvent
。完成这些以后,只需要最后一步:触发。这个触发的步骤需要用到ApplicationContext.publishEvent()
对我们生成的事件进行发布。为了发布这个事件,还需要添加一个类EventTrigger
操作ApplicationContext
。
ContextMyEvent:
public class ContextMyEvent extends ApplicationEvent {
private String content; //设置内容
public ContextMyEvent(Object source) { //设置数据源
super(source);
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
SpringMyListener:
@Repository
public class SpringMyListener implements ApplicationListener<ContextMyEvent> {
@Override
public void onApplicationEvent(ContextMyEvent event) {
//做一个简单的事件处理逻辑,区分content输出
if (event.getContent().equals("name"))
System.out.println("SpringMyListener --- fifth listener");
else
System.out.println("SpringMyListener --- fifth listener without name");
}
}
EventTrigger:
@Repository
public class EventTrigger {
ApplicationContext applicationContext;
@Autowired
public EventTrigger(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
//当调用的needContent()方法的时候会发布一个ContextMyEvent事件
public void needContent(){
//发布一个事件,并且传递事件源
ContextMyEvent event= new ContextMyEvent(applicationContext);
event.setContent("name"); //设置内容
applicationContext.publishEvent(event); //发布事件
}
}
测试类:
public class SpringEventTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext anno=new AnnotationConfigApplicationContext(AppConfig.class);
anno.getBean(EventTrigger.class).needContent(); //设置ContextMyEvent到Spring容器中
anno.start();
}
}
打印输出:
SpringMyListener --- fifth listener
总结
通过上面的讲解,我们知道SpringBoot针对SpringMVC的一系列启动方法进行监听,并且根据不同的方法的启动做不同的事件处理。比如当refresh()
方法调用时,SpringBoot就会做各种的配置,这一系列的行为就是所谓的SpringBoot自动配置。那么其实还留了一个小问题:我们自己写的观察者事件模型总有一个Listener注入的过程,但是Spring的例子里似乎哪里都没有这个添加的过程,这是为什么呢?
其实SpringBoot的过程里也是有的只是Spring做个更加复杂的封装。其内部针对不同的事件做了一个事件委派器ApplicationEventMulticaster
,用一张图来说就是下面的模型。至于SpringBoot源码里面到底是如何实施的观察者事件模型,笔者会在下一篇开始进行讲解。