Reface.AppStarter 中的事件总线功能是通过 Reface.EventBus 提供的。

参考文章 : Reface.AppStarter 框架初探

使用 Reface.EventBus ,你可以在 Reface.AppStarter 框架外使用事件总线的功能。

Reface.EventBus 提供了两个版本的包 Reface.EventBusReface.Core.EventBus ,分别工作在 .Net Framework.Net Core 平台上。

除了一些细节的不同,这两个版本的使用方法完全相同。

基本篇

下面我们一起来了解如何使用 Reface.EventBus定义事件定义监听器注册监听器发布事件

定义事件

事件是一个单独类型,它继承于 Reface.EventBus.Event其子类 , 我们先看看 Event 的定义

    public class Event
{
public object Source { get; private set; } public Dictionary<string, object> Context { get; private set; } public Event(object source)
{
if (source == null)
throw new ArgumentNullException("source");
Source = source;
this.Context = new Dictionary<string, object>();
}
}

Event 要求一个 object 类型的构造函数,用来表示事件是由谁发布的,与 .Net 中的 event 模式中的 sender 含义相同。

属性 Source

从构造函数可以看出 , 可以通过属性 Source 获取到事件的发布者。

属性 Context

正如其名,是事件的上文对象,你可以向其中存取任何你希望的内容。该属性可以成为多个 监听者 之间 沟通 的桥梁。

使用该属性时需要注意 :

  • 监听者模式情况下无论控制执行顺序,因此要考虑到当前监听者处理时,某些上下文的内容尚未创建
  • 如果你的监听者对上下文有着严格的前后 顺序要求,请为监听器添加优先级的功能 (后文有介绍)

示例

我们定义一个事件,可以在控制台程序中,描述一个用户输入了一个命令的事件。

public class CommandReadEvent : Event
{
public string Command { get; private set; }
public CommandReadEvent(object source, string cmd) : base(source)
{
this.Command = cmd;
}
}

定义监听器

所有的监听器必须实现 IEventListener<TEvent> 接口,IEventHandler<T> 的定义如下

public interface IEventListener<TEvent>
where TEvent : Event
{
void Handle(TEvent @event);
}

泛型 TEvent

泛型指定了该监听器监听哪种事件类型的事件。

职责单一 是重要的设计思想。因此,一件监听器只能针对一个事件类型进行处理。

方法 Handler

该方法是事件处理的具体过程,参数就是事件类型。

同样,我们依照 职责单一 的原则,强烈建议一个监听器内只实现一个任务,多个任务由多个监听器实现。

监听器在哪儿 ?

EventBus 无法得知开发者把它们的 监听器 都写在哪儿,所以需要以某种方式发现它们,才能调度它们。

IEventListenerFinder 就是负责找到这些 监听器 并创建它们实例的组件。

关于 监听器 的创建

所有的 监听器 都是通过 IEventListenerFinder 创建的。

.Net Framework 版本中所有的 IEventListenerFinder 都不支持 IOC / DI , 如果你希望对 监听器 注入外部组件,可以考虑和成熟的 IOC / DI 库集成,比如 AutofacInject 等等。

.Net Core 版本中,已经实现了与 ServiceCollection 的集成,当你编写 Web

项目时,你可以在 Startup.ConfigureService 中启用 EventBus 的功能以及对程序集内的所有 监听器 的发现与注册。

以下是在 .Net Core 版本中,如何启用 EventBus 以及对 监听器 的发现与注册

services // ServiceCollection 的实例
.AddEventBus() // 注册运行 EventBus 的必要组件
.AddEventListeners(typeof(SomeClass).Assembly); // 扫描指定程序集内的 监听器 并注册到容器中
在 .Net Framework 中注册监听器

强烈建议将你的 IEventListenerFinderIOC / DI 容器集成。

但是,如果你没有这样的需求,你也可以通过以下两种方式向 EventBus 注册你的 监听器

1. 通过 config 文件注册监听器

首先,将 config* 文件添加 section 节点,建议使用 eventBus 作为 name 的值。

<section name="eventBus" type="Reface.EventBus.Configuration.EventBusSection, Reface.EventBus"/>

接下来在 configuration 节点内,添加 eventBus 节点

<configuration>
<eventBus>
<listeners>
<add type="Demo.Listeners.OnConsoleStarted02, Demo" />
<add type="Demo.Listeners.OnConsoleStarted01, Demo" />
<add type="更多的监听器类型名称" />
</listeners>
</eventBus>
</configuration>

