基于.NetStandard的简易EventBus实现-基础实现
一、问题背景
最近离职来到了一家新的公司,原先是在乙方工作,这回到了甲方,在这一个月中,发现目前的业务很大一部分是靠轮询实现的,例如:通过轮询判断数据处于B状态了,则轮询到数据后执行某种动作,这个其实是非常浪费的,并且对于数据的实时性也会不怎么友好,基于以上的情况,在某天开车堵车时候,想到了之前偶然了解过的事件总线(EventBus),对比了公司当前的场景后,觉得事件总线应该是可以满足需求的(PS:只是我觉得这个有问题,很多人不觉得有问题),那既然想到了,那就想自己是否可以做个事件总线的轮子
二、什么是事件总线
我们知道事件是由一个Publisher跟一个或多个的Subsriber组成,但是在实际的使用过程中,我们会发现,Subsriber必须知道Publisher是谁才可以注册事件,进而达到目的,那这其实就是一种耦合,为了解决这个问题,就出现了事件总线的模式,事件总线允许不同的模块之间进行彼此通信而又不需要相互依赖,如下图所示,通过EventBus,让Publisher以及Subsriber都只需要对事件源(EventData)进行关注,不用管Publisher是谁,那么EventBus主要是做了一些什么事呢?
三、EventBus做了什么事?
1、EventBus实现了对于事件的注册以及取消注册的管理
2、EventBus内部维护了一份事件源与事件处理程序的对应关系,并且通过这个对应关系在事件发布的时候可以找到对应的处理程序去执行
3、EventBus应该要支持默认就注册事件源与处理程序的关系,而不需要开发人员手动去注册(这里可以让开发人员去控制自动还是手动)
四、具体实现思路
首先在事件总线中,存在注册、取消注册以及触发事件这三种行为,所以我们可以将这三种行为抽象一个接口出来,最终的接口代码如下:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks; namespace MEventBus.Core
{
public interface IEventBus
{
#region 接口注册
void Register<TEventData>(Type handlerType) where TEventData : IEventData;
void Register(Type eventType, Type handlerType);
void Register(string eventType, Type handlerType);
#endregion #region 接口取消注册
void Unregister<TEventData>(Type handler) where TEventData : IEventData;
void Unregister(Type eventType, Type handlerType);
void Unregister(string eventType, Type handlerType);
#endregion void Trigger(string pubKey, IEventData eventData);
Task TriggerAsync(string pubKey, IEventData eventData);
Task TriggerAsync<TEventData>(TEventData eventData) where TEventData : IEventData;
void Trigger<TEventData>(TEventData eventData) where TEventData : IEventData;
}
}
在以上代码中发现有些方法是有IEventData约束的,这边IEventData就是约束入参行为,原则上规定,每次触发的EventData都需要继承IEventData,而注册的行为也是直接跟入参类型相关,具体代码如下:
using System;
using System.Collections.Generic;
using System.Text; namespace MEventBus.Core
{
public interface IEventData
{
string Id { get; set; }
DateTime EventTime { get; set; }
object EventSource { get; set; }
}
}
接下来我们看下具体的实现代码
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks; namespace MEventBus.Core
{
public class EventBus : IEventBus
{
private static ConcurrentDictionary<string, List<Type>> dicEvent = new ConcurrentDictionary<string, List<Type>>();
private IResolve _iresolve { get; set; }
public EventBus(IResolve resolve)
{
_iresolve = resolve;
InitRegister();
} public void InitRegister()
{
if (dicEvent.Count > 0)
{
return;
}
//_iresolve = ioc_container;
dicEvent = new ConcurrentDictionary<string, List<Type>>();
//自动扫描类型并且注册
foreach (var file in Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll"))
{
var ass = Assembly.LoadFrom(file);
foreach (var item in ass.GetTypes().Where(p => p.GetInterfaces().Contains(typeof(IEventHandler))))
{
if (item.IsClass)
{
foreach (var item1 in item.GetInterfaces())
{
foreach (var item2 in item1.GetGenericArguments())
{
if (item2.GetInterfaces().Contains(typeof(IEventData)))
{
Register(item2, item);
}
}
}
}
}
}
}
//注册以及取消注册的时候需要加锁处理
private static readonly object obj = new object(); #region 注册事件
public void Register<TEventData>(Type handlerType) where TEventData : IEventData
{
//将数据存储到mapDic
var dataType = typeof(TEventData).FullName;
Register(dataType, handlerType);
}
public void Register(Type eventType, Type handlerType)
{
var dataType = eventType.FullName;
Register(dataType, handlerType);
}
public void Register(string pubKey, Type handlerType)
{
lock (obj)
{
//将数据存储到dicEvent
if (dicEvent.Keys.Contains(pubKey) == false)
{
dicEvent[pubKey] = new List<Type>();
}
if (dicEvent[pubKey].Exists(p => p.GetType() == handlerType) == false)
{
//IEventHandler obj = Activator.CreateInstance(handlerType) as IEventHandler;
dicEvent[pubKey].Add(handlerType);
}
}
} #endregion #region 取消事件注册
public void Unregister<TEventData>(Type handler) where TEventData : IEventData
{
var dataType = typeof(TEventData);
Unregister(dataType, handler);
} public void Unregister(Type eventType, Type handlerType)
{
string _key = eventType.FullName;
Unregister(_key, handlerType);
}
public void Unregister(string eventType, Type handlerType)
{
lock (obj)
{
if (dicEvent.Keys.Contains(eventType))
{
if (dicEvent[eventType].Exists(p => p.GetType() == handlerType))
{
dicEvent[eventType].Remove(dicEvent[eventType].Find(p => p.GetType() == handlerType));
}
}
}
}
#endregion #region Trigger触发
//trigger时候需要记录到数据库
public void Trigger<TEventData>(TEventData eventData) where TEventData : IEventData
{
var dataType = eventData.GetType().FullName;
//获取当前的EventData绑定的所有Handler
Notify(dataType, eventData);
} public void Trigger(string pubKey, IEventData eventData)
{
//获取当前的EventData绑定的所有Handler
Notify(pubKey, eventData);
}
public async Task TriggerAsync<TEventData>(TEventData eventData) where TEventData : IEventData
{
await Task.Factory.StartNew(new Action(()=>
{
var dataType = eventData.GetType().FullName;
Notify(dataType, eventData);
}));
}
public async Task TriggerAsync(string pubKey, IEventData eventData)
{
await Task.Factory.StartNew(new Action(() =>
{
var dataType = eventData.GetType().FullName;
Notify(pubKey, eventData);
}));
}
//通知每成功执行一个就需要记录到数据库
private void Notify<TEventData>(string eventType, TEventData eventData) where TEventData : IEventData
{
//获取当前的EventData绑定的所有Handler
var handlerTypes = dicEvent[eventType];
foreach (var handlerType in handlerTypes)
{
var resolveObj = _iresolve.Resolve(handlerType);
IEventHandler<TEventData> handler = resolveObj as IEventHandler<TEventData>;
handler.Handle(eventData); }
}
#endregion
}
}
代码说明:
1、如上的EventBus是继承了IEventBus后的具体实现,小伙伴可能看到在构造函数里,有一个接口参数IResolve,这个主要是为了将解析的过程进行解耦,由于在一些WebApi的项目中,更加多的是使用IOC的机制进行对象的创建,那基于IResolve就可以实现不同的对象创建方式(内置的是通过反射实现)
2、InitRegister方法通过遍历当前目录下的dll文件,去寻找所有实现了IEventHandler<IEventData>接口的信息,并且自动注册到EventBus中,所以在实际使用过程中,应该是没有机会去适用register注册的
3、触发机制实现了同步以及异步的调用,这个从方法命名中就可以看出来
五、程序Demo
TestHandler2(继承IEventHandler)
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using MEventBus.Core; namespace MEventBusHandler.Test
{
public class TestHandler2 : IEventHandler<TestEventData>
{
public void Handle(TestEventData eventData)
{
Thread.Sleep(2000);
MessageBox.Show(eventData.EventTime.ToString());
}
}
}
TestEventData(继承EventData,EventData是继承了IEventData的代码)
using MEventBus.Core;
using System;
using System.Collections.Generic;
using System.Text; namespace MEventBusHandler.Test
{
public class TestEventData : EventData
{ }
}
调用代码
using MEventBus.Core;
using MEventBusHandler.Test;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms; namespace MEventBus.Test
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
TestHandler.OnOut += TestHandler_OnOut;
} private void TestHandler_OnOut(object sender, EventArgs e)
{
MessageBox.Show("Hello World");
} private void button1_Click(object sender, EventArgs e)
{
var task = new MEventBus.Core.EventBus(new ReflectResolve()).TriggerAsync(new TestEventData());
task.ContinueWith((obj) => {
MessageBox.Show("事情全部做完");
});
} private void button2_Click(object sender, EventArgs e)
{
new EventBus(new ReflectResolve()).Trigger(new TestEventData());
}
} }
执行结果
我在真正的Demo中,其实是注册了2个handler,可以在后续公布的项目地址里看到
六、总结
从有这个想法开始,到最终实现这个事件总线,大概总共花了2,3天的时间(PS:晚上回家独自默默干活),目前只能说是有一个初步可以使用的版本,并且还存在着一些问题:
1、在.NetFrameWork下(目前公司还不想升级到.NetCore,吐血。。),如果使用AutoFac创建EventBus(单例模式下),如果Handler也使用AutoFac进行创建,会出现要么对象创建失败,要么handler里的对象与调用方的对象不是同一个实例,为了解决这个问题,我让EventBus不再是单例模式,将dicEvent变成了静态,暂时表面解决
2、未考虑跨进程的实现(感觉用savorboard大佬的CAP就可以了)
3、目前这个东西在一个小的新项目里使用,暂时在测试环境还算没啥问题,各位小伙伴如果有类似需求,可以做个参考
由于个人原因,在测试上可能会有所不够,如果有什么bug的话,还请站内信告知,感谢(ps:文字表达弱鸡,技术渣渣,各位多多包涵)
最后:附上项目地址:https://gitee.com/OneMango/MEventBus
作者: Mango
出处: http://www.cnblogs.com/OMango/
关于自己:专注.Net桌面开发以及Web后台开发,对.NetCore、微服务、DevOps,K8S等感兴趣,最近到了个甲方公司准备休养一段时间
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,如有问题, 可站内信告知.
基于.NetStandard的简易EventBus实现-基础实现的更多相关文章
- 基于redis的简易分布式爬虫框架
代码地址如下:http://www.demodashi.com/demo/13338.html 开发环境 Python 3.6 Requests Redis 3.2.100 Pycharm(非必需,但 ...
- 基于RxJava2+Retrofit2精心打造的Android基础框架
代码地址如下:http://www.demodashi.com/demo/12132.html XSnow 基于RxJava2+Retrofit2精心打造的Android基础框架,包含网络.上传.下载 ...
- Android消息传递之基于RxJava实现一个EventBus - RxBus
前言: 上篇文章学习了Android事件总线管理开源框架EventBus,EventBus的出现大大降低了开发成本以及开发难度,今天我们就利用目前大红大紫的RxJava来实现一下类似EventBus事 ...
- 面向服务体系架构(SOA)和数据仓库(DW)的思考基于 IBM 产品体系搭建基于 SOA 和 DW 的企业基础架构平台
面向服务体系架构(SOA)和数据仓库(DW)的思考 基于 IBM 产品体系搭建基于 SOA 和 DW 的企业基础架构平台 当前业界对面向服务体系架构(SOA)和数据仓库(Data Warehouse, ...
- 基于Android 平台简易即时通讯的研究与设计[转]
摘要:论文简单介绍Android 平台的特性,主要阐述了基于Android 平台简易即时通讯(IM)的作用和功能以及实现方法.(复杂的通讯如引入视频音频等可以考虑AnyChat SDK~)关键词:An ...
- 基于.netstandard的权限控制组件
基于.netstandard的权限控制组件 Intro 由于项目需要,需要在 基于 Asp.net mvc 的 Web 项目框架中做权限的控制,于是才有了这个权限控制组件. 项目基于 .NETStan ...
- 如何基于Winform开发框架或混合框架基础上进行项目的快速开发
在开发项目的时候,我们为了提高速度和质量,往往不是白手起家,需要基于一定的基础上进行项目的快速开发,这样可以利用整个框架的生态基础模块,以及成熟统一的开发方式,可以极大提高我们开发的效率.本篇随笔就是 ...
- 【转】基于Map的简易记忆化缓存
看到文章后,自己也想写一些关于这个方面的,但是觉得写的估计没有那位博主好,而且又会用到里面的许多东西,所以干脆转载.但是会在文章末尾写上自己的学习的的东西. 原文出处如下: http://www.cn ...
- 基于Map的简易记忆化缓存
背景 在应用程序中,时常会碰到需要维护一个map,从中读取一些数据避免重复计算,如果还没有值则计算一下塞到map里的的小需求(没错,其实就是简易的缓存或者说实现记忆化).在公司项目里看到过有些代码中写 ...
随机推荐
- [BZOJ2724] 蒲公英
题目背景 亲爱的哥哥: 你在那个城市里面过得好吗? 我在家里面最近很开心呢.昨天晚上奶奶给我讲了那个叫「绝望」的大坏蛋的故事的说!它把人们的房子和田地搞坏,还有好多小朋友也被它杀掉了.我觉得把那么可怕 ...
- Cocos2d-x 学习笔记(22) TableView
[Cocos2d-x 学习笔记 ]目录 1. 简介 TableView直接继承了ScrollView和ScrollViewDelegate.说明TableView能实现ScrollView拖动cont ...
- 《深入理解Java虚拟机》-----第10章 程序编译与代码优化-早期(编译期)优化
概述 Java语言的“编译期”其实是一段“不确定”的操作过程,因为它可能是指一个前端编译器(其实叫“编译器的前端”更准确一些)把*.java文件转变成*.class文件的过程;也可能是指虚拟机的后端运 ...
- c无聊编程
#include<stdio.h> #include<math.h> void fun_1()//绘制余弦直线 { double y; int m, x; ; y >= ...
- 谢宝友: 手把手教你给Linux内核发patch
本文系转载,著作权归作者所有. 商业转载请联系作者获得授权,非商业转载请注明出处. 作者: 谢宝友 来源: 微信公众号 linux阅码场 (id: linuxdev) 本文简介 本文一步一 ...
- typescript 入门教程一
##### 从今天开始,持续更新typescript入门教程系列.... 目前ts越来越火,主流的前端框架,好比*angular,vue 3*均是采用ts来编写,所有很多公司的项目都是用**ts**来 ...
- MYSQL DATE_FORMAT参数列表及用法
MYSQL DATE_FORMAT参数列表及用法 主要涉及用法 DATE_SUB(DATE, INTERVAL EXPR TYPE) DATE_FORMAT(DATE,FORMAT) REPLACE( ...
- Linux之Centos7开机之后连不上网
问题:ns33mtu 1500 qdisc noop state DOWN group default qlen 1000 解决方法: root@topcheer ~]# systemctl stop ...
- 处理echarts用到的数据格式。。。
1.需求将数据组装: 将typeNumMap中 键为 '1' 的放在数组series 索引为1的data数组中, 将'2'放在索引为2的data数组中,如果 typeNumMap 中没有 对应的 1, ...
- 正则表达式和python中的re模块
---恢复内容开始--- 常用的正则匹配规则 元字符 量词 字符组 字符集 转义符 贪婪匹配 re模块使用正则表达式 实例引入(是否使用re模块和正则表达式的区别) # 不使用正则表达式 phone_ ...