前言

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

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

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

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

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

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

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

AOP的应用

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

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

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

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

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

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

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

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

AOP框架的实现

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

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

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类的定义。

[Kiba]
public class AOPTest : ContextBoundObject
{ public string Test(int para)
{
Console.WriteLine(para);
return "数字为:" + para;
}
}

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

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

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

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

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

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

[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类。

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。

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

public IMessageSink GetObjectSink(MarshalByRefObject obj, IMessageSink next)
{
return next;
}

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

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

KibaMessageSink代码如下:

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类的实现。

/// <summary>
/// 切面
/// </summary>
public class KAspec
{
#region 处理
/// <summary>
/// 前处理
/// </summary>
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);
}
}
/// <summary>
/// 后处理
/// </summary>
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类。

[Kiba]
public class AOPTest : ContextBoundObject

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

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

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

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

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

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

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

注:此文章为原创,任何形式的转载都请联系作者获得授权并注明出处!
若您觉得这篇文章还不错,请点击下方的【推荐】,非常感谢!

【我们一起写框架】C#的AOP框架的更多相关文章

  1. Spring AOP 框架

    引言 要掌握 Spring AOP 框架,需要弄明白 AOP 的概念. AOP 概念 AOP(Aspect Oriented Programming的缩写,翻译为面向方面或面向切面编程),通过预编译方 ...

  2. 仿写一个简陋的 IOC/AOP 框架 mini-spring

    讲道理,感觉自己有点菜.Spring 源码看不懂,不想强行解释,等多积累些项目经验之后再看吧,但是 Spring 中的控制反转(IOC)和面向切面编程(AOP)思想很重要,为了更好的使用 Spring ...

  3. Spring 08: AOP面向切面编程 + 手写AOP框架

    核心解读 AOP:Aspect Oriented Programming,面向切面编程 核心1:将公共的,通用的,重复的代码单独开发,在需要时反织回去 核心2:面向接口编程,即设置接口类型的变量,传入 ...

  4. java框架篇---spring AOP 实现原理

    什么是AOP AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善.OOP引入 ...

  5. Dora.Interception: 一个为.NET Core度身定制的AOP框架

    多年从事框架设计开发使我有了一种强迫症,那就是见不得一个应用里频繁地出现重复的代码.之前经常Review别人的代码,一看到这样的程序,我就会想如何将这些重复的代码写在一个地方,然后采用“注入”的方式将 ...

  6. NET Core度身定制的AOP框架

    NET Core度身定制的AOP框架 多年从事框架设计开发使我有了一种强迫症,那就是见不得一个应用里频繁地出现重复的代码.之前经常Review别人的代码,一看到这样的程序,我就会想如何将这些重复的代码 ...

  7. Dora.Interception, 一个为.NET Core度身打造的AOP框架:不一样的Interceptor定义方式

    相较于社区其他主流的AOP框架,Dora.Interception在Interceptor提供了完全不同的编程方式.我们并没有为Interceptor定义一个接口,正是因为不需要实现一个预定义的接口, ...

  8. Java基础---Java---基础加强---类加载器、委托机制、AOP、 动态代理技术、让动态生成的类成为目标类的代理、实现Spring可配置的AOP框架

    类加载器 Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader 类加载器也是Jav ...

  9. 从壹开始前后端分离【 .NET Core2.0 +Vue2.0 】框架之十 || AOP面向切面编程浅解析:简单日志记录 + 服务切面缓存

    代码已上传Github+Gitee,文末有地址 上回<从壹开始前后端分离[ .NET Core2.0 Api + Vue 2.0 + AOP + 分布式]框架之九 || 依赖注入IoC学习 + ...

随机推荐

  1. Myeclipse使用git

    推荐使用服务器:coding,coding的上传很稳定很快 怎么建仓库: coding 这个服务器呢有个很明显的东西就是太的语言可以是中文的, 点头标签的加号就能进行创建仓库了 点击新建就行了 点击这 ...

  2. 最近面了不少java开发,据此来说下我的感受:哪怕事先只准备1小时,成功概率也能大大提升

    本人最近几年一直在做java后端方面的技术面试官,而在最近两周,又密集了面试了一些java初级和高级开发的候选人,在面试过程中,我自认为比较慎重,遇到问题回答不好的候选人,我总会再三从不同方面提问,只 ...

  3. 读《图解HTTP》有感-(返回结果的HTTP状态码)

    写在前面 HTTP状态码是由服务端产生,用于告诉客户端,服务端处理结果的编码 正文 1.状态码的作用是什么?具有什么特征? 状态码的作用是当客户端向服务器发送请求时,描述服务器的响应结果(如:服务器正 ...

  4. Alfred效率神器

    下图就是Alfred的主界面我们所有的操作都在这一个界面上进行.通过热键打开主界面(本人设置的是option+command),输入一个"a"后Alfred就会为我在候选界面上显示 ...

  5. Mysql中MyISAM引擎和InnoDB引擎的比较

    结论 如果不清楚自己应该用什么引擎,那么请选择InnoDB,Mysql5.5+的版本默认引擎都是InnoDB,早期的Mysql版本默认的引擎是MyISAM MyISAM 和 InnoDB的适用场景 M ...

  6. SSM-SpringMVC-24:SpringMVC异常高级之自定义异常

    ------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 自定义异常,大家都会,对吧,无非就是继承异常类等操作,很简单,我就不多扯皮了,但是在xml配置文件中有个不同的 ...

  7. ROC曲线的计算

    1.ROC曲线简介 在评价分类模型时,会用到ROC(receiver operating characteristic)曲线.ROC曲线可用来评价二元分类器( binary classifier)的优 ...

  8. tkinter中entry输入控件(四)

    entry控件 import tkinter wuya = tkinter.Tk() wuya.title("wuya") wuya.geometry("300x200+ ...

  9. 给xmpphp添加了几个常用的方法

    给xmpphp添加给了以下的常用方法: registerNewUser            //注册一个新用户 addRosterContact           //发送添加好友的请求 acce ...

  10. 12.app后端如何选择合适的数据库产品

    app后端的开发中,经常要面临的一个问题是:数据放在哪里? mysql ?redis?mongodb? 现在有这么多优秀的开源数据库产品,怎么根据业务场景来选择合适的数据? 常用的数据库产品的优缺点又 ...