类型名称应由 类型全名+逗号+程序集名 组成。

配置文件是 EventBus 默认的选项,你不需要做任何额外的编码,就可以发布事件了。

IEventBus eventBus = new EventBus();
eventBus.Publish(new MyEvent());

EventBus 发布事件时,会通过 Activator.CreateInstance(Type) 的方式创建这些 监听者,因此你的 监听者 的构造函数不可以有参数。

2. 扫描程序集以发现监听器

通过这种方式,你可以指定一些程序集,EventBus 会预先扫描它们,并将实现了 IEventListener 接口的类型记录。

config 模块相同,监听器 是通过 Activator.CreateInstance(Type) 的方式创建的,因此 监听者 的构造函数依然不可以有参数。

发布事件

IEventBus 是发布事件的组件,它只包含一个成员,Publish 用于发布事件。

public interface IEventBus
{
void Publish(Event @event);
}

我会在未来的版本中加入 AsyncPublish(Event) 方法,以用于异步发布事件。

库中只有一个 IEventBus 的实现类 : EventBus

该类是依赖 IEventListenerFinder 发现 监听器 并发布 事件

你也可以实现属于你自己的 IEventBus ,你可以让它通过 MQ 等其它方式来发布事件。

扩展篇

监听者的执行顺序

一个 Event 可以触发多个 监听器 的执行。在正常情况下,EventBus 只以 注册的顺序扫描程序集的顺序 来决定 监听器 的执行顺序。

在实际开发环境中,如果你对 监听器 的执行顺序有要求,比如,AListener 必须早于 BListener , 你可以选择以下两个解决方案

  • 设计一个新的事件,当 AListener 完成后触发,BListener 监听新的事件
  • AListenerBListener 实现优先级接口,并让 AListener 优于 BListener

库内有接口 IPrioritized , 它可以让你的 监听器 额外提供一个 优先级 的属性,这是一个 int 型的属性,数值越小,就越先执行。

对于未实现 IPrioritized监听器,优先级一律是 int.MaxValue

public class AListener : IEventListener<SomeEvent>, IPrioritized
{
public int Priority => 0; public void Handle(SomeEvent @event)
{
Console.WriteLine("Console Started 01");
}
}

Event 的继承

使用 Reface.EventBus ,你可以设计具有继承关系的 Event 类型。

比如

// 删除数据的事件
public class DataDeletingEvent : Event {} // 删除用户的事件
public class UserDeletingEvent : DataDeletingEvent {} // 删除部门的事件
public class DeptDeletingEvent : DataDeletingEvent {}

当你对事件监听时,你可以编写一个 监听器 来监听 DataDeletingEvent

public class DataDeletingEventListener : IEventListener<DataDeletingEvent>
{ }

该处理器可以处理所有 DataDeletingEvent其子类 的事件。

运行机制

Publish(TEvent) 时,都发生了什么事 ?

  1. 调用 IEventListenerFinder.CreateAllEventListeners() 获取所有事件 处理器
  2. 对所有的 处理器 进行排序
  • 没有标记 IPrioritized 的处理器优先级为 int.MaxValue
  • 排序按 Priority 从小到大排序
  1. 缓存中获取分析 哪些 处理器 将参与此次 事件 的处理
  2. 对参与此次 事件处理器 从缓存或创建 DynamicMethod 用于执行 Handle 方法 , DynamicMethod 是通过 Emit 创建的,因此执行速度是非常快的
  3. 执行所有的 DynamicMethod

相关链接

