22. 事件总线
22.1 什么是事件总线
事件总线是对发布-订阅模式的一种实现。它是一种集中式事件处理机制,允许不同的组件之间进行彼此通信而又不需要相互依赖,达到一种解耦的目的。
我们来看看事件总线的处理流程:
22.2 MessageCenter
消息中心
在 Furion
框架中,实现了一种轻量级的事件总线实现机制:MessageCenter
(消息中心),MessageCenter
采用字符串消息机制进行广播, 可以在绝大多数中小型项目中发挥作用,缺点是消息处理是在主线程中完成并且消息不支持分布式存储。
另外,MessageCenter
支持单播、多播发布及多订阅。如图所示:
22.2.1 注册 轻量级事件总线服务
如果想使用 MessageCenter
轻量级事件总线,只需要在 Startup.cs
中注册服务即可,如:
public void ConfigureServices(IServiceCollection services)
{
services.AddSimpleEventBus();
}
22.2.2 定义订阅处理程序
MessageCenter
是根据 MesseageId
消息 Id 来触发对应的处理程序的,所以我们需要定义 订阅处理程序类
,该类需实现 ISubscribeHandler
接口,如:
public class UserChangeSubscribeHandler : ISubscribeHandler
{
// 定义一条消息
[SubscribeMessage("create:user")]
public void CreateUser(string eventId, object payload)
{
Console.WriteLine("我是"+eventId);
}
// 多条消息共用同一个处理程序
[SubscribeMessage("delete:user")]
[SubscribeMessage("remove:user")]
public void RemoveUser(string eventId, object payload)
{
Console.WriteLine("我是"+eventId);
}
// 支持异步
[SubscribeMessage("delete:user")]
public async Task SupportAsync(string eventId, object payload)
{
await MethodAsync();
}
}
22.2.3 发布消息
定义好订阅处理程序后,我们就可以在程序任何地方进行广播消息,事件总线会自动根据 消息 Id
触发对应的处理程序方法:
MessageCenter.Send("create:user", new User {}); // => 打印:我是create:user
MessageCenter.Send("delete:user", new User {}); // => 打印:我是delete:user
MessageCenter.Send("remove:user", new User {}); // => 打印:我是remove:user
22.2.4 直接订阅消息
在上面的例子中,我们需要创建 ISubscribeHandler
的派生类进行相关配置才能实现订阅处理。
在某些特殊情况下,可能只需要订阅一次即可。所以,在 Furion
框架中,为了更简便的订阅消息,也提供了 MessageCenter.Subscribe<T>
静态方法,如:
MessageCenter.Subscribe<User>("create:user", (i,p) => {
// do something。。。
});
22.3 同步方式执行
默认情况下,事件总线总是采用新线程方式执行,但是我们可以配置为同步方式,如:
MessageCenter.Send("create:user", isSync: true);
22.4 关于依赖注入
在 Furion
框架中,事件总线是不支持构造函数注入的,而且构造函数也只会执行一次。所以需要用到服务,应该通过静态类解析,App.GetService<xx>()
或 Db.GetRepository<XX>()
。
public class UserChangeSubscribeHandler : ISubscribeHandler
{
public UserChangeSubscribeHandler()
{
// 不支持这里解析服务!!!!!!!!!!!
}
// 定义一条消息
[SubscribeMessage("create:user")]
public void CreateUser(string eventId, object payload)
{
// 创建一个作用域,对象使用完成自动释放
Scoped.Create((_, scope) =>
{
var services = scope.ServiceProvider;
var repository = Db.GetRepository<Person>(services); // services 传递进去
var someService = App.GetService<ISomeService>(services); // services 传递进去
var otherService = services.GetService<IOtherService>(); // 直接用 services 解析
});
}
}
App.GetService<TService>()
解析服务在高频定时任务中调用App.GetService(TService)
,可能会出现内存无法回收的情况,建议始终使用scope.ServiceProvider.GetService(TService)
来获取TService
如果作用域中对数据库有任何变更操作,需手动调用 SaveChanges
或带 Now
结尾的方法。也可以使用 Scoped.CreateUow(handler)
代替。
ISubscribeHandler
接口主要是用来查找定义事件对象的,也就是它的实现类并未提供依赖注入功能,所以在实现类并不支持构造函数注入依赖项。
22.5 反馈与建议
给 Furion 提 Issue。