【*狂魔】打造简易无配置的IoC

时间:2022-09-28 20:16:40
如何指定Business Event和Command之间的关系?

既然是基于惯例优先原则,那么我们首先需要定义一个惯例:

1.调度事件和调度处理器之间是一对多关系(多对多的话,相信你看完了以后应该会知道怎么改的)。

2.所有业务事件(Event)要以调度事件为基类,业务指令(Command)的调度处理器特性需要指定可处理的调度事件。

     /// <summary>
/// 调度事件
/// </summary>
public class DispatchEvent
{ }
      /// <summary>
/// 调度处理器
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class DispatchHandlerAttribute : Attribute
{
/// <summary>
/// 观察的调度事件类型
/// </summary>
public Type ObservedDispatchEventType { get; set; } /// <summary>
/// 调度实例
/// </summary>
public object DispatchInstance { get; set; } /// <summary>
/// 动作方法信息
/// </summary>
public MethodInfo ActionMethodInfo { get; set; } /// <summary>
/// 构造函数
/// </summary>
/// <param name="observedDispatchEventType">观察的调度事件类型</param>
public DispatchHandlerAttribute(Type observedDispatchEventType)
{
this.ObservedDispatchEventType = observedDispatchEventType;
}
}
如何创建一个业务事件?

我们基于获取AccessToken作为第一个业务事件,先创建一个获取AccessToken的事件

当然,具体交互微信细节就省略了,如果感兴趣可留言,看多少人关注我考虑下是否把微信相关的东西也一起加进来

      /// <summary>
/// 获取微信交互接口凭证事件
/// </summary>
public class GetAccessTokenEvent : DispatchEvent
{
/// <summary>
/// 微信交互接口凭证信息
/// </summary>
public AccessTokenInfo AccessTokenInfo { get; set; }
}

此时有人会问了,不是获取AccessToken吗?传的不应该是一些APPID、APPSecurity之类的吗,为什么是AccessTokenInfo?
嗯,伟大的值类型和引用类型就派上用场了,因为Event会作为参数传递给Command,由Command自行填充。既然GetAccessTokenEvent是引用类型,那么在Command内修改AccessToken是不需要返回一个新的Event或者对象,直接在Event内的AccessTokenInfo上修改就好了,调用者就会得到他想要的东西。

虽然这只是个小知识点,大多数人都知道,但是有人喜欢用,因为可以偷懒。

有人不喜欢,觉得这样会让一些人不明白内部到底做了些什么,调用者该如何使用这个事件。

具体怎么做,因人而异吧,这不是重点,关键是一开始就提了:惯例优先原则。而这,不就是一个惯例吗? ^_^

如何创建一个业务指令,与上一个业务事件关联起来?

这里有个小小的业务,就是AccessToken获取后有失效时间,过了要重新获取。又不能每次都获取,因为又有获取次数限制。

为了处理这个问题,我做了个自动更新缓存类,这个在AccessTokenCommand的静态构造函数里设置一次获取逻辑。

之后其他功能在使用到以后都是从缓存里拿的。那么这个稍微有点逻辑的业务我们看看Command该怎么写。

     /// <summary>
/// 微信交互接口凭证命令
/// </summary>
public class AccessTokenCommand
{
#region 静态构造函数 static AccessTokenCommand()
{
AutoUpdateCache.Add(CacheKeySet.AccessToken.ToString(), new AutoUpdateItem()
{
UpdateValue = (AutoUpdateItem autoUpdateItem) =>
{
var accessTokenInfo = CommandHelper.GetWeChatResponseObject<AccessTokenInfo>(new AccessTokenCommandRequest()); autoUpdateItem.ExpiredSeconds = accessTokenInfo.ExpiresIn - ;//预留过期时效,防止提前过期
autoUpdateItem.Value = accessTokenInfo;
}
});
} #endregion /// <summary>
/// 获取微信交互接口凭证
/// </summary>
/// <param name="e"></param>
[DispatchHandler(typeof(GetAccessTokenEvent))]
public void GetAccessToken(GetAccessTokenEvent e)
{
e.AccessTokenInfo = AutoUpdateCache.GetValue<AccessTokenInfo>(CacheKeySet.AccessToken.ToString());
}
}

从 GetAccessToken 这个方法我们可以看出他有几个地方是需要注意的。
1.有一个特性 [DispatchHandler(typeof(GetAccessTokenEvent))] ,这个意思是标识当前方法是一个调度处理器,所处理的事件是 GetAccessTokenEvent

2.没有任何返回值

3.直接把AccessTokenInfo赋值到 GetAccessTokenEvent 这个传入参数里

如何把Event和Command关联起来呢?

我先声明,我本人是不太喜欢写一坨坨的配置文件的,虽然配置更灵活,但配置太多反而维护起来极度不方便。

那么,不使用配置就相当于要有一定的规则,否则我们是梳理不出来如何自动创建关联关系。我们就叫他“调度器”好了。

