一、前言
看了使用AOP 使C#代码更清晰,很受启发。但版主似乎已经放弃C#了,所以他的这篇文章让人看得似懂非懂。但文中一段话说的特好:
有许多AOP类库通过使用编译事件和IL操作允许你在编译时“处理”这些方面,例如PostSharp;而某些类库使用DynamicProxy在运行时处理;某些要求你的类继承自ContextBoundObject使用C#内建特性来supportAspects。所有的这些都有某些“不便”。你不得不使用某些外部库,做足够的性能测试来那些类库可扩展等等。而你需要的只是一个非常简单的方式来实现“隔离”,可能并不是想要完全实现AOP。记住,你的目的是隔离那些并不重要的核心代码,来让一切变得简单并且清晰!
学AOP。首先要弄清楚什么是AOP,恐怕网上无数的文章只能让我们不知所以,每篇文章要不是直接代码,就是眉毛胡子摘抄一大篇。我花了几个晚上,在网上另外找了一篇终于能让人理解什么是AOP的文章:颠覆C#王权的“魔比斯环” — 实现AOP框架的终极利器,但这篇文章没有深入讲解Emit实现,尽在最后呈现了一份Emit实现AOP的成果,让人欲罢不能。好了,啰嗦了一大篇,我想用几句话谈谈我对AOP的理解:
1 了解来历:AOP(Aspect Oriented Programming)的中文意思是“面向方面编程”。由施乐公司提出,是OOP的一种扩展。
2 弄清楚表现形式:代码侵入,他有一个新词叫织入,是AOP最重要最明显的特性。所谓织入:就是对原生方法进行拦截(这其中也包括构造方法),在执行原方法时,要在两个位置来执行我们织入的代码,如日志代码。这两个位置是before(在原代码执行之前)和after(在原代码执行之后)。例如:
原生代码:
public class A
{
private int _i=0;
public int Add(int i)
{
return i+=i;
}
}
AOP织入代码后会变成类似如下形式:
public class A
{
private int _i=0;
public int Add(int i)
{
int result=_i
Log("Add begin");
result+=i;
<pre name="code" class="csharp"> Log("Add end");
return result; }}
当然,还可以对源代码进行around(完全取代原来的代码)。
3 搞清楚用途:有点像黑客!目的已经很明显,但这么做的意义是什么呢?使用AOP技术,将一些与业务无关的系统功能完全独立成单独的模块,在必要时可以简捷方便的使用,比如比如安全性检查、日志记录、性能监控,异常处理等,一方面维护了OOP的“单一职责”原则,降低模块之间的耦合度,另一方面是这些处理透明化,达到简洁代码,直接提高可读性,间接提高可维护性,提高工作效率的目的。
4 知道实现方法:包括静态织入和动态织入,静态织入是在程序编译时完成的,动态织入是在运行期间完成的。
5 理解与OOP/OOD的关系:有人一句话总结:OOD/OOP面向名词领域,AOP面向动词领域。
6 应该了解的一些基本概念:
。联结点(JointPoint) :一个联结程序执行过程中的一个特定点。典型的联结点有:调用一个方法;方法执行这个过程本身;类初始化;对象初始化等。联结点是 AOP 的核心概念之一,它用来定义在程序的哪里通过 AOP 加入新的逻辑。
。切入点(Pointcut) :一个切入点是用来定义某一个通知该何时执行的一组联结点。通过定义切入点,我们可以精确地控制程序中什么组件接到什么通知。上面我们提到,一个典型的联结点是方法调用,而一个典型的切入点就是对某一个类的所在方法调用的集合。通常我们会通过组建复杂的切入点来控制通知什么时候被执行。 。通知(Advice) :在某一个特定的联结点处运行的代码称为“通知”。通知有很多种,比如在联结点之前执行的前置通知(before advice)和在联结点之后执行的后置通知(after advice) 。
。方面(Aspect) :通知和切入点的组合叫做方面,所以,方面定义了一段程序中应该包括的逻辑,以及何时应该执行该逻辑。
。织入(Weaving) :织入是将方面真正加入程序代码的过程。对于静态 AOP 方案而言,织入是在编译时完成的,通常是在编译过程中增加一个步骤。类似的,动态 AOP 方案则是在程序运行是动态织入的。
。目标(Target) :如果一个对象的执行过程受到某一个 AOP 的修改,那么它就叫一个目标对象。目标对象通常也称为被通知对象。
。引入(Introduction) : 通过引入,可以在一个对象中加入新的方法或属性,以改变它的结构,这样即使该对象的类没有实现某一个接口,也可以修改它,使之成为该接口的一个实现。
7 C#中的AOP思想:典型的,Attribute就是一种AOP思想的简单实现,但使用很繁琐,而且反射机制总是让人感觉有很大的性能开销。其他的,通过Emit织入是似乎是完美的做法,但太复杂,一般人(至少我)弄不来。二那些需要指定继承某些类来完成AOP的做法更是一眼便被深恶痛绝(谁希望随时享受者被强奸的感觉?)。因此,我非常喜欢使用AOP 使C#代码更清晰一文提到的AOP思想,但博主仅仅实现了前置织入。本文随后我将对其进一步完善,完成后置织入的处理。完成后的代码基本上已经可以投入实用了。
二、代码实现
1 核心代码部分:AspectF.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Diagnostics;
namespace aoptest {
/// <summary>
/// AspectF,AOP基础类
/// </summary>
public class AspectF
{
/// <summary>
/// 获取AspectF实例
/// </summary>
public static AspectF Define
{
[DebuggerStepThrough]
get
{
return new AspectF();
}
}
/// <summary>
/// Action执行的出错信息
/// </summary>
/// <typeparam name="T"></typeparam>
[DebuggerStepThrough]
public class ActionError<T> : Chiefan.Common.ActionResult<T> { }
/// <summary>
/// Action重试过程中的出错处理委托
/// </summary>
/// <param name="e"></param>
/// <param name="cancel"></param>
public delegate void RetryErrorHandle(Exception e, ref bool cancel);
/// <summary>
/// DO的执行结果状态
/// </summary>
public ActionError<Exception> Error { get { return error; } }
/// <summary>
/// 是否启用计算并打印执行耗时
/// </summary>
public bool AllowHowLong { get; set; }
/// <summary>
/// 执行一个没有返回值的workItem
/// </summary>
/// <param name="work">action</param>
[DebuggerStepThrough]
public void Do(Action work)
{
System.Diagnostics.StackTrace stackTrace = new System.Diagnostics.StackTrace();
System.Diagnostics.StackFrame sf = stackTrace.GetFrame(1);//[0]为本身的方法 [1]为调用方法
string abc= string.Format("{0}.{1}", sf.GetMethod().ReflectedType.FullName, sf.GetMethod().Name);//类名
try
{
if (this.Chain == null)
{
this.WriteLog(Logger.LogType.Debug, "方法[{0}]执行时间计时开始", abc);
int beginTime = Environment.TickCount;
work();
this.WriteLog(Logger.LogType.Debug, "方法[{0}]执行时间共计{1}毫秒", abc, Environment.TickCount - beginTime);
}
else
{
this.Chain(() => {
this.WriteLog(Logger.LogType.Debug, "方法[{0}]执行时间计时开始", abc);
int beginTime = Environment.TickCount;
work();
this.WriteLog(Logger.LogType.Debug, "方法[{0}]执行时间共计{1}毫秒", abc, Environment.TickCount - beginTime);
});
}
}
catch (Exception ex)
{
error.ErrorCode = -1;
error.ErrorMessage = "未知错误";
error.Data = ex;
this.WriteLog(Logger.LogType.Error, "{0} - {1}", abc, ex.Message);
}
if (this.PostChain != null)
this.PostChain(() => { });
}
/// <summary>
/// 绑定一个新的委托
/// </summary>
/// <param name="newAspectDelegate">委托</param>
/// <returns>返回AspectF实例</returns>
[DebuggerStepThrough]
public AspectF Combine(Action<Action> newAspectDelegate, bool isPost=false)
{
if (isPost)
{
if (this.PostChain == null)
{
this.PostChain = newAspectDelegate;
}
else
{
Action<Action> existingChain = this.PostChain;
Action<Action> callAnother = (work) =>
existingChain(() => newAspectDelegate(work));
this.PostChain = callAnother;
}
}
else
{
if (this.Chain == null)
{
this.Chain = newAspectDelegate;
}
else
{
Action<Action> existingChain = this.Chain;
Action<Action> callAnother = (work) =>
existingChain(() => newAspectDelegate(work));
this.Chain = callAnother;
}
}
return this;
}
/// <summary>
/// 打印日志
/// </summary>
/// <param name="logType"></param>
/// <param name="format"></param>
/// <param name="args"></param>
[DebuggerStepThrough]
public void WriteLog(Logger.LogType logType, string format, params object[] args)
{
switch (logType)
{
case Logger.LogType.Error:
log.Error(format, args);
break;
case Logger.LogType.Debug:
log.Debug(format, args);
break;
case Logger.LogType.Warn:
log.Warn(format, args);
break;
case Logger.LogType.Fatal:
log.Fatal(format, args);
break;
case Logger.LogType.Info:
default:
log.Info(format, args);
break;
}
}
Action<Action> Chain = null;
Action<Action> PostChain = null;
Logger.ILog log = Logger.Log.Instance;
ActionError<Exception> error = new ActionError<Exception>();
}
}
2 扩展代码部分
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
namespace aoptest
{
using Logger = Chiefan.eSail2.Logger;
/// <summary>
/// AspectF扩展类
/// </summary>
public static class AspectExt
{
/// <summary>
/// 记录日志
/// </summary>
/// <param name="aspect"></param>
/// <param name="logType">日志类型:error|debug|info</param>
/// <param name="message"></param>
/// <param name="isPost">是否后置执行</param>
/// <returns></returns>
[DebuggerStepThrough]
public static AspectF Log(this AspectF aspect, Logger.LogType logType, string message, bool isPost = false)
{
return aspect.Combine((work) =>
{
aspect.WriteLog(logType, "{0}", message);
work();
}, isPost);
}
/// <summary>
/// 性能监视
/// </summary>
/// <param name="aspects"></param>
/// <returns></returns>
[DebuggerStepThrough]
public static AspectF HowLong(this AspectF aspects)
{
aspects.AllowHowLong = true;
return aspects;
}
/// <summary>
/// Do的失败后重复执行版本
/// </summary>
/// <param name="aspects"></param>
/// <param name="errorHandle">错误处理句柄</param>
/// <param name="failedHandle">失败处理句柄</param>
/// <param name="retryDuration">重试间隔(毫秒)</param>
/// <param name="retryCount">重试次数</param>
/// <returns></returns>
[DebuggerStepThrough]
public static AspectF Retry(this AspectF aspects,
AspectF.RetryErrorHandle errorHandle, Action failedHandle,
int retryCount = 1, int retryDuration = 1000)
{
return aspects.Combine((work) =>
Retry(retryDuration, retryCount, errorHandle, failedHandle, work));
}
/// <summary>
/// Retry
/// </summary>
/// <param name="retryDuration">retry的时间间隔</param>
/// <param name="retryCount">retry次数</param>
/// <param name="errorHandler">错误处理句柄</param>
/// <param name="retryFailed">retry失败处理句柄</param>
/// <param name="work">真实的委托内容</param>
[DebuggerStepThrough]
public static void Retry(int retryDuration, int retryCount,
AspectF.RetryErrorHandle errorHandler, Action retryFailed, Action work)
{
bool cancel = false;
bool success = false;
do
{
try
{
success = true;
work();
}
catch (Exception ex)
{
errorHandler(ex, ref cancel);
System.Threading.Thread.Sleep(retryDuration);
}
} while (retryCount-- > 0);
if (!success)
retryFailed();
}
[DebuggerStepThrough]
public static AspectF Delay(this AspectF aspect, int milliseconds)
{
return aspect.Combine((work) =>
{
System.Threading.Thread.Sleep(milliseconds);
work();
});
}
[DebuggerStepThrough]
public static AspectF MustBeNonNull(this AspectF aspect, Action<AspectF> errorHandle, params KeyValuePair<string,object>[] args)
{
return aspect.Combine((work) =>
{
for (int i = 0; i < args.Length; i++)
{
object arg = args[i].Value;
if (arg == null)
{
string errorMessage = string.Format("{0}, Action被终止.", args[i].Key);
aspect.Error.ErrorCode = -1;
aspect.Error.ErrorMessage = errorMessage;
aspect.Error.Data = new ArgumentException(errorMessage);
System.Diagnostics.StackTrace stackTrace = new System.Diagnostics.StackTrace();
System.Diagnostics.StackFrame sf = stackTrace.GetFrame(1);//[0]为本身的方法 [1]为调用方法
string abc = string.Format("{0}.{1}", sf.GetMethod().ReflectedType.FullName, sf.GetMethod().Name);//类名
aspect.WriteLog(Logger.LogType.Error, "{0} - {1}", abc, errorMessage);
errorHandle(aspect);
}
}
work();
});
}
public static AspectF MustBeNonDefault<T>(this AspectF aspect, Action<AspectF> errorHandle, params KeyValuePair<string,T>[] args) where T : IComparable
{
return aspect.Combine((work) =>
{
T defaultvalue = default(T);
for (int i = 0; i < args.Length; i++)
{
T arg = args[i].Value;
if (arg == null || arg.Equals(defaultvalue))
{
//throw new ArgumentException(string.Format("Parameter at index {0} is null", i));
string errorMessage = string.Format("{0}, Action被终止.", args[i].Key);
aspect.Error.ErrorCode = -1;
aspect.Error.ErrorMessage = errorMessage;
aspect.Error.Data = new ArgumentException(errorMessage);
System.Diagnostics.StackTrace stackTrace = new System.Diagnostics.StackTrace();
System.Diagnostics.StackFrame sf = stackTrace.GetFrame(1);//[0]为本身的方法 [1]为调用方法
string abc = string.Format("{0}.{1}", sf.GetMethod().ReflectedType.FullName, sf.GetMethod().Name);//类名
aspect.WriteLog(Logger.LogType.Error, "{0} - {1}", abc, errorMessage);
errorHandle(aspect);
}
}
work();
});
}
public static AspectF WhenTrue(this AspectF aspect, params Func<bool>[] conditions)
{
return aspect.Combine((work) =>
{
int i = 0;
foreach (Func<bool> condition in conditions)
{
if (!condition())
{
string errorMessage = string.Format("执行条件[{0}]不满足要求,Action被终止.", i);
aspect.Error.ErrorCode = -1;
aspect.Error.ErrorMessage = errorMessage;
aspect.Error.Data = new ArgumentException(errorMessage);
System.Diagnostics.StackTrace stackTrace = new System.Diagnostics.StackTrace();
System.Diagnostics.StackFrame sf = stackTrace.GetFrame(1);//[0]为本身的方法 [1]为调用方法
string abc = string.Format("{0}.{1}", sf.GetMethod().ReflectedType.FullName, sf.GetMethod().Name);//类名
aspect.WriteLog(Logger.LogType.Error, "{0} - {1}", abc, errorMessage);
return;
}
i++;
}
work();
});
}
}
}
3 使用范例:
public static void InsertCustomerTheEasyWay(string firstName, string lastName, int age,
Dictionary<string, string> attributes)
{
AspectF.Define
.Log(Logger.LogType.Debug, "Inserting customer the easy way1")
.Log(Logger.LogType.Debug, "Inserting customer the easy way2")
.Log(Logger.LogType.Debug, "Inserting customer the ooo2 way", true)
.Log(Logger.LogType.Debug, "Inserting customer the ooo1 way", true)
.HowLong()
.Do(() => { Console.WriteLine("ok!"); })
//.Do(() => {throw new Exception("a dog is at ahead!"); })
//.Do<string>(new Func<string>(() => { Console.WriteLine("ok"); return "okey!"; }));
;
}
准备在项目上试用一下,试用成功后我将继续更新本文。
欢迎共同探讨,转载请保留本文连接:C#之AOP初探