【我们一起写框架】C#的AOP框架

前言 AOP,大家都是听过的,它是一种面向切面的设计模式。 不过AOP虽然是被称为设计模式,但我们应该很少能看到AOP设计的框架。为什么呢? 因为,AOP单独设计的框架几乎是无法使用的。普遍的情况是,AOP要是和其他设计模式结合在一起使用。 所以,AOP虽然是设计模式,但我认为它更接近一种设计元素,是我们在设计框架的作料。 其实AOP的原理就是将公共的部分提取出来,这件事,即便不考虑设计模式,每个开发人员在工作时也是会做的。也就是说,在AOP设计模式被提出来之前,我们就在应用AOP的设计了。 那么,为什么还要单独将AOP拿出来说事呢? 我认为,主要目的应该是要强化切面的重要性。因为设计框架时加入AOP的理念,确实会让框架更加立体。 AOP的应用 AOP既然是一种作料,那么它的应用就是多种多样的;它可以出现在任何场合的。 下面我们举出一个例子,来说明AOP的应用。 ---------------------------------------------------------------------------------------------------- 我们在开发的时候,通常会有这样的需求。 [将函数的入参和返回值记录到日志中][入参中为负数抛出异常] 当我们面对这样的需求时,通常会将入参和返回值全部传到一个独立的操作函数中,对其进行相应的操作。 这样实现,就是AOP的理念;不过开发者处理时,稍微繁琐了一点,因为每个函数都要处理。 为了减少这种重复操作,让我们一起来编写函数的切面AOP吧。 AOP框架的实现 首先,我们一起看下AOP框架应用后的效果。 在下面代码中,可以看到,我们定义了一个AOPTest类,然后调用了他的Test方法,之后传入了一个正数和一个负数,如果函数抛出异常,我们将输出异常的消息。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Program { static void Main(string[] args) { AOPTest test = new AOPTest(); try { test.Test(518); test.Test(-100); } catch(Exception ex) { Console.WriteLine(ex.Message); } Console.ReadLine(); } } 接下来我们看下AOPTest类的定义。 1 2 3 4 5 6 7 8 9 10 [Kiba] public class AOPTest : ContextBoundObject { public string Test(int para) { Console.WriteLine(para); return "数字为:" + para; } } 代码如上所示,很简单,就是输出了入参,不过有两个地方需要注意,该类继承了ContextBoundObject类,并且拥有一个KIba的特性。 然后,我们看下运行结果。 从运行结果中我们看到,第一个函数正常输出,但第二个函数抛出了异常,而且异常的Message是异常两个汉字。 这就是我们AOP实行的效果了,我们的AOP框架对函数入参进行了判断,如果是正数,就正常运行,如果为负数就抛出异常。 下面我们一起来看看AOP框架是如何实现这样的效果的。 首先我们一起来看下Kiba这个特性。 1 2 3 4 5 6 7 8 9 10 11 12 [AttributeUsage(AttributeTargets.Class)] public class KibaAttribute : ContextAttribute { public KibaAttribute() : base("Kiba") { } public override void GetPropertiesForNewContext(IConstructionCallMessage ctorMsg) { ctorMsg.ContextProperties.Add(new KibaContextProperty()); } } 代码如上所示,很简单很基础的一个特性,不过它继承了ContextAttribute类,并重写了其下的方法GetPropertiesForNewContext。 这个方法是干什么的呢? 我们可以从函数名的直译来理解它是干什么的,GetPropertiesForNewContext直译过来就是创建新对象时获取他的属性。然后我们看到,我们重新了该方法后又为他添加了一个新的属性。 而我们添加的这个新的属性将截获拥有该特性的类的函数。 【PS:该描述并不是ContextAttribute真实的运行逻辑,不过,初学时,我们可以先这样理解,当我们更深入的理解了函数的运行机制后,自然就明白该类的意义。】 下面我们看下KibaContextProperty类。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class KibaContextProperty : IContextProperty, IContributeObjectSink { public KibaContextProperty() { } public IMessageSink GetObjectSink(MarshalByRefObject obj, IMessageSink next) { return new KibaMessageSink(next); } public bool IsNewContextOK(Context newCtx) { return true; } public void Freeze(Context newCtx) { } public string Name { get { return "Kiba"; } } } 代码如上所示,依然很简单,只是继承并实现了IContextProperty和IContributeObjectSink两个接口。 其中我们重点看下GetObjectSink方法,该方法用于截获函数。 我们可以看到该方法的两个参数,但我们只用到了一个IMessageSink ,并且,该方法的返回值也是IMessageSink。 所以,我们可以想到,该方法的本来面目是这样的。 1 2 3 4 public IMessageSink GetObjectSink(MarshalByRefObject obj, IMessageSink next) { return next; } 也就是说,IMessageSink 封装了函数的一切内容,那么我们的AOP实现的地方也就找到了。 于是我们用KibaMessageSink类处理一下IMessageSink 。 KibaMessageSink代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 public class KibaMessageSink : IMessageSink { private KAspec kaspec = new KAspec(); private IMessageSink nextSink; public KibaMessageSink(IMessageSink next) { nextSink = next; } public IMessageSink NextSink { get { return nextSink; } } public IMessage SyncProcessMessage(IMessage msg) { IMethodCallMessage call = msg as IMethodCallMessage; if (call != null) { //拦截消息,做前处理 kaspec.PreExcute(call.MethodName, call.InArgs); } for (int i = 0; i < call.InArgs.Count(); i++) { var para = call.InArgs[i]; var type = para.GetType(); string typename = type.ToString().Replace("System.Nullable`1[", "").Replace("]", "").Replace("System.", "").ToLower(); if (typename == "int32") { int inparame = Convert.ToInt16(call.InArgs[i]); if (inparame < 0) { throw new Exception("异常"); } } } //传递消息给下一个接收器 IMessage retMsg = nextSink.SyncProcessMessage(call as IMessage); IMethodReturnMessage dispose = retMsg as IMethodReturnMessage; if (dispose != null) { //调用返回时进行拦截,并进行后处理 kaspec.EndExcute(dispose.MethodName, dispose.OutArgs, dispose.ReturnValue, dispose.Exception); } return retMsg; } public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink) { return null; } } 我们重点看下SyncProcessMessage方法。 可以看到,我们在方法调用先调用了KAspec类的PreExcute方法,该方法用于把入参输出到日志中。 接下来,我们对入参进行了判断,如果入参是负数,我们将不执行函数,直接抛出异常。 然后我们调用KAspec类的EndExcute方法,将返回值输出到日志中。 再然后,我们才返回IMessage,让函数完结。 下面我们一起看下KAspec类的实现。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 /// /// 切面 /// public class KAspec { #region 处理 /// /// 前处理 /// public void PreExcute(string MethodName, object[] InParams) { Logger.Info("==================== " + MethodName + ":" + " Start===================="); Logger.Info(string.Format("参数数量:{0}", InParams.Count())); for (int i = 0; i < InParams.Count(); i++) { Logger.Info(string.Format("参数序号[{0}] ============ 参数类型:{1} 执行类:{1}", i + 1, InParams[i])); Logger.Info("传入参数:"); string paramXMLstr = XMLSerializerToString(InParams[i], Encoding.UTF8); Logger.Info(paramXMLstr); } } /// /// 后处理 /// public void EndExcute(string MethodName, object[] OutParams, object ReturnValue, Exception ex) { Type myType = ReturnValue.GetType(); Logger.Info(string.Format("返回值类型:{0}", myType.Name)); Logger.Info("返回值:"); if (myType.Name != "Void") { string resXMLstr = DataContractSerializerToString(ReturnValue, Encoding.UTF8); Logger.Info(resXMLstr); } if (OutParams.Count() > 0)//out 返回参数 { Logger.Info(string.Format("out返回参数数量:{0}", OutParams.Count())); for (int i = 0; i < OutParams.Count(); i++) { Logger.Info(string.Format("参数序号[{0}] == 参数值:{1}", i + 1, OutParams[i])); } } if (ex != null) { Logger.Error(ex); } Logger.Info("==================== " + MethodName + ":" + " End===================="); } } 代码如上所示,就是简单的日志输出。 到此,我们的AOP框架就编写完成了;其上的代码编写都是为KAspec服务,因为KAspec才是切面。 也就是说,只要将特性Kiba赋予给类,那么该类的函数,就被拦截监听,然后我们就可以KAspec切面中,做我们想做的操作了。 最后,我们再回头看下AOPTest类。 1 2 [Kiba] public class AOPTest : ContextBoundObject 可以看到,该类不止拥有Kiba特性,还继承了ContextBoundObject类,该类是干什么的呢? ContextBoundObject类是内容边界对象,只有继承了ContextBoundObject类的类,其类中才会驻留的Context上下文,并且会被ContextAttribute特性拦截监听。 呃,其实,这样解释还是有点不太正确,不过我也没找到更好的说明方式,如果你还理解不了,也可以去MSDN查询下,当然,MSDN的解释是反人类的,需要做好心理准备。 ---------------------------------------------------------------------------------------------------- 框架代码已经传到Github上了,欢迎大家下载。 Github地址:https://github.com/kiba518/KAOP ---------------------------------------------------------------------------------------------------- 注:此文章为原创,欢迎转载,请在文章页面明显位置给出此文链接! 若您觉得这篇文章还不错,请点击下右下角的【推荐】,非常感谢!https://www.cnblogs.com/kiba/p/9920691.html
50000+
5万行代码练就真实本领
17年
创办于2008年老牌培训机构
1000+
合作企业
98%
就业率

联系我们

电话咨询

0532-85025005

扫码添加微信