这个调度器应该具备以下几个功能:

1.解析任意一个程序集

2.扫描所有的DispatchHandler,即调度处理器

3.缓存调度关系网,降低反射带来的性能损耗

4.传一个调度事件参数,自行调用所有的调度处理器

       /// <summary>
/// 调度器
/// </summary>
public class Dispatcher
{
/// <summary>
/// 调度关系网
/// </summary>
private static Dictionary<Type, List<DispatchHandlerAttribute>> _dicDispatchRelativeNetwork = new Dictionary<Type, List<DispatchHandlerAttribute>>(); /// <summary>
/// 建立调度关系网
/// </summary>
/// <param name="assembly"></param>
public static void BuildDispatchRelationship(Assembly assembly)
{
Logger.Info("调度器:开始建立调度关系网..."); var types = assembly.GetTypes(); foreach (var type in types)
{
var methods = type.GetMethods(); foreach (var method in methods)
{
var attribute = method.GetCustomAttributes(typeof(DispatchHandlerAttribute), true).FirstOrDefault();
if (attribute != null)
{
CheckParameterRule(method); var handler = attribute as DispatchHandlerAttribute;
handler.DispatchInstance = Activator.CreateInstance(type);
handler.ActionMethodInfo = method;
AddDispatchRelation(handler);
}
}
}
} /// <summary>
/// 添加调度关系
/// </summary>
/// <param name="handler">调度处理器</param>
private static void AddDispatchRelation(DispatchHandlerAttribute handler)
{
var eventType = handler.ObservedDispatchEventType; if (!_dicDispatchRelativeNetwork.ContainsKey(eventType))
{
_dicDispatchRelativeNetwork.Add(eventType, new List<DispatchHandler>());
} _dicDispatchRelativeNetwork[eventType].Add(handler); Logger.Info(string.Format("调度器:建立新的关系网 [{0}]-[{1}.{2}]", eventType.Name, handler.DispatchInstance.GetType().Name, handler.ActionMethodInfo.Name));
} /// <summary>
/// 检查参数规则
/// </summary>
/// <param name="method"></param>
private static void CheckParameterRule(MethodInfo method)
{
var parameters = method.GetParameters();
if (parameters == null ||
parameters.Length != ||
(!parameters.FirstOrDefault().ParameterType.Equals(typeof(DispatchEvent)) &&
!parameters.FirstOrDefault().ParameterType.BaseType.Equals(typeof(DispatchEvent))
))
{
throw new Exception(string.Format("DispatchHandler - [{0}]的参数必须是只有一个且继承自DispatchEvent", method.Name));
}
} /// <summary>
/// 激活事件
/// </summary>
/// <param name="dispatchEvent">调度事件</param>
public static void ActiveEvent(DispatchEvent dispatchEvent)
{
var type = dispatchEvent.GetType(); if (!_dicDispatchRelativeNetwork.ContainsKey(type))
{
Logger.Error(string.Format("调度器:当前事件[{0}]没有找到绑定的Handler", type.FullName));
return;
} _dicDispatchRelativeNetwork[type].ForEach(action =>
{
ActiveAction(action, dispatchEvent);
});
} private static void ActiveAction(DispatchHandlerAttribute action, DispatchEvent dispatchEvent)
{
try
{
action.ActionMethodInfo.Invoke(action.DispatchInstance, new object[] { dispatchEvent });
}
catch (Exception ex)
{
if (ex.InnerException != null && ex.InnerException.GetType().Equals(typeof(WCFLib.ExceptionExtension.GException)))
{
throw ex.InnerException;
}
else
{
throw;
}
}
}
}
如何激活Event?

创建一个事件,使用调度器激活事件,最后从事件中的属性获取你需要的返回值。

             var getAccessTokenEvent = new GetAccessTokenEvent();
Dispatcher.ActiveEvent(getAccessTokenEvent);
var accessTokenInfo = getAccessTokenEvent.AccessTokenInfo;

此时又有人会问了,为什么不直接用  new AccessTokenCommand().GetAccessToken(new GetAccessTokenEvent());
因为Event所在类库是没有添加Command引用的。通过调度者动态加载的。

那么问题又来了,这么劳民伤财为的是什么?

卖个关子,下篇继续造*。 ^_^

别急别急,还没完

这年头不应该有图有真相吗?

【*狂魔】打造简易无配置的IoC

感谢所有耐心看完的人,欢迎提出你们的宝贵意见和批评。让我们一起进步吧。

下一篇预告:【*狂魔】调度器的扩展能力

如果你喜欢这篇博文,或者期待下一篇博文,麻烦点下推荐,你们的支持是我的动力 ^_^

