在上一篇文章中我介绍了AOP的实现原理,下面我们就使用Emit来实现一个简单的AOP框架,最后再写一个使用这个框架的例子来检测一下效果。
1、SimpleAOP项目文件结构
由项目文件截图可以看出这个SimpleAOP确实挺Simple呀,项目虽然简单,不过用来学习AOP思想还是足够用的。
项目的主要功能部分是用Emit和Attribute实现的,如果大家对Emit和Attribute不了解的话,建议大家先学习一下IL&反射系列文章和EMIT学习系列文章。
下面逐一介绍项目中各文件的作用。
2、AspectContext
上一篇文章中两个切面类的WriteLog方法都没有参数,所以我们无法在WriteLog中获取原Add方法的相关信息,也就无法做一些跟原Add方法有关的操作,有的人可能说反正现在也用不到考虑那么多干嘛!你可能现在用不到,万一你以后需要校验参数格式呢?万一你以后需要记录方法返回值呢?万一你以后需要记录日志来自哪个方法呢?万一你以后需要记录异常的信息呢?等等……
这个AspectContext就是用来记录这些内容的,本例中的AspectContext记录了方法、参数、返回值、异常的相关内容。如果在上文的BeforeActionAttribute、AfterActionAttribute加上AspectContext的话,那它的作用应该是这样的:
public class BeforeActionAttribute:Attribute
{
public void WriteLog(AspectContext aspectContext)
{
//【获取原方法相关信息】
string methodName = aspectContext.Method.MethodName;
object[] paramValues = aspectContext.ParameterList.Select(delegate(ParameterMetaData parameter)
{
return parameter.ParameterValue;
}).ToArray();
object returnValue = aspectContext.ReturnValue.Value;
Exception ex = aspectContext.Exception.Ex;
//模拟记录日志
Console.WriteLine("BeforeAction");
}
}
public class AfterActionAttribute:Attribute
{
public void WriteLog(AspectContext aspectContext)
{
//【获取原方法相关信息】
string methodName = aspectContext.Method.MethodName;
object[] paramValues = aspectContext.ParameterList.Select(delegate(ParameterMetaData parameter)
{
return parameter.ParameterValue;
}).ToArray();
object returnValue = aspectContext.ReturnValue.Value;
Exception ex = aspectContext.Exception.Ex;
//模拟记录日志
Console.WriteLine("AfterAction");
}
}
有了这个AspectContext我们就可以在切面类中获取到原方法的一些内容,如果需要的话我们可以在切面类中做一些与原方法相关操作,这样切面类的作用就更加丰富了。
3、*MetaData
由于AspectContext中存储了方法、参数、返回值、异常的相关内容,为了体现OOP的开发思想,我新建了四个类来分别封装方法、参数、返回值、异常的相关内容,在AspectContext只需要保存跟这四个类相关的对象就可以了。
Metadata中定义了四个类:
- ExceptionMetadata:用于保存程序中exception的信息
- MethodMetadata :用于保存程序中方法的信息(目前只有方法名)
- ParameterMetadata:用于保存程序中方法的参数信息
- ReturnValueMetadata:用于保存程序中方法的返回值信息
4、AspectAttribute
下一步要定义切面了,在AOP框架中定义的切面其实起到了占位符的作用。客户端之所以能在方法中注入一些自定义代码,是因为我们在AOP框架中预留了允许代码注入的位置,再讲得通俗一些,客户端之所以能在方法中注入一些自定义代码,是因为框架就是这么设计的,这是设计好的,要的就是这效果。
既然这是设计好的,那代码的注入方式也就必须按照设计好的方式来,那就是用户自定义的切面类必须继承自BeforeActionAttribute、AfterActionAttribute或HandleExceptionAttribute,只有这样才能把用户自定义的切面类放到父类对应的占位符位置。
下面四个类中AspectAttribute类不是必须存在的,由于BeforeActionAttribute、AfterActionAttribute、HandleExceptionAttribute都要继承自Attribute,而且都有一个参数类型为AspectContext的Action方法,所以为这三个类创建了一个AspectAttribute基类。
public abstract class AspectAttribute:Attribute
{
public abstract void Action(AspectContext aspectContext);
}
public class BeforeActionAttribute:AspectAttribute
{
public override void Action(AspectContext aspectContext)
{
Console.WriteLine("BeforeAction");
}
}
public class AfterActionAttribute:AspectAttribute
{
public override void Action(AspectContext aspectContext)
{
Console.WriteLine("AfterAction");
}
}
public class HandleExceptionAttribute:AspectAttribute
{
public override void Action(AspectContext aspectContext)
{
Console.WriteLine("HandleException");
}
}
5、ProxyFactory
ProxyFactory是SimpleAOP对外的接口,对于需要使用AOP的类,必须使用ProxyFactory来生成相应的代理类。如果用上一篇文章中的示例来解释那就是:ProxyFactory能够根据ICal和Calculator动态生成DynamicCalculator,最后返回DynamicCalculator类的实例。
ProxyFactory类中只有一个静态方法,它就是用来生成动态代理类的。
public class ProxyFactory
{
public static T CreateProxy<T>(Type realProxyType)
{
DynamicProxyGenerator dynamicProxyGenerator = new DynamicProxyGenerator(typeof(T), realProxyType);
Type type = dynamicProxyGenerator.GenerateType();
T result = (T)System.Activator.CreateInstance(type);
return result;
}
}
6、DynamicProxyGenerator
这个类是整个框架的核心,ProxyFactory就是通过调用DynamicProxyGenerator中的GenerateType()方法实现根据现有type生成新type功能的。
我们在DynamicProxyGenerator中完成对现有type方法的拦截操作,并在拦截后根据方法本身Attribute依附情况进行代码注入。
public DynamicProxyGenerator()
{
}
public DynamicProxyGenerator(Type interfacType, Type realProxyType)
{
this._interfaceType = interfacType;
this._realProxyType = realProxyType;
}
public Type GenerateType()
{
//构造程序集
BuildAssembly();
//构造模块
BuildModule();
//构造类型
BuildType();
//构造字段
BuildField();
//构造函数
BuildConstructor();
//构造方法
BuildMethods();
Type dynamicType = typeBuilder.CreateType();
assemblyBuilder.Save(AssemblyFileName);
return dynamicType;
}