介绍
EventBus是一类自动事件处理器,也叫事件总线,基于观察者设计模式,使得某些任务能够得到自动处理,例如添加审批日志。
提供了发布-订阅模型,可以方便的在EventBus上注册订阅者,发布者可以简单的将事件传递给EventBus,EventBus会自动将事件传递给相关联的订阅者,只能用于线程间通信。
跟线程池的区别
- 事件总线可以有多个订阅者,线程池只会有一个执行逻辑
- 事件总线底层就是基于线程池
事件总线的优点
- 编程方便
- 支持同步/异步模式
- 与aop来比更加灵活
事件总线的缺点
- 基于内存,如果断电,就会丢失事件
- 只能单进程使用
- 代码过于分散,不方便调试
使用方法
// 事件总线的定义
public class EventBusCenter {
private static EventBus eventBus = new EventBus();
private EventBusCenter() {
}
public static EventBus getInstance() {
return eventBus;
}
public static void register(Object obj) {
eventBus.register(obj);
}
public static void unregister(Object obj) {
eventBus.unregister(obj);
}
public static void post(Object obj) {
eventBus.post(obj);
}
}
// Consumer
public class DataObserver {
/**
* 只有通过@Subscribe注解的方法才会被注册进EventBus
* 而且方法有且只能有1个参数
*/
@Subscribe
public void func(String msg) {
System.out.println("String msg: " + msg);
}
}
// Provider
public class Test {
public static void main(String[] args) throws InterruptedException {
DataObserver observer = new DataObserver1();
EventBusCenter.register(observer);
// 只有注册的参数类型为String的方法会被调用
EventBusCenter.post("post string method");
EventBusCenter.post(123);
}
}
上述代码分别是事件总线的定义,消费者和提供者,代码逻辑都很简单,但是,实际开发中,会给代码解耦。
实际开发中,类图如下
/**
* 事件管理接口,提供消费者订阅、事件投递,作为顶层的接口
*/
public interface IEventBus {
/**
* 发布异步事件
*
* @param event 事件实体
*/
void asyncPost(Object event);
/**
* 发布同步事件
*
* @param event 事件实体
*/
void syncPost(Object event);
/**
* 添加消费者
*
* @param obj 消费者对象,默认以class为key
*/
void addConsumer(Object obj);
/**
* 移除消费者
*
* @param obj 消费者对象,默认以class为key
*/
void removeConsumer(Object obj);
/**
* 扫描消费者
*
* @param packageName 扫描包
*/
void scanConsumer(String packageName);
}
上面就是顶层的事件接口
/**
* Guava EventBus和Spring的桥梁
*/
public abstract class AbstractSpringEventBus implements IEventBus, ApplicationContextAware {
private ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
this.scanConsumer(null);
}
@Override
public void scanConsumer(String packageName) {
context.getBeansOfType(IEventConsumer.class).forEach((k, v) -> {
this.addConsumer(v);
});
}
}
上面的代码很有趣,首先是抽象类实现接口, 不需要实现所有的接口方法
- 实现
ApplicationContextAware
接口,这是Bean初始化第一步的过程,Bean还没有完全初始化,也就是在Spring启动的时候setApplicationContext
会被调用,在其中初始化了应用上下文,并且调用scanConsumer方法扫描消费者的Bean,并注册到事件总线上。 - 实现
IEventBus
接口中的scanConsumer
,为第一步提供服务。
当然抽象类还是过于抽象了,实际使用可能还需要分异步和同步事件,况且AbstractSpringEventBus
还没有实现addConsumer方法,所以在不同的业务中需要定义不同的实现,如下
/**
* 业务事件总线
*/
@Component
@Slf4j
public class InvoiceEventBus extends AbstractSpringEventBus {
private final EventBus asyncEventBus;
private final EventBus syncEventBus;
public InvoiceEventBus() {
//异步事件配置线程池
asyncEventBus = new AsyncEventBus(new ThreadPoolExecutor(4, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<>(),
new ThreadFactoryBuilder().setNameFormat("invoiceAsyncEventBus-thread-%d").build()),
new EventBusSubscriberExceptionHandler());
// 同步事件配置
syncEventBus = new EventBus("SyncEventBus");
}
@Override
public void asyncPost(Object event) {
asyncEventBus.post(event);
}
@Override
public void syncPost(Object event) {
syncEventBus.post(event);
}
@Override
public void addConsumer(Object obj) {
asyncEventBus.register(obj);
syncEventBus.register(obj);
}
@Override
public void removeConsumer(Object obj) {
asyncEventBus.unregister(obj);
syncEventBus.unregister(obj);
}
}
至此,上面的代码做好了所有消费者注册的工作,所以后面扩展只需要实现IEventConsumer
,就会被注册,就会监听事件
public interface IEventConsumer {
/**
* 消费者事件
* @param event 事件
*/
void consumer(T event);
}
接口如上所示,实现如下
/**
* 状态更改事件执行器
*/
@Slf4j
@Component
public class StatusExecutor implements IEventConsumer {
@Subscribe
@Override
public void consumer(StatusChangeEvent statusChangeEvent) {
log.info("状态变更事件");
// 业务代码
}
}
}
注意到泛型是StatusChangeEvent
,也就是说,只要post了这个类型的对象,内部业务代码就会执行.
post逻辑就简单了
@Slf4j
@Service
public class InvoiceRequisitionService {
@Resource
private InvoiceEventBus invoiceEventBus;
// 更新service
// 显然,这里更改了具体的Object,但是可以看到代码的层次关系是很漂亮的。
public Boolean update(Object object) {
invoiceEventBus.syncPost(object);
return true;
}
}