Application Event
Spring
中的事件 Application Event
为 Bean
与 Bean
之间的消息通信提供了支持。
当一个Bean
处理完成事件之后,其他的Bean能够知道并作出相应的处理,这时候就要让另外一个 Bean
监听当前 Bean
所发送的事件。
Spring
中的事件需要遵循下述流程:
- 自定义事件,继承
ApplicationEvent
。 - 定义事件监听器,实现
ApplicationEvent
。 - 发布事件。
1:自定义事件
import org.springframework.context.ApplicationEvent;
/**
* @author xiaoFsu
* 继承 ApplicationEvent
*/
public class DemoEvent extends ApplicationEvent {
private String msg;
public DemoEvent(Object source,String msg) {
super(source);
this.msg = msg;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
继承 ApplicationEvent
之后需要在当前类的构造器中调用父类的构造器,并传入与事件相关联的对象,不能为 null
。
2:事件监听器
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
/**
* @author xiaoFsu
* ApplicationListener<DemoEvent> 实现ApplicationListener接口,指定监听的事件类型
*
*/
@Component
public class DemoListener implements ApplicationListener<DemoEvent> {
/**
* author:xiaoFsu
* time:2020.09.18 9:59:09
* 使用此方法对消息进行接收处理
*/
@Override
public void onApplicationEvent(DemoEvent event) {
System.out.println("The Msg is :"+event.getMsg());
}
}
注解 @FunctionalInterface
函数式接口,具体解释可以查看:函数式接口
ApplicationListener
中定义了一个方法,用来处理事件的响应。
3:事件发布类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
@Component
public class DemoPublisher {
@Autowired
private ApplicationContext applicationContext;
public void publishMsg(String msg){
applicationContext.publishEvent(new DemoEvent(this,msg));
}
}
注入 ApplicationContext
用来发布事件,使用 publishEvent
方法来进行发布。
4:配置包扫描
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("com.xiaofsu.demo.event") // 替换成你的包名称
public class DemoConfig {
}
5:执行
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
// 从配置文件中扫描包中的类获取上下文对象
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DemoConfig.class);
// 获取到Spring注入的Bean
DemoPublisher bean = context.getBean(DemoPublisher.class);
// 发布消息
bean.publishMsg("233333");
context.close();
}
这时候我们运行可以看到控制台输出了在 #2事件监听器
中设置的输出。
6:回顾
实现简单的监听之后我们需要考虑一下这个消息的处理逻辑:
- 消息通过
#3 事件发布类
进行发布之后。 - 消息会在
#2 事件监听器
中监听到。
7:问题疑惑
7.1:同步异步?
考虑一下,这时候如果大量的消息涌入,这个监听器是同步处理还是异步处理?假设我现在监听的是一个 用户注册的方法,当用户注册完成之后要发送 短信通知用户,那么我就可以如下模拟:
// 启动类中的修改
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
// 必须指定一下这个
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DemoConfig.class);
// 模拟6个用户注册成功,都需要通过监听器来发送注册成功的短信
DemoPublisher bean = context.getBean(DemoPublisher.class);
bean.publishMsg("A 注册成功");
bean.publishMsg("B 注册成功");
bean.publishMsg("C 注册成功");
bean.publishMsg("D 注册成功");
bean.publishMsg("E 注册成功");
bean.publishMsg("F 注册成功");
context.close();
}
// #2 监听器类的修改
@Override
public void onApplicationEvent(DemoEvent event) {
System.out.println("线程名称:"+Thread.currentThread().getName()+ " ->> 时间:"+ new Date()+ " ->>消息:"+event.getMsg());
// 模拟短信发送处理需要的时长
try {
TimeUnit.SECONDS.sleep(1);
}catch (Exception e){
e.printStackTrace();
}
}
这时候再运行程序,会得到主线程 main
一直在运行,目前模拟 6位用户,第一位用户得到短信的时间是1s之后,貌似可以接受,但是如果1000位用户同时注册呢?那第1000位用户得到消息的时间呢?
所以我们得知事件监听是同步执行的,那么该怎么换成异步的呢?只需要一个 @EnableSync
开启异步注解并把 @Async
注解注入到想异步执行的方法上就可以了。@Async
如果注解到类上,则代表此类的所有方法都是异步方法,如果注解到方法上,则代表该方法为异步方法。
所以再改造:
@Component
@EnableAsync // #2 事件监听器上加上此注解代表这个类开启异步处理
public class DemoListener implements ApplicationListener<DemoEvent>
// 并在方法上添加 @Async 注解表示此方法开启异步
@Override
@Async
public void onApplicationEvent(DemoEvent event)
------------------------------------------------
@Component
@EnableAsync // #3 事件发布类上加上此注解代表这个类开启异步处理
public class DemoPublisher
// 并在方法上添加 @Async 注解表示此方法开启异步
@Async
public void publishMsg(String msg)
此时再运行程序,可以看到新建了线程帮我们异步处理了数据。
7.2:更简单的方式
都用 Spring
了,有没有更简单的方式,比如注解?
这时候可以使用 @EventListener
来进行处理操作。
在 #2 事件监听器的时候,我们需要实现 ApplicationListener
接口 并指定类型,重写 onApplicationEvent
方法,当我们有多个需要监听的时候,这种方式就显得特别复杂繁琐。
所以在使用 @EnentListener
注解就可以完美的融入到我们的业务类当中,只需要将 #2 事件监听器中的代码改为下述代码:
@Component
@EnableAsync
public class DemoListener{
/**
* author:xiaoFsu
* time:2020.09.18 9:59:09
* 使用此方法对消息进行接收处理
*/
@Async
@EventListener
public void onApplicationEvent(DemoEvent event) {
System.out.println("DemoEvent -- > 线程名称:"+Thread.currentThread().getName()+ " ->> 时间:"+ new Date()+ " ->>消息:"+event.getMsg());
// 模拟短信发送处理需要的时长
try {
TimeUnit.SECONDS.sleep(1);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* author:xiaoFsu
* time:2020.09.18 9:59:09
* 使用此方法对消息进行接收处理
*/
@Async
@EventListener
public void onApplicationEvent(MyEvent event) {
// 我这里新建了一个 MyEvent,与DemoEvent相同,仅更改了名称,为了演示使用。
System.out.println("MyEvent -- > 线程名称:"+Thread.currentThread().getName()+ " ->> 时间:"+ new Date()+ " ->>消息:"+event.getMsg());
// 模拟短信发送处理需要的时长
try {
TimeUnit.SECONDS.sleep(1);
}catch (Exception e){
e.printStackTrace();
}
}
}
在启动类中,我们来发布这些消息,看看是否能够监听到这些消息:
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
// 必须指定一下这个
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DemoConfig.class);
DemoPublisher bean = context.getBean(DemoPublisher.class);
// DemoEvent 类,模拟注册成功操作
bean.publishMsg("A 注册成功");
bean.publishMsg("B 注册成功");
bean.publishMsg("C 注册成功");
bean.publishMsg("D 注册成功");
bean.publishMsg("E 注册成功");
bean.publishMsg("F 注册成功");
bean.publishMsg("G 注册成功");
// MyEvent 类,模拟发送数据操作
bean.publishMsg2("A 发送成功");
bean.publishMsg2("B 发送成功");
bean.publishMsg2("C 发送成功");
bean.publishMsg2("D 发送成功");
bean.publishMsg2("E 发送成功");
bean.publishMsg2("F 发送成功");
bean.publishMsg2("G 发送成功");
// context.close();
}
编写完成之后,查看控制台输出如下:
8:总结
SpringEvent
自定义事件链,是实用性很强的一种设计,可以利用它来做业务剥离,复杂场景解耦、代码独立等,也是事件驱动模型的核心,并且可以处理一对多,点对点,发布订阅的场景。
勿忘国耻,铭记历史,吾辈自强。
Q.E.D.