一、问题背景

  最近离职来到了一家新的公司,原先是在乙方工作,这回到了甲方,在这一个月中,发现目前的业务很大一部分是靠轮询实现的,例如:通过轮询判断数据处于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实现-基础实现的更多相关文章

  1. 基于redis的简易分布式爬虫框架

    代码地址如下:http://www.demodashi.com/demo/13338.html 开发环境 Python 3.6 Requests Redis 3.2.100 Pycharm(非必需,但 ...

  2. 基于RxJava2+Retrofit2精心打造的Android基础框架

    代码地址如下:http://www.demodashi.com/demo/12132.html XSnow 基于RxJava2+Retrofit2精心打造的Android基础框架,包含网络.上传.下载 ...

  3. Android消息传递之基于RxJava实现一个EventBus - RxBus

    前言: 上篇文章学习了Android事件总线管理开源框架EventBus,EventBus的出现大大降低了开发成本以及开发难度,今天我们就利用目前大红大紫的RxJava来实现一下类似EventBus事 ...

  4. 面向服务体系架构(SOA)和数据仓库(DW)的思考基于 IBM 产品体系搭建基于 SOA 和 DW 的企业基础架构平台

    面向服务体系架构(SOA)和数据仓库(DW)的思考 基于 IBM 产品体系搭建基于 SOA 和 DW 的企业基础架构平台 当前业界对面向服务体系架构(SOA)和数据仓库(Data Warehouse, ...

  5. 基于Android 平台简易即时通讯的研究与设计[转]

    摘要:论文简单介绍Android 平台的特性,主要阐述了基于Android 平台简易即时通讯(IM)的作用和功能以及实现方法.(复杂的通讯如引入视频音频等可以考虑AnyChat SDK~)关键词:An ...

  6. 基于.netstandard的权限控制组件

    基于.netstandard的权限控制组件 Intro 由于项目需要,需要在 基于 Asp.net mvc 的 Web 项目框架中做权限的控制,于是才有了这个权限控制组件. 项目基于 .NETStan ...

  7. 如何基于Winform开发框架或混合框架基础上进行项目的快速开发

    在开发项目的时候,我们为了提高速度和质量,往往不是白手起家,需要基于一定的基础上进行项目的快速开发,这样可以利用整个框架的生态基础模块,以及成熟统一的开发方式,可以极大提高我们开发的效率.本篇随笔就是 ...

  8. 【转】基于Map的简易记忆化缓存

    看到文章后,自己也想写一些关于这个方面的,但是觉得写的估计没有那位博主好,而且又会用到里面的许多东西,所以干脆转载.但是会在文章末尾写上自己的学习的的东西. 原文出处如下: http://www.cn ...

  9. 基于Map的简易记忆化缓存

    背景 在应用程序中,时常会碰到需要维护一个map,从中读取一些数据避免重复计算,如果还没有值则计算一下塞到map里的的小需求(没错,其实就是简易的缓存或者说实现记忆化).在公司项目里看到过有些代码中写 ...

随机推荐

  1. php有orm吗

    ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中.本质上就是将数据从一种形式转换到另外一种形式. ORM提供了所有SQL语句的生成,代码人员远离了数据库概念.从 ...

  2. Linux Capabilities 入门教程:概念篇

    原文链接:Linux Capabilities 入门教程:概念篇 Linux 是一种安全的操作系统,它把所有的系统权限都赋予了一个单一的 root 用户,只给普通用户保留有限的权限.root 用户拥有 ...

  3. 面试官:"准备用HashMap存1w条数据,构造时传10000还会触发扩容吗?"

    // 预计存入 1w 条数据,初始化赋值 10000,避免 resize. HashMap<String,String> map = new HashMap<>(10000) ...

  4. Spring 事务笔记

    代码写着写着就钻进源码了. 概念 InfrastructureProxy 结构代理 百度查了查,这个类还没有解释. 进去看了一下: Interface to be implemented by tra ...

  5. 包+time+datetime+random+hashlibhmac+typing+requests+re模块(day17整理)

    目录 昨日内容 os模块 sys模块 json模块 pickle模块 logging模块 今日内容 包 相对导入 绝对导入 time模块 sleep 时间戳 time 格式化时间 strtime 结构 ...

  6. C#关于private protected sealed Virtual/Override

    Public:公开权限 Private:修饰类时类为程序集或者包含此类的类内部权限:修饰变量时只能类内部使用: Protected:修饰变量,只能继承类可以使用,对外(包括继承类的实例)无权限: Ab ...

  7. django-URL重定向(八)

    HttpResponseRedirect()不常用 redirect(to,permanent=False,*args,**kwargs) to:指重定向的位置,可以是视图,也可以是url地址,也可以 ...

  8. django-URL匹配(二)

    1.新建django项目 django-admin startproject newwebsite 2.建立app 在newwebsite目录下:python manage.py startapp b ...

  9. DRF之注册器、响应器、分页器

    一.url注册器 通过DRF的视图组件,数据接口逻辑被我们优化到最剩下一个类,接下来,我们使用DRF的url控制器来帮助我们自动生成url,使用步骤如下: 第一步:导入模块 1 from rest_f ...

  10. iOS11 SDK 新特性 CoreML 及swift 小demo

    github代码 如果本博客对您有帮助,希望可以得到您的赞赏! swift 机器学习Core ML的简单调用小demo.完整代码附上: https://github.com/Liuyubao/LYBC ...