经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C# » 查看文章
【我们一起写框架】C#的AOP框架
来源:cnblogs  作者:kiba518  时间:2018/11/12 10:57:48  对本文有异议

前言

AOP,大家都是听过的,它是一种面向切面的设计模式。

不过AOP虽然是被称为设计模式,但我们应该很少能看到AOP设计的框架。为什么呢?

因为,AOP单独设计的框架几乎是无法使用的。普遍的情况是,AOP要是和其他设计模式结合在一起使用。

所以,AOP虽然是设计模式,但我认为它更接近一种设计元素,是我们在设计框架的作料。

其实AOP的原理就是将公共的部分提取出来,这件事,即便不考虑设计模式,每个开发人员在工作时也是会做的。也就是说,在AOP设计模式被提出来之前,我们就在应用AOP的设计了。

那么,为什么还要单独将AOP拿出来说事呢?

我认为,主要目的应该是要强化切面的重要性。因为设计框架时加入AOP的理念,确实会让框架更加立体。

AOP的应用

AOP既然是一种作料,那么它的应用就是多种多样的;它可以出现在任何场合的。

下面我们举出一个例子,来说明AOP的应用。

----------------------------------------------------------------------------------------------------

我们在开发的时候,通常会有这样的需求。

[将函数的入参和返回值记录到日志中][入参中为负数抛出异常]

当我们面对这样的需求时,通常会将入参和返回值全部传到一个独立的操作函数中,对其进行相应的操作。

这样实现,就是AOP的理念;不过开发者处理时,稍微繁琐了一点,因为每个函数都要处理。

为了减少这种重复操作,让我们一起来编写函数的切面AOP吧。

AOP框架的实现

首先,我们一起看下AOP框架应用后的效果。

在下面代码中,可以看到,我们定义了一个AOPTest类,然后调用了他的Test方法,之后传入了一个正数和一个负数,如果函数抛出异常,我们将输出异常的消息。

  1. class Program
  2. {
  3.     static void Main(string[] args)
  4.     {
  5.         AOPTest test = new AOPTest();
  6.         try
  7.         { 
  8.             test.Test(518);
  9.             test.Test(-100);
  10.         }
  11.         catch(Exception ex)
  12.         {
  13.             Console.WriteLine(ex.Message);
  14.         }
  15.         Console.ReadLine();
  16.     }
  17. }

接下来我们看下AOPTest类的定义。

  1. [Kiba]
  2. public class AOPTest : ContextBoundObject
  3. { 
  4.     
  5.     public string Test(int para)
  6.     {
  7.         Console.WriteLine(para);
  8.         return "数字为:" + para;
  9.     }
  10. }

代码如上所示,很简单,就是输出了入参,不过有两个地方需要注意,该类继承了ContextBoundObject类,并且拥有一个KIba的特性。

然后,我们看下运行结果。

从运行结果中我们看到,第一个函数正常输出,但第二个函数抛出了异常,而且异常的Message是异常两个汉字。

这就是我们AOP实行的效果了,我们的AOP框架对函数入参进行了判断,如果是正数,就正常运行,如果为负数就抛出异常。

下面我们一起来看看AOP框架是如何实现这样的效果的。

首先我们一起来看下Kiba这个特性。

  1. [AttributeUsage(AttributeTargets.Class)]
  2. public class KibaAttribute : ContextAttribute
  3. {
  4.     public KibaAttribute()
  5.         : base("Kiba")
  6.     {
  7.     } 
  8.     public override void GetPropertiesForNewContext(IConstructionCallMessage ctorMsg)
  9.     {
  10.         ctorMsg.ContextProperties.Add(new KibaContextProperty()); 
  11.     }
  12. }

代码如上所示,很简单很基础的一个特性,不过它继承了ContextAttribute类,并重写了其下的方法GetPropertiesForNewContext。

这个方法是干什么的呢?

