SpringBoot事件机制
# SpringBoot 事件机制
# 使用背景
考虑到部分项目对消息队列的要求不高,又不想引入额外部署的消息队列,这时候就可以使用 Spring Event 实现【内存】级别的消息队列。
# 简单介绍
Spring 基于观察者模式,实现了自身的事件机制,由三部分组成:
- 事件 ApplicationEvent (opens new window):通过继承它,实现自定义事件。另外,通过它的
source
属性可以获取事件源,timestamp
属性可以获得发生时间。 - 事件发布者 ApplicationEventPublisher (opens new window):通过它,可以进行事件的发布。
- 事件监听器 ApplicationListener (opens new window):通过实现它,进行指定类型的事件的监听。
友情提示:JDK 也内置了事件机制的实现,考虑到通用性,Spring 的事件机制是基于它之上进行拓展。因此,
- ApplicationEvent 继承自
java.util.EventObject
(opens new window),- ApplicationListener 继承自
java.util.EventListener
(opens new window)。
# 观察者模式和发布订阅模式有什么不同?
# 观察者模式(Observer Pattern)
在我们日常业务开发中,观察者模式对我们很大的一个作用,在于实现业务的解耦。例如用户注册的场景。
定义
观察者模式定义了一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会收到通知并自动更新。
组成部分
- Subject(主题): 被观察的对象,维护一个观察者列表,并在自身状态改变时通知所有观察者。
- Observer(观察者): 接收主题通知并更新自身状态的对象。
实现
- 主题对象直接持有观察者的引用。
- 主题对象负责通知所有观察者。
# 发布订阅模式(Publish-Subscribe Pattern)
定义
发布订阅模式是一种消息传递模式,发布者将消息发布到特定的通道,不直接与订阅者进行通信。订阅者订阅一个或多个通道,并接收来自这些通道的消息。
组成部分
- Publisher(发布者): 生产消息的对象,将消息发布到某个通道。
- Subscriber(订阅者): 消费消息的对象,订阅某个通道以接收消息。
- Message Broker(消息代理): 管理发布者和订阅者之间的通信,确保消息从发布者传递到正确的订阅者。
实现
- 发布者和订阅者通过消息代理进行通信,解耦了两者之间的直接依赖。
- 消息代理负责管理和分发消息。
# 关键区别
- 耦合性:
- 观察者模式: 观察者和主题之间存在直接依赖,观察者必须知道主题的存在。
- 发布订阅模式: 发布者和订阅者之间没有直接依赖关系,消息代理负责两者的通信。
- 通信方向:
- 观察者模式: 通知是从主题到观察者的单向通知。
- 发布订阅模式: 通信可以是多向的,多个发布者和订阅者通过消息代理进行交互。
- 应用场景:
- 观察者模式: 适用于对象之间存在明确依赖关系的场景,如 GUI 事件处理。
- 发布订阅模式: 适用于需要解耦发布者和订阅者的场景,如消息队列系统。
简单来说:
- 发布订阅模式 中间多了一个 事件通道,以此避免了发布者和订阅者之间产生的依赖关系。
更多参考:
# 代码示例
# 引入依赖
<!-- springboot web 实现对 Spring MVC 的自动化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
1
2
3
4
5
2
3
4
5
# 开启异步
启动类上添加 @EnableAsync
注解,开启 Spring 异步的功能。
# 1、事件类
/**
* 用户注册事件
*
* @author chenmeng
*/
@Getter
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;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 2、事件发布者类
/**
* 事件发布者类
*
* @author chenmeng
*/
@Service
public class UserService implements ApplicationEventPublisherAware {
private final Logger logger = LoggerFactory.getLogger(getClass());
private ApplicationEventPublisher applicationEventPublisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
public void register(String username) {
// ... 执行注册逻辑
logger.info("[事件发布-register][执行用户({}) 的注册逻辑]", username);
// ... 发布
applicationEventPublisher.publishEvent(new UserRegisterEvent(this, username));
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 3、事件监听器类
/**
* 事件监听器类(不需实现接口版)
*
* @author chenmeng
*/
@Service
public class CouponService {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Order(1)
@EventListener
public void addCoupon(UserRegisterEvent event) {
logger.info("[事件监听-addCoupon][给用户({}) 发放优惠劵]", event.getUsername());
}
}
/**
* 事件监听器类(实现接口版)
*
* @author chenmeng
*/
@Service
public class EmailService implements ApplicationListener<UserRegisterEvent> {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Override
@Async // 可以不加,锦上添花,设置 @Async 注解,声明异步执行。毕竟实际场景下,发送邮件可能比较慢,又是非关键逻辑。
public void onApplicationEvent(UserRegisterEvent event) {
logger.info("[事件监听-onApplicationEvent][给用户({}) 发送邮件]", event.getUsername());
}
}
/**
* 事件监听器类(不需实现接口版)
*
* @author chenmeng
*/
@Service
public class InfoService {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Order(2)
@EventListener
public void sendInfo(UserRegisterEvent event) {
logger.info("[事件监听-sendInfo][给用户({}) 发送信息]", event.getUsername());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# 客户端
/**
* @author chenmeng
*/
@RestController
@RequestMapping("/demo")
@RequiredArgsConstructor
public class DemoController {
private final UserService userService;
@GetMapping("/register")
public String register(String username) {
userService.register(username);
return "success";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
测试结果:
2024-12-31 14:29:15.712 INFO 25272 --- [nio-9111-exec-2] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms
2024-12-31 14:29:15.741 INFO 25272 --- [nio-9111-exec-2] c.chenmeng.project.service.UserService : [事件发布-register][执行用户(张三) 的注册逻辑]
2024-12-31 14:29:15.742 INFO 25272 --- [nio-9111-exec-2] c.c.project.service.CouponService : [事件监听-addCoupon][给用户(张三) 发放优惠劵]
2024-12-31 14:29:15.742 INFO 25272 --- [nio-9111-exec-2] c.chenmeng.project.service.InfoService : [事件监听-sendInfo][给用户(张三) 发送信息]
2024-12-31 14:29:15.744 INFO 25272 --- [ task-1] c.chenmeng.project.service.EmailService : [事件监听-onApplicationEvent][给用户(张三) 发送邮件]
1
2
3
4
5
2
3
4
5
# 设置监听顺序
如果想要多个监听器按照指定顺序执行,可在监听方法上加上 @Order()
注解。
注意事项:
- 标上异步注解
@Async
的方法,使用会无效(即异步方法无法排序) - 若需排序,必须都使用添加
@EventListener
注解的形式类创建监听器类(另一种写法创建的监听器,笔者测试@Order()
发现无效)
# 学习参考
上次更新: 2024/12/31 17:34:27