Guava的event bus
guava, https://github.com/google/guava 是一个非常有名的Java类库,提供了很多在日常开发中常用的集合、函数接口等。此外,guava还提供了一个模块叫做event bus,生产者往event bus上投递消息,event bus负责回调订阅了此类消息的回调函数,实现了消息生产者和消费者之间的解耦和异步处理。以下是一个简单的例子:
public classSimpleListener {
@Subscribepublic voidtask(String s) {
System.out.println("do task(" + s + ")");
}
}
public classSimpleEventBusExample {public static voidmain(String[] args) {
EventBus eventBus= newEventBus();
eventBus.register(newSimpleListener());
System.out.println("Post Simple EventBus Example");
eventBus.post("Simple EventBus Example");
}
}
output
Post Simple EventBus Exampledo 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
@Servicepublic @interfaceEventSubscriber {}
对于一个订阅者,在类的接口上,加上这个注解,并且确定这个bean会在纳入spring的生命周期管理中。
@EventSubscriberpublic class SimpleSubscriber implementsHiDasSubscriber {
@Autowired
Somebean somebean;
@Subscribe
@AllowConcurrentEventspublicInteger logEventToDbAndUpdateStatus(String event) {
System.out.println("receive a event:"+event);
}
}
声明一个event bus的服务,并且在初始化之后,通过ApplicationContext的 getBeansWithAnnotation 的方法把所有的订阅者获取,并且注册到event bus中。
@Servicepublic class EventBusService implementsInitializingBean{privateEventBus innerBus;
@InjectprivateApplicationContext appContext;public voidunRegister(Object eventListener){
innerBus.unregister(eventListener);
}public voidpostEvent(String event){
innerBus.post(event);
}public voidregister(Object eventListener){
innerBus.register(eventListener);
}
@Overridepublic void afterPropertiesSet() throwsException {
innerBus= new AsyncEventBus("Hidas-event-bus", Executors.newCachedThreadPool());
appContext.getBeansWithAnnotation(EventSubscriber.class).forEach((name, bean) ->{
innerBus.register(bean);
});
}
}
完成了这两步之后。如果其他的消息生产者要往event bus上发消息,只需要注入这个event bus service,并且调用其post方法就好了。
注:这个只是例子,在实际项目中,对所有的消息都会声明一个基类或者接口,每个订阅者对只会处理消息的某个具体实现。这样event bus会根据消息的具体类型,来调用真正关注此类消息的订阅者的回调函数。这样比起让所有消息订阅者去实现一个 onEvent(BaseEvent event)的方法, 实际上是避免了一个多路分配的问题。
总结
对于一些类库在spring中使用,这种方法实际上是一种通用的模式,实现某个接口或者编写某个注解,然后通过ApplicationContext来获取对应的bean,之后进行某些注册或者组装操作。这样的话,可以让业务的代码,和框架的代码做到一定程度的关注点分离。