guava Event Bus 使用方式和半 Spring 式 Event Bus

guava Event Bus 使用方式和半 Spring 式 Event Bus,第1张

guava Event Bus 使用方式和半 Spring 式 Event Bus 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;
    
    @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 管理的方式相比,不用每次都装配进来,哪里用直接写。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5075087.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-11-16
下一篇 2022-11-16

发表评论

登录后才能评论

评论列表(0条)

保存