最近一直忙于业务开发实现,很少有机会做一些技术沉淀,这可能对一个技术工作者来说,未必是一件好事。之前就一直想写一篇关于spring事件驱动模型相关技术博客,主要还是想给自己做一个技术总结,同时也好和大家做一做分享交流。
好吧,下面直接进入正题:
在说spring事件编程模型之前,还是先看下一个实际开发场景:
这个模型可能大家都很熟悉,一般也就是一个正常订单业务,可能我下订单了,然后xxx一通乱操作,最后订单数据入库。这个是比较简单的核心业务了。那么现在问题来了:
问题一:
订单下完之后,需要基于订单信息给客户发一条短信推送
这时后一般的开发人员很容易想到如下模型,很简单也很容易实现:
继续看问题二:
订单下完之后,需要将订单信息推送给数据中心,做相关大数据分析,报表等,好继续加
很容易想到,后续场景越来越多,那么业务代码需要做的东西也就越来越多,简而易见,就是这个service的调用越来越多。这样功能是可以实现的,但是代码一点不优美,最主要的额问题是,业务增减或业务扩展,都需要去改我们的核心业务代码。那有什么好的方法去做相关改善呢,下面就好看看我们的主角:spring事件驱动模型了
首先可以看下如下业务模型:
在订单发送结束后,我们可以直接将我们的订单信息包装起来通过一个事件发布出去,最后需要消费的直接通过对应的listener去接收我们的事件,然后做对应相关的业务处理即可。这其实就类似于我们经常使用的mq,event其实就是我们的provider,listener就是我们的一个个consumer。使用这种模型的好处就是,在我们业务有相关拓展的时候,可以不用去改造我们的核心业务代码,只需要关系我们的listener即可,减少很多的开发测试成本。但是这种同样也会带来一些相关问题,比如我们的同步异步问题(下面会讲),事务问题(这块问题比较多,后面会单独写篇来介绍基于spring事件驱动模型编程的事务问题)。
我们先由浅入深,先来看一个简单的demo,先来了解下spring事件驱动模型是怎么用的。
(1)定义事件,很简单,继承ApplicationEvent 实现相关方法即可,message就是我需要发布的内容,可以根据实际业务自己定义。
public class SpringEvent extends ApplicationEvent {
@Getter
private String message;
/**
* Create a new ContextStartedEvent.
*
* @param source the {@code ApplicationContext} that the event is raised for
* (must not be {@code null})
*/
public SpringEvent(ApplicationContext source, String message) {
super(source);
this.message = message;
}
}
(2)定义监听,监听需要实现 ApplicationListener接口,并将我们的事件(SpringEvent)泛型进去,表示我们需要监听的事件是SpringEvent,该类需要交给spring管理,同事在有多个监听的情况下,支撑监听的先后执行顺序,@Order(10),数值越大,越先执行。
@Component
@Order(2)
public class SpringListener implements ApplicationListener<SpringEvent> {
@Async
@Override
public void onApplicationEvent(SpringEvent event) {
System.out.println(this.getClass() + String.format("收到事件,消息内容为[%s]",event.getMessage()));
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getClass() + "执行了监听");
}
}
(3)定义事件源,发布事件,整个spring只提供了一个唯一的事件源ApplicationContext,就是我们的上下文事件,所有想事件都需要直接或间接实现ApplicationContext接口。然后通过applicationContext.publishEvent去发布相关的事件
@Service
public class TestService {
@Autowired
private ApplicationContext applicationContext;
void test(String meseage){
applicationContext.publishEvent(new SpringEvent(applicationContext,meseage));
System.out.println("事件发布完了");
}
}
执行效果如下,我配置了两个监听,很明显监听都收到了事件,并做了相关操作:
上面我配置的是同步,其实spring事件驱动还可以支持异步,默认是同步执行,支持异步的话,需要配置两个东西:
(1)配置全局任务调度器和线程池
<context:component-scan base-package="com.baomw"/>
<!-- 任务调度器 -->
<task:scheduler id="scheduler" pool-size="5"/>
<!-- 任务执行器 -->
<task:executor id="payment-status-update-thread" pool-size="10"/>
<!--开启注解调度支持 @Async @Scheduled-->
<task:annotation-driven executor="payment-status-update-thread" scheduler="scheduler" proxy-target-class="true"/>
那么问题来了,spring底是怎么执行的?
首先我们来看下事件驱动模型相关类图
也比较简单,其实最核心的就是两个接口:ApplicationEventPublisher,SmartApplicationListener
但是真正将这两个关联起来的就需要靠我们的事件发布器(我这么命名的):ApplicationEventMulticaster
他在spring容器启动的时候初始化,可以参考:
org.springframework.context.support.AbstractApplicationContext#refresh
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
Initialize event multicaster for this context.
可以看到spring 是这么注释的。初始化所有的事件发布器到我们的上下文。
/**
* Name of the ApplicationEventMulticaster bean in the factory.
* If none is supplied, a default SimpleApplicationEventMulticaster is used.
* @see org.springframework.context.event.ApplicationEventMulticaster
* @see org.springframework.context.event.SimpleApplicationEventMulticaster
*/
public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";
核心实现如下上,首先会在beanFactory中去getbean,如果在配置信息找到一个beanName为:applicationEventMulticaster的配置,就会初始化全局applicationEventMulticaster,否则就会:实例化一个:SimpleApplicationEventMulticaster,这个类是spring中ApplicationEventMulticaster的唯一实现。
加载完applicationEventMulticaster之后就是注册我们的listener
这里面具体实现就不一一贴代码了,有兴趣的可以自行研究下,其实就是扫描所有的ListenerBean,如下,其实就是将所有的ListenerBean添加到一个HashSet里面去。
初始化完之后便是具体的发布执行了,下面来看看具体发布的源码:
/**
* Publish the given event to all listeners.
* @param event the event to publish (may be an {@link ApplicationEvent}
* or a payload object to be turned into a {@link PayloadApplicationEvent})
* @param eventType the resolved event type, if known
* @since 4.2
*/
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
Assert.notNull(event, "Event must not be null");
if (logger.isTraceEnabled()) {
logger.trace("Publishing event in " + getDisplayName() + ": " + event);
}
// Decorate event as an ApplicationEvent if necessary
ApplicationEvent applicationEvent;
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent) event;
}
else {
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);
}
// Publish event via parent context as well...
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
}
else {
this.parent.publishEvent(event);
}
}
}
这个是applicationContext发布事件的核心api,核心在这里,可以看到其实applicationContext就是通过我们的事件发布器去发布事件,前面已经看到了,在sping容器初始化的时候就已经初始化好了全局唯一发布器:SimpleApplicationEventMulticaster,
通过执行:org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType)方法来发布发布并执行事件,
看上面的方法,其实就很好理解了,无非就是获取当前事件的所有监听,然后去for循环所有的监听去依次执行:invokeListener监听方法而已。
这里需要注意一个问题,同步异步他其实是先去
Executor executor = getTaskExecutor();
去get一个线程池,如果我们有配置线程池的话这边就开异步线程去执行监听,否则就是同步执行了,这样就解决了相关同步异步的问题。
当然里面还有很多细节操作,比如怎么去实现我们order,怎么去根据注解去判定哪些是需要同步哪些是需要异步的,有兴趣的可以自己去研究一下,这边不做一一扩展了。
上述就是spring的事件驱动模型的一些基本使用及相关底层实现的一些核心知识点了,最后,在说下spring给我们预留的相关扩展事件,先看下官方给出的核心事件:
The following table describes the standard events that Spring provides:
其实这些事件贯穿整个spring生命周期,我们可以用不同的监听去监听对应阶段的时间拿到全局的applicationContext,然后做相关扩展操作:
以ContextRefreshedEvent为例:
org.springframework.context.support.AbstractApplicationContext#refresh
org.springframework.context.support.AbstractApplicationContext#finishRefresh
根据如上调用链,可以看到,在容器初始化完成之后,会发布一个ContextRefreshedEvent事件。
我这边监听了一个启动刷新结束后的事件,效果如下,当然能拿到全局applicationContext,那么后续就可以做很多相关操作了。
好了关于spring事件编程模型先讲到这,纯属个人理解,不足(错误)之处欢迎留言指正!