1)简介
Spring Event (接口名为Aplication Event)
观察者设计模式,由事件发起者publisher发布事件(指定事件名),事件监听者监听事件(指定事件名)。 好比, A 说了一句话,让B做啥事,B听到了,就去做了
已知好处,先返回用户想要的内容,例如考试系统中计算后的总分先返回用户看,后续的日志记录异步处理,不阻塞线程,抽离业务到异步任务,速度更快
实现流程
- 自定义事件 (extends ApplicationEvent)
- 定义事件监听器 (implement ApplicationListener)
- 容器发布事件 (autowired 容器对象,然后 容器对象调用publisher方法指定触发哪个事件)
2)快速入门
2.1)依赖引入
如果引入了springboot的依赖就不用添加了,里面那个starter包含了这个spring依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.9.RELEASE</version>
<scope>compile</scope>
</dependency>
2.2)事件定义
新建一个类,继承applicationEvent使其成为事件类
定义属性,类似实体属性,承载发布事件所传递的参数
其中构造函数必须重写,且形参必须为Ojbect,内部调用super实例化事件类
public class BuyEvent extends ApplicationEvent {
private String name;
public BuyEvent(Object source,String name) {
super(source);
this.name=name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
2.3)监听器定义
第一种方法,通过实现ApplicationListener接口
内部重写onApplicationEvent方法,泛型参数需使用BuyEvent,形参才能修改为事件类,方便后续不使用向下转型(强转为BuyEvent)而调用事件的get set方法
@Slf4j
@Component
public class BuyListener implements ApplicationListener<BuyEvent> {
@Override
public void onApplicationEvent(BuyEvent buyEvent) {
System.out.println("购买成功:"+ buyEvent.getName());
}
}
第二种方法,使用@EventListener注解
事件类作为一个简单的pojo封装数据,不需要@Component加入容器管理
但是监听器是通过ApplicationContext上下文调用的publisher,
需要在容器管理中找有无监听器监听了 XX事件,所以所有监听器的头顶必须带一个@Component
@Component
public class SayListener {
@EventListener({SayEvent.class})
public void say(SayEvent sayEvent) {
String content = sayEvent.getContent();
System.out.println(content);
}
}
此时的事件不需要继承ApplicationContent和重写方法,仅需一个构造函数和get set用来传递事件的参数即可
public class SayEvent {
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public SayEvent(String content) {
this.content = content;
}
}
2.4)事件发布
@Service
@Slf4j
public class BuyService {
@Autowired
ApplicationContext applicationContext;
public void buying(String name) {
applicationContext.publishEvent(new BuyEvent(this,name));
}
}
@Service
public class SayService {
@Autowired
ApplicationContext applicationContext;
public void saying(String content){
applicationContext.publishEvent(new SayEvent(content));
}
}
2.5)单测
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
@SpringBootTest
@RunWith(SpringRunner.class)
public class BuyServiceTest {
@Autowired
BuyService buyService;
@Autowired
SayService sayService;
@Test
public void testing() {
buyService.buying("苹果");
sayService.saying("hahaha");
}
}
输出测试
3)异步处理
仅需要在listener监听方法上加@Async和sprinboot启动类上加@EnableAsync即可
模拟异步,场景为购买了商品后返回成功消息,同时开启新线程处理记录日志的异步请求
事件
public class LogEvent {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public LogEvent(String name) {
this.name = name;
}
}
监听
@Slf4j
@Component
public class LogListener {
@Async
@EventListener({LogEvent.class})
public void logging(LogEvent logEvent) throws InterruptedException {
long start = System.currentTimeMillis();
log.info("日志记录开始为:"+logEvent.getTime());
Thread.sleep(6000);
long end = System.currentTimeMillis();
log.info("记录结束,总耗时为:"+(end-start));
}
}
发布
@Service
@Slf4j
public class BuyService {
@Autowired
ApplicationContext applicationContext;
public void buying(String name) {
long start = System.currentTimeMillis();
applicationContext.publishEvent(new BuyEvent(this,name));
applicationContext.publishEvent(new LogEvent(new Date()));
long end = System.currentTimeMillis();
log.info("全部任务结束,耗时:"+(end-start)) ;
}
}
测试
@SpringBootTest
@RunWith(SpringRunner.class)
public class BuyServiceTest {
@Autowired
BuyService buyService;
@Autowired
SayService sayService;
@Test
public void buying() {
buyService.buying("苹果");
}
}
结果
这里异步线程提前结束了是因为测试类主线程结束,异步线程终止,可以在异步线程后增加一句话测试不阻塞的特性 或者在生成环境服务下运行,因为主服务springboot会一直运行
可以看到异步事件的发布不阻碍下面语句的执行