【*狂魔】打造简易无配置的IoC的更多相关文章

  1. 【*狂魔】抛弃IIS,打造个性的Web Server - WebAPI&sol;Lua&sol;MVC(附带源码)

    引言 此篇是<[*狂魔]抛弃IIS,向天借个HttpListener - 基础篇(附带源码)>的续篇,也可以说是提高篇,如果你对HttpListener不甚了解的话,建议先看下基础篇. ...

  2. 树莓派最简易Wifi配置

    树莓派最简易Wifi配置 相信我,连博客都会偷懒写个最简易给你看 前提,只有一根网线没有网络的前提下进行的. 基于Win10系统和树莓派2015-05-05-raspbian-wheezy.img测试 ...

  3. 打造简易可扩展的jQuery&sol;CSS3 Tab菜单

    原文:打造简易可扩展的jQuery/CSS3 Tab菜单 今天我们利用jQuery和CSS3来打造一款简易而且扩展性强的Tab菜单,这款Tab菜单在切换时也有滑块的效果,先来看看效果图: 由与Tab菜 ...

  4. 【*狂魔】手把手教你自造Redis Client

    为什么做Redis Client? Redis Client顾名思义,redis的客户端,主要是封装了一些对于Redis的操作. 而目前用的比较广泛的 ServiceStack.Redis 不学好,居 ...

  5. 移动工程后,打开ROM核无配置信息

    问题: 从他人处下载的ISE工程,打开dw51的ROM IP核,无配置信息,为block memory generator的初始配置,并显示无法找到coe文件 原因:ROM配置过程中的部分内容丢失导致 ...

  6. Spring-02 Java配置实现IOC

    Java配置 Spring4推荐使用java配置实现IOC Spring boot也推荐采用java配置实现IOC 在实际项目中,一般采用注解配置业务bean,全局配置使用Java配置. Java配置 ...

  7. hi-nginx-java的无配置路由配置

    hi-nginx-java既可以通过实现hi.servlet抽象来像Flask那样快速配置路由,例如: 1 hi.route r = hi.route.get_instance(); 2 r.get( ...

  8. Golang项目的配置管理——Viper简易入门配置

    Golang项目的配置管理--Viper简易入门配置 What is Viper? From:https://github.com/spf13/viper Viper is a complete co ...

  9. JavaEE中的MVC(二)Xml配置实现IOC控制反转

    毕竟我的经验有限,这篇文章要是有什么谬误,欢迎留言并指出,我们可以一起讨论讨论. 我要讲的是IOC控制反转,然后我要拿它做一件什么事?两个字:"解耦",形象点就是:表明当前类中需要 ...

随机推荐

  1. Palindrome Pairs

    Given a list of unique words. Find all pairs of distinct indices (i, j) in the given list, so that t ...

  2. Java泛型-类型擦除

    一.概述 Java泛型在使用过程有诸多的问题,如不存在List<String>.class, List<Integer>不能赋值给List<Number>(不可协变 ...

  3. &lbrack;原创&rsqb;自定义view之:快速开发一款Material Design风格的dialog的开源项目MDDialog

    随着google开始主导Material Design风格的设计,越来越多的app开始使用Material Design风格来设计自己的UI.虽然在Android Studio中集成了多种快速开发框架 ...

  4. 【VB技巧】VB静态调用与动态调用dll详解

    本文“[VB技巧]VB静态调用与动态调用dll详解”,来自:Nuclear'Atk 网络安全研究中心,本文地址:http://lcx.cc/?i=489,转载请注明作者及出处! [[请注意]]:在以下 ...

  5. js 跨域的使用

    try{document.domain="jincin.com"}catch(error){} 需要在被调用的函数和调用函数出都要加入上面相同的语句 下面看一下第二种跨域的解决方案 ...

  6. mysql 函数执行权限

    mysql> show grants for query_all@'115.236.1x0.x'; +---------------------------------------------- ...

  7. 正向选择(positive selection)、中性选择(neutral selection)、平衡选择(balancing selection)示意图

    正向选择:某一位点逐渐积累,成优势的位点,具体表现为:随着时间延长,该位点的突变allele频率越来越高,远远超过野生型allele: 中性选择:随着时间的延长,总体频率没有改变太多: 平衡选择:位点 ...

  8. shiro默认登录

    业务需要,A项目跳转到B项目进行相关操作.而B项目使用的是shiro登录验证,懵逼了半天,好吧我很菜. 当然你也可以在shiro配置文件中放过这些方法,但是为了安全考虑需要遵守这些规则. 因此A跳转到 ...

  9. 前端基础之JQuery - day15

    写在前面 上课第15天,打卡: 张国臂掖,以通西域: ########### # 课上简书 # ########## http://jquery.cuishifeng.cn/index.html JQ ...

  10. 虚拟机centos7配置本地yum源

    在虚拟机中要使用yum命令,就要先配置一下yum源,下面就分享一下这个过程: 1. 挂载iso到vmware,首先得确保CD/DVD连接到镜像.可以这样操作 2. 执行下面的命令 # mkdir /m ...