springboot事件机制event
关于SpringBoot事件机制
在这里,我们先❀几分钟的时间去了解一下SpringBoot事件机制Event
1 概述
在设计模式中,观察者模式是一个比较常用的设计模式。维基百科解释如下:
观察者模式是软件设计模式的一种。在此种模式中,一个目标对象管理所有相依于它的观察者对象,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。
此种模式通常被用来实时事件处理系统。 --FROM https://zh.wikipedia.org/wiki/观察者模式
在我们日常业务开发中,观察者模式对我们很大的一个作用,在于实现业务的解耦。以用户注册的场景来举例子,假设在用户注册完成时,需要给该用户发送邮件、发送优惠劵等等操作,如下图所示:
- UserService 在完成自身的用户注册逻辑之后,仅仅只需要发布一个 UserRegisterEvent 事件,而无需关注其它拓展逻辑。
- 其它 Service 可以自己订阅 UserRegisterEvent 事件,实现自定义的拓展逻辑。
😆 友情提示:很多时候,我们会把观察者模式和发布订阅模式放在一起对比。
简单来说,发布订阅模式属于广义上的观察者模式,在观察者模式的 Subject 和 Observer 的基础上,引入 Event Channel 这个中介,进一步解耦。如下图所示:
进一步的讨论,胖友可以瞅瞅《观察者模式和发布订阅模式有什么不同?》的讨论。
2 Spring 事件机制
Spring 基于观察者模式,实现了自身的事件机制,由三部分组成:
- 事件 ApplicationEvent:通过继承它,实现自定义事件。另外,通过它的
source
属性可以获取事件源,timestamp
属性可以获得发生时间。 - 事件发布者 ApplicationEventPublisher:通过它,可以进行事件的发布。
- 事件监听器 ApplicationListener:通过实现它,进行指定类型的事件的监听。
友情提示:JDK 也内置了事件机制的实现,考虑到通用性,Spring 的事件机制是基于它之上进行拓展。因此,ApplicationEvent 继承自
java.util.EventObject
,ApplicationListener 继承自java.util.EventListener
。
3 入门示例
示例代码对应仓库:
看完一些基础的概念,我们来撸个 Spring 事件机制的入门示例,具体的场景还是以用户注册为例子。新建项目,最终项目如下图:
- 引入依赖,在
pom.xml
文件中,引入相关依赖。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.southtang</groupId>
<artifactId>learning</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>springboot-event</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
引入 spring-boot-starter-web
依赖的原因,是稍后会提供示例 API 接口,方便测试。
- 创建
UserRegisterEvent
事件类,继承 ApplicationEvent 类,用户注册事件。代码如下:
/**
* 用户注册事件
*/
public class UserRegisterEvent extends ApplicationEvent {
/**
* 用户名
*/
private String username;
public UserRegisterEvent(Object source) {
super(source);
}
public UserRegisterEvent(Object source, String username) {
super(source);
this.username = username;
}
public String getUsername() {
return username;
}
}
通过在 UserRegisterEvent 类中,定义成员变量 username
,将用户名附带上。
- 创建
UserService
类,用户 Service。代码如下:
@Service
@Slf4j
public class UserService implements ApplicationEventPublisherAware { //<1>
private ApplicationEventPublisher applicationEventPublisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
public void register(String username) {
// ... 执行注册逻辑
log.info("[register][执行用户({}) 的注册逻辑]", username);
// ... 发布<2>
applicationEventPublisher.publishEvent(new UserRegisterEvent(this, username));
}
}
<1>
处,实现 ApplicationEventPublisherAware 接口,从而将 ApplicationEventPublisher 注入到其中。
<2>
处,在执行完注册逻辑后,调用 ApplicationEventPublisher 的 #publishEvent(ApplicationEvent event)
方法,发布UserRegisterEvent
事件。
- 创建
EmailService
类,邮箱 Service。代码如下:
@Service
@Slf4j
public class EmailService implements ApplicationListener<UserRegisterEvent> {//<1>
@Override
@Async
public void onApplicationEvent(UserRegisterEvent event) { //<2>
log.info("[onApplicationEvent][给用户({}) 发送邮件]", event.getUsername());
}
}
<1>
处,实现 ApplicationListener 接口,通过 E
泛型设置感兴趣的事件。
<2>
处,实现 #onApplicationEvent(E event)
方法,针对监听的 UserRegisterEvent 事件,进行自定义处理。
【可以不加】<3>
处,锦上添花,设置 @Async
注解,声明异步执行。毕竟实际场景下,发送邮件可能比较慢,又是非关键逻辑。
- 创建
CouponService
类,优惠劵 Service。代码如下:
@Service
@Slf4j
public class CouponService {
@EventListener//<1>
public void addCoupon(UserRegisterEvent event) {
log.info("[addCoupon][给用户({}) 发放优惠劵]", event.getUsername());
}
}
<1>
处,在方法上,添加 @EventListener
注解,并设置监听的事件为 UserRegisterEvent。这是另一种使用方式!
- 创建
DemoController
类,提供/demo/register
注册接口。代码如下:
@RestController
@RequestMapping("/demo")
public class DemoController {
@Autowired
private UserService userService;
@GetMapping("/register")
public String register(String username) {
userService.register(username);
return "success";
}
}
- 创建
DemoApplication
类,应用启动类。代码如下:
@SpringBootApplication
@EnableAsync // 开启 Spring 异步的功能
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
- 测试
- 执行 DemoApplication 类,启动项目。
- 调用 http://127.0.0.1:8080/demo/register?username=lihua接口,进行注册。IDEA 控制台打印日志如下:
2023-09-13 16:49:28.036 INFO 13992 --- [nio-8080-exec-1] c.l.s.event.service.UserService : [register][执行用户(lihua) 的注册逻辑]
2023-09-13 16:49:28.046 INFO 13992 --- [nio-8080-exec-1] c.l.s.event.service.CouponService : [addCoupon][给用户(lihua) 发放优惠劵]
2023-09-13 16:49:28.046 INFO 13992 --- [ task-1] c.l.s.event.service.EmailService : [onApplicationEvent][给用户(lihua) 发送邮件]
4. Spring 内置事件
在 Spring 框架中,自定义了非常多的自定义事件,让我们更容易的进行拓展。下面,我们来简单举一些例子。
- ApplicationContextEvent 是 Spring Context 相关的事件基类,如下图所示:
友情提示:Spring Context 可以简单理解成 IoC 容器。
- ContextStartedEvent:Spring Context 启动完成事件。
- ContextStoppedEvent:Spring Context 停止完成事件。
- ContextClosedEvent:Spring Context 停止开始事件。
- ContextRefreshedEvent:Spring Context 初始化或刷新完成事件。
也就是说,在 Spring Context 的整个生命周期中,会发布相应的 ApplicationContextEvent 事件。
SpringApplicationEvent 是 Spring Boot Application(应用)相关的事件基类,如下图所示:
- ApplicationStartingEvent:Application 启动开始事件。
- ApplicationEnvironmentPreparedEvent:Spring Environment 准备完成的事件。
- ApplicationContextInitializedEvent:Spring Context 准备完成,但是 Bean Definition 未加载时的事件
- ApplicationPreparedEvent:Spring Context 准备完成,但是未刷新时的事件。
- ApplicationReadyEvent:Application 启动成功事件。
- ApplicationFailedEvent:Application 启动失败事件。
也就是说,在 Application 的整个生命周期中,会发布相应的 SpringApplicationEvent 事件。
有一个SpringBoot + Nginx优雅上下线的案例
- RouteRefreshListener
Spring Cloud Gateway 通过监听 RefreshRoutesEvent 事件,结合 Nacos 作为配置中心,实现网关路由动态刷新的功能。
点击案例跳转
友情提示:Spring Cloud Zuul 也是通过监听 RoutesRefreshedEvent 事件,实现网关路由动态刷新的功能。
- RefreshRemoteApplicationEvent
Spring Cloud Config Client 通过监听 RefreshRemoteApplicationEvent 事件,结合 RabbitMQ 作为 Spring Cloud Bus 消息总线,实现本地配置刷新的功能。
点击案例跳转