我们可以从函数名的直译来理解它是干什么的,GetPropertiesForNewContext直译过来就是创建新对象时获取他的属性。然后我们看到,我们重新了该方法后又为他添加了一个新的属性。

而我们添加的这个新的属性将截获拥有该特性的类的函数。

【PS:该描述并不是ContextAttribute真实的运行逻辑,不过,初学时,我们可以先这样理解,当我们更深入的理解了函数的运行机制后,自然就明白该类的意义。】

 下面我们看下KibaContextProperty类。

  1. public class KibaContextProperty : IContextProperty, IContributeObjectSink
  2. {
  3.     public KibaContextProperty()
  4.     {
  5.     } 
  6.     public IMessageSink GetObjectSink(MarshalByRefObject obj, IMessageSink next)
  7.     {
  8.         return new KibaMessageSink(next);
  9.     } 
  10.     public bool IsNewContextOK(Context newCtx)
  11.     {
  12.         return true;
  13.     }  
  14.     public void Freeze(Context newCtx)
  15.     {
  16.     }  
  17.     public string Name
  18.     {
  19.         get { return "Kiba"; }
  20.     }
  21. }

代码如上所示,依然很简单,只是继承并实现了IContextProperty和IContributeObjectSink两个接口。

其中我们重点看下GetObjectSink方法,该方法用于截获函数。

我们可以看到该方法的两个参数,但我们只用到了一个IMessageSink ,并且,该方法的返回值也是IMessageSink。

所以,我们可以想到,该方法的本来面目是这样的。

  1. public IMessageSink GetObjectSink(MarshalByRefObject obj, IMessageSink next)
  2. {
  3.     return next; 
  4. }

也就是说,IMessageSink 封装了函数的一切内容,那么我们的AOP实现的地方也就找到了。

于是我们用KibaMessageSink类处理一下IMessageSink 。

KibaMessageSink代码如下:

  1. public class KibaMessageSink : IMessageSink
  2. {
  3.     private KAspec kaspec = new KAspec(); 
  4.     private IMessageSink nextSink; 
  5.     public KibaMessageSink(IMessageSink next)
  6.     {
  7.         nextSink = next;
  8.     } 
  9.     public IMessageSink NextSink
  10.     {
  11.         get
  12.         {
  13.             return nextSink;
  14.         }
  15.     } 
  16.     public IMessage SyncProcessMessage(IMessage msg)
  17.     { 
  18.         IMethodCallMessage call = msg as IMethodCallMessage; 
  19.         if (call != null)
  20.         {
  21.             //拦截消息,做前处理
  22.             kaspec.PreExcute(call.MethodName, call.InArgs);
  23.         }
  24.         for (int i = 0; i < call.InArgs.Count(); i++)
  25.         {
  26.             var para = call.InArgs[i];
  27.             var type = para.GetType();
  28.             string typename = type.ToString().Replace("System.Nullable`1[", "").Replace("]", "").Replace("System.", "").ToLower();
  29.             if (typename == "int32")
  30.             {
  31.                 int inparame = Convert.ToInt16(call.InArgs[i]);
  32.                 if (inparame < 0)
  33.                 {
  34.                     throw new Exception("异常");
  35.                 }
  36.             } 
  37.         }
  38.         //传递消息给下一个接收器 
  39.         IMessage retMsg = nextSink.SyncProcessMessage(call as IMessage);   
  40.         IMethodReturnMessage dispose = retMsg as IMethodReturnMessage;
  41.         if (dispose != null)
  42.         { 
  43.             //调用返回时进行拦截,并进行后处理
  44.             kaspec.EndExcute(dispose.MethodName, dispose.OutArgs, dispose.ReturnValue, dispose.Exception);
  45.         } 
  46.         return retMsg;
  47.     } 
  48.     public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)
  49.     {
  50.         return null;
  51.     } 
  52. }

我们重点看下SyncProcessMessage方法。