事件总线功能库,Reface.EventBus 详细使用教程的更多相关文章

  1. Android事件总线分发库EventBus3.0的简单讲解与实践

    Android事件总线分发库EventBus的简单讲解与实践 导语,EventBus大家应该不陌生,EventBus是一款针对Android优化的发布/订阅事件总线.主要功能是替代Intent,Han ...

  2. 45、Android事件总线分发库的使用

    事件总线分发库EventBus和Otto的简介及对比 什么是事件总线管理: a.将事件放到队列里,用于管理和分发b.保证应用的各个部分之间高效的通信及数据.事件分发c.模块间解耦 Event Bus是 ...

  3. 基于ASP.NET Core 5.0使用RabbitMQ消息队列实现事件总线(EventBus)

    文章阅读请前先参考看一下 https://www.cnblogs.com/hudean/p/13858285.html 安装RabbitMQ消息队列软件与了解C#中如何使用RabbitMQ 和 htt ...

  4. C# 事件总线 EventBus

    1. 引言 事件总线这个概念对你来说可能很陌生,但提到观察者(发布-订阅)模式,你也许就很熟悉.事件总线是对发布-订阅模式的一种实现.它是一种集中式事件处理机制,允许不同的组件之间进行彼此通信而又不需 ...

  5. SOFA 源码分析— 事件总线

    前言 大部分框架都是事件订阅功能,即观察者模式,或者叫事件机制.通过订阅某个事件,当触发事件时,回调某个方法.该功能非常的好用,而 SOFA 内部也设计了这个功能,并且内部大量使用了该功能.来看看是如 ...

  6. .Net 事件总线之Autofac解耦

    事件总线是通过一个中间服务,剥离了常规事件的发布与订阅(消费)强依赖关系的一种技术实现.事件总线的基础知识可参考圣杰的博客[事件总线知多少] 本片博客不再详细概述事件总线基础知识,核心点放置使用Aut ...

  7. Autofac解耦事件总线

    事件总线之Autofac解耦 事件总线是通过一个中间服务,剥离了常规事件的发布与订阅(消费)强依赖关系的一种技术实现.事件总线的基础知识可参考圣杰的博客[事件总线知多少] 本片博客不再详细概述事件总线 ...

  8. 浅入 ABP 系列(4):事件总线

    浅入 ABP 系列(4):事件总线 版权护体作者:痴者工良,微信公众号转载文章需要 <NCC开源社区>同意. 目录 浅入 ABP 系列(4):事件总线 事件总线 关于事件总线 为什么需要这 ...

  9. 事件总线(Event Bus)知多少

    源码路径:Github-EventBus 简书同步链接 1. 引言 事件总线这个概念对你来说可能很陌生,但提到观察者(发布-订阅)模式,你也许就很熟悉.事件总线是对发布-订阅模式的一种实现.它是一种集 ...

随机推荐

  1. 人生苦短,学用python

    1. 我为什么开始学着用 python 啦?   扯扯网上疯传的一组图片.网上流传<人工智能实验教材>的图片,为幼儿园的小朋友们量身打造的实验教材,可谓是火了.甚至有网友调侃道:pytho ...

  2. Java toString解析

    Java toString解析 我们知道,Java所有的类都是Object类的派生类,toString就是Object类中的一个方法. 我们看看它的源代码. public String toStrin ...

  3. java/php DES/CBC/PKCS5Padding加密解密算法实现过程

    先看java代码 public static String encrypt(String message, String key) throws Exception { Cipher cipher = ...

  4. Jmeter 压力测试笔记(2)--问题定位

    事情已经出了,是该想办法解决的时候了. 经过运维和DBA定位: 数据库读写分离中,读库延时超过了30秒,导致所有请求都压在主库.另外所有数据库都连接数都被占满,但活跃请求数量缺不多. 数据库16K的连 ...

  5. .NET项目升级手记:可为空引用

    c# 8引入了新特性:"可为空引用"(详情),这个功能个人觉得挺好的,能够非常明确的表现程序设计者的意图,编译器能够进行检查,尽最大可能减小NullReferenceExcepti ...

  6. 基于 Jepsen 来发现几个 Raft 实现中的一致性问题(2)

    Nebula Graph 是一个高性能.高可用.强一致的分布式图数据库.由于 Nebula Graph 采用的是存储计算分离架构,在存储层实际只是暴露了简单的 kv 接口,采用 RocksDB 作为状 ...

  7. vue-cli3 按需引入 element-ui 报错

    报错信息: Cannot find module 'babel-preset-es2015' from .... 解决办法: 安装最新的 Babel 编译插件:@babel/preset-env 修改 ...

  8. Pandownload作者被抓之后

    近日,pandownload作者被抓,可以说是圈内的大事件,被抓之后, Pandownload 已经是打不开,用不了了 就在我为此感到惋惜的时候,竟然有出来个shengdownload 先来一块看看这 ...

  9. EFCore.Sharding(EFCore开源分表框架)

    EFCore.Sharding(EFCore开源分表框架) 简介 引言 开始 准备 配置 使用 按时间自动分表 性能测试 其它简单操作(非Sharing) 总结 简介 本框架旨在为EF Core提供S ...

  10. Cucumber(2)——目录结构以及基本语法

    目录 回顾 HelloWorld 扩展 回顾 在上一节中,我大致的介绍了一下cucumber的特点,以及基于ruby和JavaScript下关于cucumber环境的配置,如果你还没有进行相关的了解或 ...