遇到个需求:在发放给用户奖励的时候,将奖励明细记录下来。部分代码如下:
// 从数据库中读取 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; @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 管理的方式相比,不用每次都装配进来,哪里用直接写。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)