可以看到,我们在方法调用先调用了KAspec类的PreExcute方法,该方法用于把入参输出到日志中。

接下来,我们对入参进行了判断,如果入参是负数,我们将不执行函数,直接抛出异常。

然后我们调用KAspec类的EndExcute方法,将返回值输出到日志中。

再然后,我们才返回IMessage,让函数完结。

下面我们一起看下KAspec类的实现。

  1. /// <summary>
  2. /// 切面
  3. /// </summary>
  4. public class KAspec
  5. { 
  6.     #region 处理
  7.     /// <summary>
  8.     /// 前处理
  9.     /// </summary> 
  10.     public void PreExcute(string MethodName, object[] InParams)
  11.     {
  12.  
  13.         Logger.Info("==================== " + MethodName + ":" + " Start====================");
  14.         Logger.Info(string.Format("参数数量:{0}", InParams.Count()));
  15.  
  16.         for (int i = 0; i < InParams.Count(); i++)
  17.         {
  18.             Logger.Info(string.Format("参数序号[{0}] ============    参数类型:{1}    执行类:{1}", i + 1, InParams[i])); 
  19.             Logger.Info("传入参数:");
  20.             string paramXMLstr = XMLSerializerToString(InParams[i], Encoding.UTF8);
  21.             Logger.Info(paramXMLstr);
  22.         }
  23.     } 
  24.     /// <summary>
  25.     /// 后处理
  26.     /// </summary> 
  27.     public void EndExcute(string MethodName, object[] OutParams, object ReturnValue, Exception ex)
  28.     {
  29.         Type myType = ReturnValue.GetType();
  30.         Logger.Info(string.Format("返回值类型:{0}", myType.Name));
  31.         Logger.Info("返回值:");
  32.         if (myType.Name != "Void")
  33.         {
  34.             string resXMLstr = DataContractSerializerToString(ReturnValue, Encoding.UTF8);
  35.             Logger.Info(resXMLstr);
  36.         }
  37.        
  38.         if (OutParams.Count() > 0)//out 返回参数
  39.         {
  40.             Logger.Info(string.Format("out返回参数数量:{0}", OutParams.Count())); 
  41.             for (int i = 0; i < OutParams.Count(); i++)
  42.             {
  43.                 Logger.Info(string.Format("参数序号[{0}] == 参数值:{1}", i + 1, OutParams[i]));
  44.             }
  45.         }
  46.  
  47.         if (ex != null)
  48.         {
  49.             Logger.Error(ex);
  50.         }
  51.         Logger.Info("==================== " + MethodName + ":" + " End====================");
  52.     }
  53. }

代码如上所示,就是简单的日志输出。

到此,我们的AOP框架就编写完成了;其上的代码编写都是为KAspec服务,因为KAspec才是切面。

也就是说,只要将特性Kiba赋予给类,那么该类的函数,就被拦截监听,然后我们就可以KAspec切面中,做我们想做的操作了。

最后,我们再回头看下AOPTest类。

  1. [Kiba]
  2. public class AOPTest : ContextBoundObject

可以看到,该类不止拥有Kiba特性,还继承了ContextBoundObject类,该类是干什么的呢?

ContextBoundObject类是内容边界对象,只有继承了ContextBoundObject类的类,其类中才会驻留的Context上下文,并且会被ContextAttribute特性拦截监听。

呃,其实,这样解释还是有点不太正确,不过我也没找到更好的说明方式,如果你还理解不了,也可以去MSDN查询下,当然,MSDN的解释是反人类的,需要做好心理准备。

----------------------------------------------------------------------------------------------------

框架代码已经传到Github上了,欢迎大家下载。

Github地址:https://github.com/kiba518/KAOP

----------------------------------------------------------------------------------------------------

注:此文章为原创,欢迎转载,请在文章页面明显位置给出此文链接!
若您觉得这篇文章还不错,请点击下右下角的推荐】,非常感谢!

 

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号