SpringBoot 事件驱动(本地模式)

一.SpringBoot 事件驱动(本地模式)

1.什么是事件驱动

Spring的事件(Application Event)为Bean与Bean之间的消息通信提供了支持

事件机制中有三种角色:发布事件者、事件、事件监听者

发布事件者:发布事件的对象

事件:事件的具体内容

事件监听者:等待处理时间的对象

image-20240117153834622

2.内置有哪些事件

image-20240117154018354

图上可以看到ApplicationEvent有两个子类

ApplicationContextEvent 是Spring提供的事件监听,包路径为:org.springframework.context.event

SpringApplicationEvent 是Springboot的扩展事件,包路径为:org.springframework.boot.context.event

Spring Boot扩展了Spring的ApplicationContextEvent,提供了四种事件:

  1. ApplicationStartedEvent :spring boot启动开始时执行的事件
  2. ApplicationEnvironmentPreparedEvent:spring boot 对应Enviroment已经准备完毕,但此时上下文context还没有创建
  3. ApplicationPreparedEvent:spring boot上下文context创建完成,但此时spring中的bean是没有完全加载完成的
  4. 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)

其中,historyRecordRepositoryuserRepository 都是自动装配进来的。这样的代码估计不会有人挑刺
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 事件简单

posted @   zhangyf1121  阅读(63)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示