概述
1.场景描述
遇到个需求:在发放给用户奖励的时候,将奖励明细记录下来。部分代码如下:
// 从数据库中读取 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,官网资料:https://github.com/google/guava/wiki/EventBusExplained。
2.1 Event Bus 使用方式
依赖:
implementation group: 'com.google.guava', name: 'guava', version: '31.0.1-jre'
基本使用方法:
主方法:
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 事件简单。
2.2 Spring 式的 Event Bus
上面的使用方式只能算是玩玩,如果放在实际生产中只能做一些打印日志这样的工作,没有多大威力。监听器能做的也只是跟 pojo 类交互下而已。
比如: eventBus.post(1)
是把 “1” 存到 MySQL 或者 Redis 或者 Http 发送给别的微服务。
public class MultipleListeners {
@Subscribe
public void intTask(Integer i) {
// 这里怎么写!!!
}
}
因为 new 出来的监听器不会被 spring 管理,更谈不上自动装配依赖了。而且,eventBus 也是 new 出来的,如果 有10 个地方要发布事件,那就得 new 上 10 个对象,虽说有垃圾回收机制,但好歹也是程序员,想想办法。
解决办法:将 eventBus 和 MultipleListeners 交给 spring 容器管理。
实现方式:
eventBus 交给 spring 管理很容易,一个配置就搞定了。
@Configuration
public class GuavaEventConfig {
@Bean
public EventBus eventBus(){
return new EventBus();
}
}
MultipleListeners 交给容器管理也很容易,一个注解搞定。
@Component
public class MultipleListeners {
// 假定监听器依赖该组件做持久化
@Autowired
private HistoryRecordRepository historyRecordRepository;
@Subscribe
public void intTask(Integer i) {
historyRecordRepository.save(i);
}
}
接下来就是在 创建 bean 的时候将 multipleListeners 注册到 eventBus 中。
@Component
public class MultipleListenersBeanPostProcessor implements BeanPostProcessor {
@Autowired
EventBus eventBus;
/**
* 该方法在 bean 创建后会被调用。可以做一些事情。
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
eventBus.register(bean);
return bean;
}
}
上面的代码稍微解释下:bean 在执行 BeanPostProcessor 接口的方法是,它已经被实例化而且解决了依赖问题。所以 eventBus 直接装配进来是没有问题的。当调到 postProcessAfterInitialization 时,将 MultipleListenersBeanPostProcessor bean 注册到 EventBus bean 中,就实现了注册。
这样两者都被 spring 容器管理,都是单例,MultipleListeners 中依赖的各种组件都会被自动装配。So 监听器可以发挥更大的威力,至于前面提到的持久化,洒洒水啦。
2.3 半 Spring 式的 Event Bus
上述方式能够发挥出事件监听的全部威力,但监听器的注册方式觉得有点”骚“,而且但凡使用 Event Bus 发布事件的地方都得先 @Autowired 进来,再 eventBus.post() 发布事件,还是有那么一点点多余。 写到这里不禁要再次膜拜一下我上家公司的老大,至于是自创的还是在哪看的,这都不重要了。可惜我忘记了注册代码,注册这块代码是自己补上的。
首先,eventBus 是个单例,哪里使用哪里取。那它仅仅只是个单例不可以吗!
// 单例模式
public class EventPublish {
private EventPublish() {
}
public static EventBus get() {
return EventHolder.instance;
}
private static class EventHolder {
private static final EventBus instance = new EventBus();
}
}
EventPublish.get()
就能直接拿到 EventBus 对象,而且全局唯一,EventPublish.get().post()
就能发布事件。哪里发布那里写,就可以了。
又回到刚才那个问题,监听器怎么注册?
先创建监听器:
@Component
public interface Listeners {
@Subscribe
public void intTask(Integer i)
}
@Component
public class MultipleListeners implements Listeners{
// 假定监听器依赖该组件做持久化
@Autowired
private HistoryRecordRepository historyRecordRepository;
@Subscribe
public void intTask(Integer i) {
historyRecordRepository.save(i);
}
}
注册:
public class Application {
public static void main(String[] args) {
// 这里完成了所有自动配置类的 bean 的创建。
ApplicationContext context = SpringApplication.run(BwzdApplication.class, args);
// 从 spring 容器中拿出监听器
EventHandler bean = context.getBean(Listeners.class);
// 注册。
EventPublish.get().register(bean);
}
}
之后在引用程序中使用时,直接拿着发布就行了。
再看看最开始的场景需求
代码可以改成
UserGameInfo userGameInfo = getUserGameInfo(userId);
userRepository.saveUserGameInfo(userGameInfo.addGold(faceValue));
// 保存明细。
EventPublish.get().post(faceValue)
可以在多个地方直接多次调用 EventPublish.get().post(faceValue)
也可以放在 userGameInfo.addGold(faceValue)
里面,这样只需要调用一次就行了。
public UserGameInfo addGold(long number) {
this.gold = Math.max(0, this.gold + number);
EventPublish.get().post(number);
return this;
}
EventBus 全局单例,与spring 管理的方式相比,不用每次都装配进来,哪里用直接写。
最后
以上就是无语龙猫为你收集整理的guava Event Bus 使用方式和半 Spring 式 Event Bus1.场景描述2.事件机制的全部内容,希望文章能够帮你解决guava Event Bus 使用方式和半 Spring 式 Event Bus1.场景描述2.事件机制所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复