Guava的event bus

Guava的event bus

guava, https://github.com/google/guava 是一个非常有名的Java类库,提供了很多在日常开发中常用的集合、函数接口等。此外,guava还提供了一个模块叫做event bus,生产者往event bus上投递消息,event bus负责回调订阅了此类消息的回调函数,实现了消息生产者和消费者之间的解耦和异步处理。以下是一个简单的例子:

Guava的event bus

public class SimpleListener {
    @Subscribe
    public void task(String s) {
        System.out.println("do task(" + s + ")");
    }
}

Guava的event bus

Guava的event bus

public class SimpleEventBusExample {
    public static void main(String[] args) {
        EventBus eventBus = new EventBus();
        eventBus.register(new SimpleListener());
        System.out.println("Post Simple EventBus Example");
        eventBus.post("Simple EventBus Example");
    }
}

Guava的event bus

output

Post Simple EventBus Example
do task(Simple EventBus Example)

event bus集成到spring中

在之前的例子和guava的官方文档里面可以看到,guava的event bus使用方式如下

1. 声明一个event bus对象(线程安全,所以可以做到全局唯一,而且订阅者和发布者必须共享这个event bus对象)

2. 对于订阅者,支持 @Subscribe,定义处理消息的回调函数。

3. 对每一个订阅者,需要调用event bus的register方法,才能收到消息订阅。

对于1 和 2,都比较好。但是对于第3步来说,则有一点困难。因为

1. 在spring中,通常的你的订阅者还会依赖其他的spring管理的bean,于是你的订阅者也会被纳入到spring的生命周期的管理中来,这样如果用new的方式来初始化一个订阅者,显得非常的"不spring"。

2. 对于每个订阅者,都要显式的注册到event bus里面,这样并没有做到关注点分离。理想的情况下,订阅者是不应该去关注如何注册到event bus中。它只应该申明处理消息的回调函数,以及该回调函数是否能够并发调用。注册到event bus中这件事,对于订阅者应该是被动且自动的(只需要申明自己是否想注册到event bus,而不需要关心细节)。这一点在多人开发,并且项目人员水平参差不齐的时候,尤其重要。

那么如何做到自动注册呢?其实答案很简单,在spring中,ApplicationContext这个类提供了一系列的方法去获取到当前spring context中的bean,只需要在event bus初始化之后,通过ApplicationContext来获取当前有哪些订阅者,并且主动的去注册就行。由于,guava的实现中,并没有要求订阅者实现某个接口,而是用注解的方式来声明回调函数的,则这篇文章中的实现,也不需要订阅者去实现某个接口,而是用注解的方式来申明自己是一个订阅者。代码如下

先声明一个注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Service
public @interface EventSubscriber {}

对于一个订阅者,在类的接口上,加上这个注解,并且确定这个bean会在纳入spring的生命周期管理中。

Guava的event bus

@EventSubscriber
public class SimpleSubscriber implements HiDasSubscriber {

    @Autowired
    Somebean somebean;

    @Subscribe
    @AllowConcurrentEvents
    public Integer logEventToDbAndUpdateStatus(String event) {
        System.out.println("receive a event:"+event);
    }
}

Guava的event bus

声明一个event bus的服务,并且在初始化之后,通过ApplicationContext的 getBeansWithAnnotation 的方法把所有的订阅者获取,并且注册到event bus中。

Guava的event bus

@Service
public class EventBusService implements InitializingBean{

    private EventBus innerBus;

    @Inject
    private ApplicationContext appContext;

    public void unRegister(Object eventListener){
        innerBus.unregister(eventListener);
    }

    public void postEvent(String event){
        innerBus.post(event);
    }

    public void register(Object eventListener){
        innerBus.register(eventListener);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        innerBus = new AsyncEventBus("Hidas-event-bus", Executors.newCachedThreadPool());
        appContext.getBeansWithAnnotation(EventSubscriber.class).forEach((name, bean) -> {
            innerBus.register(bean);
        });
    }
}

Guava的event bus

完成了这两步之后。如果其他的消息生产者要往event bus上发消息,只需要注入这个event bus service,并且调用其post方法就好了。

注:这个只是例子,在实际项目中,对所有的消息都会声明一个基类或者接口,每个订阅者对只会处理消息的某个具体实现。这样event bus会根据消息的具体类型,来调用真正关注此类消息的订阅者的回调函数。这样比起让所有消息订阅者去实现一个 onEvent(BaseEvent event)的方法, 实际上是避免了一个多路分配的问题。

总结

对于一些类库在spring中使用,这种方法实际上是一种通用的模式,实现某个接口或者编写某个注解,然后通过ApplicationContext来获取对应的bean,之后进行某些注册或者组装操作。这样的话,可以让业务的代码,和框架的代码做到一定程度的关注点分离。

上一篇:Flutter跨组件传递事件


下一篇:Abp 领域事件简单实践 <二>