spring 事件处理模型/观察者
Spring的事件(Application Event)为Bean与Bean之间的消息通信提供了支持,当一个Bean处理完一个任务之后,希望另外一个Bean知道并且能做及时的处理,这时我们需要让另外一个bean监听当前Bean所发送的事件。个人认为主要优点是降低耦合。Spring事件需要遵循如下流程
- 自定义事件,继承ApplicationEvent
- 自定义事件监听器,实现ApplicationListener
- 使用容器发布事件
- 消息对列
简单理解就是为系统业务逻辑之间进行了解耦,事件发布者并不需要考虑谁去监听,监听具体的实现内容是什么,发布者的工作只是为了发布事件而已。
例如:用户注册的例子:
用户注册成功后,需要做很多事情:
- 增加积分
- 发送确认邮件
- 如果式游戏用户,还需要赠送游戏大礼包
- 建立索引用户数据
- …
解决的方法是,增加了一个Listener来解耦UserService和其他服务,即注册成功后,只需要通知相关的监听器,不需要关系它们如何处理。增删功能非常容易。
一. 自定义事件
@Data
public class DemoEvent extends ApplicationEvent {
private String text;
/**
* Create a new ApplicationEvent.
*
* @param source 发生事件的对象
*/
public DemoEvent(Object source, String text) {
super(source);
this.text = text;
}
}
定义了一个事件DemoEvent继承了ApplicationEvent,继承后必须重载构造函数,构造函数的参数可以任意指定,其中source参数指的是发生事件的对象,一般我们在发布事件时使用的是this关键字代替本类对象。
二.事件监听
在Spring内部中有多种方式实现监听如:
- @EventListener注解
- 实现ApplicationListener泛型接口
- 实现SmartApplicationListener接口,有序监听器
2.1 @EventListener注解
/**
* 注册监听实现方法
* @param DemoEvent 用户注册事件
*/
@EventListener
public void register(DemoEvent demoEvent)
{
//获取信息
String text = demoEvent.getText();
//../省略逻辑
}
监听类被Spring管理即可,注册监听实现方法上添加@EventListener注解,该注解会根据方法内配置的事件完成监听。使用注解式,可以监听统一事件的不同实现,只需要再方法上加注解,节省代码。
2.2 实现ApplicationListener泛型接口
Component
public class DemoEventListener implements ApplicationListener<DemoEventListener>
{
/**
* 实现监听
* @param userRegisterEvent
*/
@Override
public void onApplicationEvent(DemoEvent demoEvent) {
//获取注册用户对象
String text = demoEvent.getText();
//../省略逻辑
}
}
ApplicationListener 源码如下:
package org.springframework.context;
import java.util.EventListener;
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E var1);
}
使用@Component注解来声明该监听需要被Spring注入管理,当有DemoEvent事件发布时监听程序会自动调用onApplicationEvent方法并且将DemoEvent对象作为参数传递。这样的监听器是无序的
2.3 定义有序的监听器
方法一: @EventListener + @Order
方法二:实现SmartApplicationListener接口
package org.springframework.context.event;
public interface SmartApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {
// 如果实现支持该事件类型 那么返回true
boolean supportsEventType(Class<? extends ApplicationEvent> var1);
default boolean supportsSourceType(@Nullable Class<?> sourceType) {
return true;
}
//顺序,即监听器执行的顺序,值越小优先级越高
default int getOrder() {
return 2147483647;
}
示例一:
@Component
public class syncKeywordDeleteListener implements SmartApplicationListener {
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> aClass) {
return KeywordDeleteEvent.class == aClass;
}
@Override
public int getOrder() {
return 1;
}
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
//逻辑代码
System.out.println("hello");
}
}
//-------------------
@Component
public class DeleteKeywordsLitener implements SmartApplicationListener {
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> aClass) {
return KeywordDeleteEvent.class == aClass;
}
//执行顺序二
@Override
public int getOrder() {
return 2;
}
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
System.out.println("hello,顺序二");
}
示例二:
@Component
public class KeywordDeleteEventListener {
/**
* @param event event
*/
@EventListener
@Order(1)
public void syncKeywordDelete(KeywordDeleteEvent event) throws NoSuchMethodException {
String name = Thread.currentThread().getStackTrace()[1].getMethodName();
Method method = this.getClass().getDeclaredMethod(name, KeywordDeleteEvent.class);
Integer order = method.getAnnotation(Order.class).value();
if (event.getOrder() > order) {
log.debug("当前step已经执行过,不再执行");
return;
}
//逻辑代码
System.out.println("hello");
}
//-------------------------------------------------------------------
@EventListener
@Order(2)
public void DeleteKeywords(KeywordDeleteEvent event) throws NoSuchMethodException {
String name = Thread.currentThread().getStackTrace()[1].getMethodName();
Method method = this.getClass().getDeclaredMethod(name, KeywordDeleteEvent.class);
Integer order = method.getAnnotation(Order.class).value();
if (event.getOrder() > order) {
log.debug("当前step已经执行过,不再执行");
return;
}
System.out.println("hello,顺序二");
}
}
巧妙的利用了@Order注解
@Order,可以定义在类上,也可以定义到方法上,指定一个先后顺序。
三. 发布事务
@Resource
private ApplicationContext applicationContext;
KeywordDeleteEvent event = new KeywordDeleteEvent(this);
applicationContext.publishEvent(event);
ApplicationContext接口继承了ApplicationEventPublisher,并在AbstractApplicationContext实现了具体代码如下,实际执行是委托给ApplicationEventMulticaster(可以认为是多播):
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
//省略部分
getApplicationEventMulticaster().multicastEvent(event);
if (this.parent != null) {
this.parent.publishEvent(event);
}
}
Spring事件机制是观察者模式的一种实现,但是除了发布者和监听者者两个角色之外,还有一个EventMultiCaster的角色负责把事件转发给监听者如下;
applicationContext.publishEvent(event); 是会将事件发送给了EventMultiCaster,EventMultiCaster注册着所有的Listener,然后根据事件类型决定转发给那个Listener。
TODO EventMultiCaster,源码学习
四. 实现异步监听
@Aysnc其实是Spring内的一个组件,可以实现方法实现异步调用,节省耗时。内部实现机制是线程池任务ThreadPoolTaskExecutor,被注解的方法调用的时候,会在新的线程中执行,而调用它的方法会在原来的线程中执行。
4.1 配置线程池,开启异步处理
@Configuration
@EnableAsync
public class TaskExecutorConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(5);//线程池大小
taskExecutor.setMaxPoolSize(10);//线程池最大线程数
taskExecutor.setQueueCapacity(25);//最大等待任务数
taskExecutor.initialize();
return taskExecutor;
}
}
@EnableAsync注解开启支持异步处理
异步监听,本质上还是Spring的异步任务,只是在监听事件上加了异步
@EventListener
@Async
@Order(3)
public void upodate(KeywordDeleteEvent event) throws NoSuchMethodException {
//异步r任务
String name = Thread.currentThread().getStackTrace()[1].getMethodName();
Method method = this.getClass().getDeclaredMethod(name, UpdateCampaignDateEvent.class);
Integer order = method.getAnnotation(Order.class).value();
if (event.getOrder() > order) {
log.debug("当前step已经执行过,不再执行");
return;
}
//具体逻辑
}
}
五 消息队列的联想
上面的这种形式让我联想到消息队列。消息队列有两种类型,点对点消息队列模型和发布订阅消息模型。
- 发布订阅模型
支持向特定的消息主题发布消息,0个或者1个订阅者可以接收特定的topic的消息。在这种模型下,发布者和订阅者彼此不知道对方。这种模型如下图:
多个消费者可以获得消息在发布者和订阅者之间存在时间依赖性,即必须先订阅,再发送消息,而后接收订阅的消息,这个操作顺序必须保证。
- 点对点消息队列模型
每一个成功处理的消息都由消息消费者签收确认(Acknowledge)
此处只是简单的说明自己的联想,详细的消息队列还会有文章介绍,敬请期待
六 Java的事件(对比Spring,原理一样)
Java事件主要角色
- 事件源(Source):规定事件由谁来产生,在listener中作路由
- 事件对象(EventObject):传递信息的作用,对source的包装,根据不同的构造方法,可以附带除source之外的其他信息(参考上面)
- 事件监听(EventListener):业务逻辑处,根据事件中的事件源(source)或者事件对象,进行不同的路由,实现不同的业务。
source+event -> 触发 Listener
参考:
消息队列技术之基本概念
[