- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
在我们的一个应用中,经常会出现一个逻辑执行之后要跟随执行另一个逻辑的情况,例如一个用户创建了后续还需要发送邮件进行通知,或者需要初始化相应的权限等。面对这样的情况,我们当然可以顺序进行相应的逻辑代码的编写,但这样会导致各种业务逻辑全部集中耦合在一个类中,违背了 "单一职责原则".
在 ABP 框架中,对于上面的业务场景的处理,我们可以通过事件总线来解耦,使得代码逻辑实现更加清晰。事件总线的本质就是中介者模式,利用一个中介角色在发送方和接受方之间进行消息的传递,接受方单独实现关注的独立的小功能点,从而达到各块业务逻辑清晰,代码松散耦合的目的.
ABP 框架中的事件总线分为 本地事件总线 和 分布式事件总线 两种,两种使用的方式基本类似,只是分布式事件总线需要借助 RabbitMQ、Kafaka 等第三方消息队列中间件。本章先讲本地事件总线相关知识点.
本地事件总线实现进程内的事件的发布订阅功能,通常运用于单体应用架构或微服务架构中的一个服务内部。使用方式比较简单,以下是演示,也将通过控制台程序来进行.
本地事件总线的实现包含在 Volo.Abp.EventBus Nuget 包中,我们可以通过以下方式来集成.
通过以下命令创建一个控制台项目:
abp new AbpEventBus -t console
在 AbpEventBusSample.csproj 执行以下命令:
Abp add-package Volo.Abp.EventBus
如果是 Web 应用的话,在通过 ABP CLI 初始化启动模板的时候就已经集成了事件总线模块,无须再自己进行集成.
ABP 提供了 ILocalEventBus 接口来满足我们对本地事件总线的使用。我们只需要在要进行事件发布的类注入该接口即可,之后就能通过以下的方式进行事件的发布了.
public class HelloWorldService : ITransientDependency
{
private readonly ILocalEventBus _localEventBus;
public HelloWorldService(ILocalEventBus localEventBus)
{
_localEventBus = localEventBus;
}
public Task SayHelloAsync()
{
// 当前业务逻辑
Console.WriteLine("Hello Jerry!");
// 关联业务逻辑
_localEventBus.PublishAsync(new HelloEventData
{
Who = "Tom",
ToWho = "Jerry",
Where = "广州天河正佳广场",
When = DateTime.Now
});
return Task.CompletedTask;
}
}
在进行事件发布之前需要先定义一个事件对象,这是一个普通类,是事件相关的各种数据的一个包装类,例如上面使用到的 HelloEventData .
public class HelloEventData
{
public string Who { get; set; }
public string ToWho { get; set; }
public string Where { get; set; }
public DateTime When { get; set; }
}
就算在事件发布过程中不需要传输任何数据也需要创建一个类,在这种情况下为空类,这是因为事件总线是通过这个事件对象的类型来确定其对于的订阅者,从而执行相应的处理方法的.
通过源码可以看到,当我们调用 PublishAsync 方法时,最终时调用了 TriggerHandlersAsync 方法,该方法中从 HandlerFactories 中找到相应的的 HandlerFactory,然后通过 IEventHandlerInvoker 执行相应的事件执行器.
最终,就是反射创建了对于的 IEventHandlerMethodExecutor 对象,传入执行器和事件对象,再通过委托调用执行器的 HandleEventAsync 方法.
那么 HandlerFactories 是怎么来的呢?它实际上就是一个事件对象和执行器对应的集合。这里的工厂实际上并不是执行器工厂,它不负责执行器的创建,而是通过执行器生成执行器的包装类(为了类型对象能够释放销毁).
在我们通过 PublishAsync 方法发布事件的时候,还可以通过 onUnitOfWorkComplete 参数设置事件发布是否和工作单元挂钩,实现事件和其他业务的原子性。其实这也很简单,就是结合工作单元的时候,只是将事件存储起来,没有立刻触发.
等到工作单元提交了,再通过 IUnitOfWorkEventPublisher 对象发布,而该接口在事件总线模块中有对于的实现类UnitOfWorkEventPublisher,其实就是工作单元提交时再次发布一次不结合工作单元的事件而已.
事件的订阅有多种方式.
(1) 实现 ILocalEventHandler<TEvent> 接口,并配置到容器 。
public class HelloEventHandler : ILocalEventHandler<HelloEventData>, ITransientDependency
{
public Task HandleEventAsync(HelloEventData eventData)
{
Console.WriteLine($"{eventData.Who} Say Hello To { eventData.ToWho } in { eventData.When } at { eventData.When }");
return Task.CompletedTask;
}
}
这种方式是最简便的方式,ABP 框架中的事件总线模块会在服务配置到容器时自动方向这些订阅者执行器.
从源码中可以看到,事件总线模块中注册了容器中依赖关系配置的拦截器(auto Ioc 容器的功能),在应用启动向容器中配置依赖关系的时候,这里的事件会触发,对每一个配置进行检查,通过接口类型查找到相应的实现类之后,会被保存到 选项 当中.
在 事件总线 构造函数会根据选项中保存的执行器注册订阅 。
实际上这里就是通过执行器实例创建了一个工厂类,并且将其添加到上面讲到的事件对象和执行器对应的集合 HandlerFactories 中,维护好事件对象类型与执行器的对于关系.
(2) 手动调用 ILocalEventBus 接口进行订阅 。
public Task SayHelloAsync()
{
// 当前业务逻辑
Console.WriteLine("Hello Jerry!");
_localEventBus.Subscribe<HelloEventData, HelloLogEventHandler>();
//_localEventBus.Subscribe(new HelloLogEventHandler());
//_localEventBus.Subscribe<HelloEventData>((eventData) => { return Task.CompletedTask; });
// 关联业务逻辑
_localEventBus.PublishAsync(new HelloEventData
{
Who = "Tom",
ToWho = "Jerry",
Where = "广州天河正佳广场",
When = DateTime.Now
});
return Task.CompletedTask;
}
public class HelloLogEventHandler : ILocalEventHandler<HelloEventData>
{
public Task HandleEventAsync(HelloEventData eventData)
{
Console.WriteLine($"Log: {eventData.Who} Say Hello To { eventData.ToWho } in { eventData.When } at { eventData.When }");
return Task.CompletedTask;
}
}
手动注册的订阅者会在调用注册代码之后全局生效,应该保存只有一次的订阅注册.
手动注册订阅者的时候,其实和上面自动注册的方式没太大区别,事件总线会根据我们注册订阅者的方式进行一定的包装,最终也是添加到事件对象和执行器对照的集合.
这些订阅者会在我们调用 ILocalEventBus 的 PublishAsync 方法对相关的事件进行发布之后触发。事件的发布订阅有以下特点:
事件可以由0个或多个处理程序订阅. 。
一个事件处理程序可以订阅多个事件,但是需要为每个事件实现 ILocalEventHandler 接口. 。
如果需要在订阅者执行器中执行数据库操作并且使用到仓储,那可能需要使用工作单元。因为一些存储库方法需要在活动的工作单元中工作。应确保处理方法设置为 virtual,并为该方法添加一个 [UnitOfWork] 特性,或者手动使用 IUnitOfWorkManager 创建一个工作单元范围.
当一个事件发布,订阅的事件处理程序将立即执行,而同时 PublishAsync 如果通过 await 关键字转同步的话,它将阻塞,直到事件处理程序执行完成。换句话说,本地事件总线事件发布与处理实际是立即触发,顺序执行的.
这意味着如果处理程序抛出一个异常,它会影响发布该事件的代码,我们可以在 PublishAsync 调用上捕捉异常。 如果想要隐藏错误,可以在事件处理程序中使用 try-catch。 如果在一个工作单元范围内进行事件发布,那么相应的事件处理程序也会被工作单元覆盖. 这意味着,如果你的 UOW 是事务和处理程序抛出一个异常,事务会回滚.
这从上面列出来的源码中也可以看出来,本地事件总线本质上就还是通过在维护好事件对象类型与执行器对照集合中通过事件对象查找执行器,然后调用执行器中的方法的过程.
实体的增、删、改是非常常见的操作,有些时候一些实体的增、删、改之后需要关联一些其他的业务逻辑,这时候我们可以通过事件总线来解决。ABP框架会为所有的实体自动发布这些事件,我们只需要订阅相关的事件.
sing System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.EventBus;
namespace AbpDemo
{
public class MyHandler
: ILocalEventHandler<EntityCreatedEventData<IdentityUser>>,
ITransientDependency
{
public async Task HandleEventAsync(
EntityCreatedEventData<IdentityUser> eventData)
{
var userName = eventData.Entity.UserName;
var email = eventData.Entity.Email;
//...
}
}
}
上面的例子是 ABP 官方的示例,订阅了 EntityCreatedEventData<Entity> 接口的事件处理程序会在相应的实体创建之后触发。这种和领域对象(实体、聚合根)操作相关的预定义事件有两类:
用过去时态事件 。
当相关工作单元完成且实体更改成功保存到数据库时,将发布带有过去时态的事件. 如果在这些事件处理程序上抛出异常,则无法回滚事务,因为事务已经提交. 事件类型,
EntityCreatedEventData<T>
当实体创建成功后发布. EntityUpdatedEventData<T>
当实体更新成功后发布. EntityDeletedEventData<T>
当实体删除成功后发布. EntityChangedEventData<T>
当实体创建,更新,删除后发布. 如果你需要监听任何类型的更改,它是一种快捷方式 - 而不是订阅单个事件. 用于进行时态事件(6.0版本可用,7.0版本已移除) 。
带有进行时态的事件在完成事务之前发布(如果数据库事务由所使用的数据库提供程序支持). 如果在这些事件处理程序上抛出异常,它会回滚事务,因为事务还没有完成,更改也没有保存到数据库中. 事件类型,
EntityCreatingEventData<T>
当新实体保存到数据库前发布. EntityUpdatingEventData<T>
当已存在实体更新到数据库前发布. EntityDeletingEventData<T>
删除实体前发布. EntityChangingEventData<T>
当实体创建,更新,删除前发布. 如果你需要监听任何类型的更改,它是一种快捷方式 - 而不是订阅单个事件. 它们是在将更改保存到数据库时发布预构建事件,
领域对象中是不能够通过依赖注入注入服务,在聚合根类中我们可以通过 AddLocalEvent 添加本地事件,实体类中则不行,这里添加的事件将在聚合根对象持久化操作的时候发布.
using System;
using Volo.Abp.Domain.Entities;
namespace AbpDemo
{
public class Product : AggregateRoot<Guid>
{
public string Name { get; set; }
public int StockCount { get; private set; }
private Product() { }
public Product(Guid id, string name)
: base(id)
{
Name = name;
}
public void ChangeStockCount(int newCount)
{
StockCount = newCount;
//ADD an EVENT TO BE PUBLISHED
AddLocalEvent(
new StockCountChangedEvent
{
ProductId = Id,
NewCount = newCount
}
);
}
}
}
聚合根中可以通过 AddLocalEvent 方法添加事件,是因为 ABP 框架中的聚合根基类实现了 IGeneratesDomainEvents 接口,如果我们的实体类中也需要发布事件,也可以实现 IGeneratesDomainEvents 接口。但是 ABP 并不建议随意地普通的实体类实现该接口,因为基于 IGeneratesDomainEvents 的事件发布是基于特定的数据库提供程序的,目前 ABP 框架中仅支持 EF Core 、MongoDB 的实现.
通过源码可以看到,这些事件的发布是重写了 SaveChangeAsync 等数据持久化的方法,在其中根据 IGeneratesDomainEvents 接口添加了相应的领域对象的更改操作事件.
最终还是和上面的发布事件时的工作单元操作一样,先添加到工作单元中,在工作单元提交的时候由 IUnitOfWorkEventPublisher 对象发布.
参考文档: ABP 官方文档 - 本地事件总线 。
ABP 系列总结: 目录: ABP 系列总结 上一篇: ABP - 缓存模块(2) 。
最后此篇关于ABP-本地事件总线的文章就讲到这里了,如果你想了解更多关于ABP-本地事件总线的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
目录 总线是什么? 常见总线类型有哪些? 总线的串行和并行的区别? 数据总线 地址总线
从下面的代码我在 map 上添加标记,每 15 秒刷新一次并从数据库中获取新的纬度和经度。标记(巴士图像)已成功添加到 map 上并从一个位置平稳移动到另一个位置,就像汽车在路上行驶一样。现在我想要的
如果在小端处理器上运行的程序将未缓存的值 0xaabbccdd 写入地址 0,并且处理器使用 32 位宽的 AXI4 总线,那么 WDATA 的第 31-24 位是 0xaa 还是 0xdd? AXI
是否存在用于将进程内服务消息传递给另一个服务的扭曲机制?我写了一个原型(prototype)总线,看起来像 from collections import defaultdict ch
我修改了设备树文件并使用 4 个 GPIO 引脚启用了 spi,这些引脚支持 pinmux 并从 gpio 切换到 spi 功能。但是在 Linux 内核代码中,代码如何知道使用了哪个 spi 总线/
我正在使用控创嵌入式计算机通过 I2C 与 ST 微 Controller 通信。我正在使用开发适配器与 I2C 接口(interface),使用描述的简单 read() 和 write() 函数 h
我有一个需要 PEC 的 I2C/SMBus 设备我正在为它编写一个内核空间驱动程序。 在 Linux 2.6.37 上我使用 i2c_board_info实例化客户端并在那里设置标志,但现在驱动程序
我想确认我的消息已经通过 socketCAN 库保存在 CAN 总线上。socketCAN 文档描述了使用 recvmsg() 函数时的这种可能性,我对其实现有疑问。 我要实现的功能是确认我的消息在仲
下面是我的代码 #import #import int main(int argc, const char *argv[]) { char *str = "First string";
在大量使用 D-Bus 的应用程序中获得更好的时间性能有哪些好的做法? ? 以下是我们的团队通过硬敲学校学到的一些知识: 尝试将数据实体组合成一个单一的大型结构/对象,以通过 D-Bus IPC 发送
我正在Akka内核下运行Akka应用程序,该程序在其他系统上也可以正常工作。 akka { loggers = ["akka.event.slf4j.Slf4jLogger"] log
就目前情况而言,这个问题不太适合我们的问答形式。我们希望答案得到事实、引用资料或专业知识的支持,但这个问题可能会引发辩论、争论、民意调查或扩展讨论。如果您觉得这个问题可以改进并可能重新开放,visit
我是 C 语言新手,对于家庭作业考试,我必须实现一个简单的服务器套接字程序,该程序在循环中发送一些数据,并且如果客户端连接到服务器套接字(已使用 Arduino 完成,但需要相同的功能)在 raspb
我正在构建一个由许多(> 100)个相同节点组成的系统,所有节点均通过 CAN 总线连接。这个想法是所有节点必须具有相同的信息,任何节点都可以生成事件并通过 CAN 广播它。对于这些事件,CAN 帧提
您好,我在解析 IIB Toolkit 中的任何 JSON 时遇到问题。 java计算节点抛出的异常为:java.lang.NoClassDefFoundError: org.json.JSONObj
我买了这个传感器: http://dlnmh9ip6v2uc.cloudfront.net/datasheets/Sensors/Weather/RHT03.pdf 输出为“MaxDetect 1-w
我正在用户空间编写包装器 API,用于在嵌入式 Linux 平台上用 C 语言控制 I2C 总线。我能够使用 read() 和 write() 方法以及 ioctl() 调用来选择从属设备,从而从传感
在我的软件(用 C++ 编写)中,我使用 Linux 标准函数打开 CAN 总线套接字并执行 I/O 操作。 套接字的打开和使用如下: /* Create the socket */ if ((
每天——大约 5 到 10 次——我的 USB 摄像头从系统中消失。它从第一天开始就发生了,因为制造商驱动程序与 Linux 不兼容。 lsusb 和 dmesg 一开始正确显示,但在较长时间后有时会
我分发了包含多个 Go 服务的应用程序。其中一些使用 Kafka 作为数据总线。我能够使用 Jaeger 的 opentracing 追踪服务之间的调用。我在图表上绘制 Kafka 跨度时遇到问题,它
我是一名优秀的程序员,十分优秀!