SpringBoot 事件驱动(本地模式)
一.SpringBoot 事件驱动(本地模式)
1.什么是事件驱动
Spring的事件(Application Event)为Bean与Bean之间的消息通信提供了支持
事件机制中有三种角色:发布事件者、事件、事件监听者
发布事件者:发布事件的对象
事件:事件的具体内容
事件监听者:等待处理时间的对象
2.内置有哪些事件
图上可以看到ApplicationEvent有两个子类
ApplicationContextEvent 是Spring提供的事件监听,包路径为:org.springframework.context.event
SpringApplicationEvent 是Springboot的扩展事件,包路径为:org.springframework.boot.context.event
Spring Boot扩展了Spring的ApplicationContextEvent,提供了四种事件:
- ApplicationStartedEvent :spring boot启动开始时执行的事件
- ApplicationEnvironmentPreparedEvent:spring boot 对应Enviroment已经准备完毕,但此时上下文context还没有创建
- ApplicationPreparedEvent:spring boot上下文context创建完成,但此时spring中的bean是没有完全加载完成的
- ApplicationFailedEvent:spring boot启动异常时执行事件
4.自定义
内置的事件再多也无法满足自定义的需要,所以怎么实现自己的事件才是我们学习的目标,理解这套框架
3.1 新建一个Springboot web项目
一路next就好,勾选web,别犹豫
3.2 创建自定义事件,继承ApplicationEvent
这里只是加了一个name,在正常开发中,可以设置一些自定义的属性
public class CarawayEvent extends ApplicationEvent {
private final String name;
public CarawayEvent(Object source, String name) {
super(source);
this.name = name;
}
public String getName() {
return name;
}
}
3.3 创建监听器Listener,实现ApplicationListener
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
@Component
public class CarawayEventListener implements ApplicationListener<CarawayEvent> {
@Override
public void onApplicationEvent(CarawayEvent event) {
System.out.println(event.getName());
}
}
3.4 发布事件
@RestController
public class TestController {
@Autowired
private ApplicationEventPublisher publisher;
@GetMapping("/")
public String test(HttpServletRequest request){
CarawayEvent event = new CarawayEvent(this, "香菜");
publisher.publishEvent(event);
return "hello";
}
}
二.Guava Event Bus事件驱动模式
1.场景描述
遇到个需求:在发放给用户奖励的时候,将奖励明细记录下来。部分代码如下:
// 从数据库中读取 userGameInfo。
UserGameInfo userGameInfo = getUserGameInfo(userId);
//...... 其他逻辑 .....
// 给 userGameInfo 增加 faceValue 的奖励,再 save 到数据库中
userRepository.saveUserGameInfo(userGameInfo.addGold(faceValue));
// 从数据库中读取 userGameInfo。
UserGameInfo userGameInfo = getUserGameInfo(userId);
//...... 其他逻辑 .....
// 给 userGameInfo 增加 faceValue 的奖励,再 save 到数据库中。
userRepository.saveUserGameInfo(userGameInfo.addGold(faceValue));
需要添加的功能:将每次增加的 faveValue 明细记录下来保存到一张明细表中
最简单的方式就是在 userRepository.saveUserGameInfo
的上面或者下面写上保存明细的代码。但项目中有多个地方发放奖励,这样就会出现很多重复代码。改进的方式,可以将保存明细的代码封装起来,然后各个地方调用,就能减少重复代码,会干净些
改进后的代码结构:
UserGameInfo userGameInfo = getUserGameInfo(userId);
userRepository.saveUserGameInfo(userGameInfo.addGold(faceValue));
// 保存明细。
historyRecordRepository.saveLGERecord(faceValue)
其中,historyRecordRepository
、userRepository
都是自动装配进来的。这样的代码估计不会有人挑刺
2.事件机制
上面的需求还有一种解决方式,采用事件机制,“当 AAAA 发生的时候,去做一件 BBBB 的事情” 是符合的该场景的。其实我第一反应是采用观察者模式,事件机制,记忆中上家公司处理这种场景都采用这种方案,我当时对那段代码印象很深,觉得写的很帅气,但是没有完全整明白
稍稍研究了下 spring 的监听器后,我就觉得不对劲,一个监听器一只能监听一个事件(也许可以监听多个,是我还没找见),但是之前项目中是用一个“事件处理器”处理了多个事件
很明显打开方式不对,而且之前的调用方式很简单。Google 着记忆中的 guava event…。最终确认使用的是 guava EventBus
2.1 Event Bus 使用方式
依赖:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.2-jre</version>
</dependency>
基本使用方法:
主方法:
import com.google.common.eventbus.EventBus;
public class MultipleEventTypeBusExample {
public static void main(String[] args) {
// 事件总线
EventBus eventBus = new EventBus();
// 注册监听器
eventBus.register(new MultipleListeners());
// 打个日志做标记
System.out.println("Post 'Multiple Listeners Example'");
// 发布事件
eventBus.post("Multiple Listeners Example");
// 再次发布事件
eventBus.post(1);
}
}
监听器:
import com.google.common.eventbus.Subscribe;
public class MultipleListeners {
@Subscribe
public void task1(String s) {
System.out.println("do task1(" + s +")");
}
@Subscribe
public void task2(String s) {
System.out.println("do task2(" + s +")");
}
@Subscribe
public void intTask(Integer i) {
System.out.println("do intTask(" + i +")");
}
}
执行结果:
Post 'Multiple Listeners Example'
do task2(Multiple Listeners Example)
do task1(Multiple Listeners Example)
do intTask(1)
分析结果:
先是创建事件总线,再讲监听器注册到事件中。@Subscribe
注解的方法会在总线发布事件时被执行,至于发布事件时触发监听器的哪个方法,由监听器方法的入口参数类型决定。
eventBus.post("Multiple Listeners Example")
触发了 task1(String s) 和 task2(String s)
eventBus.post(1)
只触发了 intTask(Integer i)
这样就实现了:一个发布器可以发布多种事件,同时一个监听器可以监听多种事件,而且使用方式比 spring 事件简单
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)