前言
尽管同时遭受众多正反两方面的评价,但采用AOP(Aspect-oriented Programming)技术开发的应用框架无论在.NET阵营还是Java阵营都越来越普遍的情况。从微软在MTS和COM+中引入声明方式(Declarative)的编码技术开始,通过元数据层次扩展对象能力似乎成为很多应用框架同时解决使用复杂性和功能丰富性矛盾的有效手段之一,毕竟虽然通过设计模式等技巧可以尽量避免类泛滥的情况,但编码上还是不如打个标签(.NET Attribute / Java Annotation)方便。
AOP开发为各种应用逻辑之外的控制和管理机制以横切方式“楔入”控制流程提供便利,以COM+为例Role-Based Security、事务性、同步处理、上下文等很多机制的扩展可以在Attribute层次完成。虽然实现AOP本身可以通过Attribute之外的很多方式完成,但随着开发语言的演进,在.NET和Java开发中Attribute仍是一个不错的方式。
不过实现AOP本身不仅限于Attribute的开发,为了通用并提高处理效率,根据.NET平台的特点,还需要完成很多配套机制:
Dynamic Proxies :动态代理,代理类的作用是在目标类型的外部增加一个壳,它本质上是一个丰富了之后的目标对象;
Dynamic MSIL Generator :对于高级AOP框架,为了能适应大多数类型的要求,最灵活同时也是难度最高的部分就是动态生成MSIL,实现一个“落实了”之后的代理类型;
Cache & Assembly Generation :为了提高执行速度,最好把动态生成的新类型缓冲到内存,同时为相应的Assembly提供面向I/O的保存和加载能力;
Custom Class Loader :定制类型加载过程,而不仅仅是直接对目标类型new();
Semantic Attribute:语义属性,扩展外部控制的语义属性,例如:安全、授权、访问控制、日志、性能监控等;
原始的实现
在介绍一个相对完成的AOP实现之前,我们先看看如何用Attribute和专用自定义接口实现一个原始的、但控制逻辑“站在业务对象之外”的框架。
在Builder模式中,我们通常用一个Director对象定一BuildUp的过程、控制每个Build Step的次序,但一种更简洁的方式是把执行次序标注在Builder类型的Attribute上,这样实现过程如下:
定义抽象的Builder类型:
C#
// Builder 抽象行为定义
public interface IAttributedBuilder
{
IList<string> Log { get;} // 记录Builder 的执行情况
void BuildPartA();
void BuildPartB();
void BuildPartC();
}
public class AttributedBuilder : IAttributedBuilder
{
private IList<string> log = new List<string>();
public IList<string> Log { get { return log; } }
public void BuildPartA() { log.Add("a"); }
public void BuildPartB() { log.Add("b"); }
public void BuildPartC() { log.Add("c"); }
}
此后,将Director指导Builder组装的每个步骤通过 DirectorAttribute 属性类来表示,而Director在BuildUp的过程中,通过反射获得相关Builder的DirectorAttribute列表,对列表按照优先级排序后,执行每个DirectorAttribute指向的Build Part方法。
C#
// 通过attribute 扩展Director
[AttributeUsage(AttributeTargets.Class, AllowMultiple =true)]
public sealed class DirectorAttribute : Attribute, IComparable<DirectorAttribute>
{
private int priority; // 执行优先级
private string method;
public DirectorAttribute(int priority, string method)
{
this.priority = priority;
this.method = method;
}
public int Priority { get { return this.priority; } }
public string Method { get { return this.method; } }
// 提供按照优先级比较的ICompare<T> 实现, 由于Array.Sort<T>
// 实际是升序排列,而Array.Reverse 是完全反转,因此这里调
// 整了比较的方式为“输入参数优先级- 当前实例优先级”
public int CompareTo(DirectorAttribute attribute)
{
return attribute.priority - this.priority;
}
}
public class Director
{
public void BuildUp(IAttributedBuilder builder)
{
// 获取Builder 的DirectorAttribute 属性
object[] attributes =
builder.GetType().GetCustomAttributes(typeof(DirectorAttribute), false);
if (attributes.Length <= 0) return;
DirectorAttribute[] directors = new DirectorAttribute[attributes.Length];
for (int i = 0; i < attributes.Length; i++)
directors[i] = (DirectorAttribute)attributes[i];
// 按每个DirectorAttribute 优先级逆序排序后,逐个执行
Array.Sort<DirectorAttribute>(directors);
foreach (DirectorAttribute attribute in directors)
InvokeBuildPartMethod(builder, attribute);
}
// helper method : 按照DirectorAttribute 的要求,执行相关的Builder 方法
private void InvokeBuildPartMethod(
IAttributedBuilder builder, DirectorAttribute attribute)
{
switch (attribute.Method)
{
case "BuildPartA": builder.BuildPartA(); break;
case "BuildPartB": builder.BuildPartB(); break;
case "BuildPartC": builder.BuildPartC(); break;
}
}
}
使用DirectorAttribute指导Builder的装配过程:
C#
[Director(1, "BuildPartB")]
[Director(2, "BuildPartA")]
public class AttributedBuilder : IAttributedBuilder
Unit Test
[TestMethod]
public void Test()
{
IAttributedBuilder builder = new AttributedBuilder();
Director director = new Director();
director.BuildUp(builder);
Assert.AreEqual<string>("a", builder.Log[0]);
Assert.AreEqual<string>("b", builder.Log[1]);
}
修改BuildUp过程
如果要修改Builder的装配过程,仅需要增加、维护相关属性即可,例如:C#实际工程中,Attribute常常会和反射、配置一同使用,比如[Director(2, "BuildPartA")]中优先级和方法名称都可以在配置文件定义。虽然看起来开发阶段增加了一些额外的代码工作(例如:Director和DirectorAttribute的编码),但从使用者角度看,减少了反复定义Director相关BuidUp装配的过程。对于其他行为型和结构型模式,Attribute同样可以从很多方面扩展。
[Director(3, "BuildPartA")]
[Director(2, "BuildPartB")]
[Director(1, "BuildPartC")]
public class AttributedBuilder : IAttributedBuilder
Unit Test
[TestMethod]
public void Test()
{
IAttributedBuilder builder = new AttributedBuilder();
Director director = new Director();
director.BuildUp(builder);
Assert.AreEqual<string>("a", builder.Log[0]);
Assert.AreEqual<string>("b", builder.Log[1]);
Assert.AreEqual<string>("c", builder.Log[2]);
}
应用情景讨论
上面的示例虽然因为基于专用接口具有很大的局限性,但其实反映了Attirubte方式下AOP开发的一个关键理念——横切,也就是新增的机制游离在业务逻辑之外对业务逻辑发生作用。但如果想让AOP框架更具普适性,就需要把框架和具体类型间的直接调用(包括反射调用)打破,按照面向对象的常规原理,这里需要填补一个新的从客户程序看就是目标对象,但从执行AOP框架看是符合自己需要的新对象出来,为此我们需要通过增加一个所谓“透明代理”的方式完成,这样新的执行流程如下:
概念上这个透明代理类的主要作用就是把客户程序对目标类型的直接调用拆成两步甚至多步调用,而对客户程序而言他感觉不到这个重发的过程,实际执行步骤相对更为复杂,客户程序调用Transparent Proxy,而Transparent Proxy会调用Real Proxy,Real Proxy后续还可能需要完成一系列Message Sink操作。
下面是两个更为完整的面向通用类型的AOP实现 (.NET的Attribute和Property一般都译为“属性”,而比较大型的.NET应用中两者又常常同时出现,因此很容易混淆。在同时出现两个概念的时候,本文尽量采用“属性”和“属性方法”分别指向Attribute和Property,尽量避免误会)。
采用.NET平台自带的AOP机制实现:
CLR本身对外提供了对AOP机制的支持,不过如用它完成装饰属性则有一个非常不利的限制——客户类型必须继承自MarshalByRefObject 或 ContextBoundObject.。下面是一个示例:
C# 定义装饰属性的基类
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property)]
abstract class DecoratorAttributeBase : Attribute
{
public abstract void Intercept(object target);
}
C# 定义代理类
class CustomProxy<T> : RealProxy, IDisposable where T : MarshalByRefObject
{
/// 构造过程中把Proxy需要操作的内容与实际目标对象实例Attach到一起。
public CustomProxy(T target) : base(target.GetType()){ AttachServer(target); }
/// 析构过程则借助proxy和目标对象实例的Attach,便于GC回收。
public void Dispose() { DetachServer(); }
public static T Create(T target)
{
if (target == null) throw new ArgumentNullException("target");
return (T)(new CustomProxy<T>(target).GetTransparentProxy());
}
/// 实际执行的拦截,并根据装饰属性进行定制处理。
public override IMessage Invoke(IMessage msg)
{
MethodCallMessageWrapper caller =
new MethodCallMessageWrapper((IMethodCallMessage)msg);
// 提取实际宿主对象
MethodInfo method = (MethodInfo)caller.MethodBase;
T target = (T)GetUnwrappedServer();
DecoratorAttributeBase[] attributes = (DecoratorAttributeBase[])
method.GetCustomAttributes(typeof(DecoratorAttributeBase), true);
if (attributes.Length > 0)
foreach (DecoratorAttributeBase attribute in attributes)
attribute.Intercept(caller);
object ret = method.Invoke(target, caller.Args);
// 拦截处理后,继续回到宿主对象的调用过程
return new ReturnMessage(ret, caller.Args, caller.ArgCount,
caller.LogicalCallContext, caller);
}
}
C# 定义具体装饰属性
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]C# 定义业务对象
class ArgumentTypeRestrictionAttribute : DecoratorAttributeBase
{
private Type type;
public ArgumentTypeRestrictionAttribute(Type type) { this.type = type; }
public override void Intercept(object target)
{
MethodCallMessageWrapper caller = (MethodCallMessageWrapper)target;
if (caller.ArgCount == 0) return;
for (int i = 0; i < caller.ArgCount; i++)
{
object arg = caller.Args[i];
if ((arg.GetType() != type) && (!arg.GetType().IsAssignableFrom(type)))
throw new ArgumentException(i.ToString());
}
}
}
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
class ArgumentNotEmptyAttribute : DecoratorAttributeBase
{
public override void Intercept(object target)
{
MethodCallMessageWrapper caller = (MethodCallMessageWrapper)target;
if(caller.ArgCount == 0) return;
foreach(object arg in caller.Args)
if(string.IsNullOrEmpty((string)arg))
throw new ArgumentException();
}
}
class User : MarshalByRefObjectUnit Test
{
private string name;
private string title;
[ArgumentTypeRestriction(typeof(string))] // 提供拦截入口
[ArgumentNotEmpty()] // 提供拦截入口
public void SetUserInfo(object name, object title)
{
this.name = (string)name;
this.title = (string)title;
}
}
[TestMethod]从上面的示例不难看出通过Remoting和MarshalByRefObject的组合,可以借助代理类把调用过程拦截,并“横切”的楔入额外的控制逻辑。但比较遗憾的是这种方式要把C#类型唯一一次继承机会让给MarshalByRefObject,侵入性过高。尽管有很多不足,不过在项目中采用该方法实现一个透明的装饰属性框架成本相对还算较低的。另外,上面的示例中并没有提供对params参数、对于属性方法和构造函数的支持,更为复杂的工程化实现可以参考Enterprise Library的Policy Injection代码块。
public void Test()
{
User user = CustomProxy<User>.Create(new User());
user.SetUserInfo("joe", "manager"); // 成功
try
{
user.SetUserInfo(20, "manager");
}
catch (Exception exception)
{
// 因第一个参数类型异常被拦截后抛出异常
Assert.AreEqual<string>("0", exception.Message);
}
try
{
user.SetUserInfo("", "manager");
}
catch (Exception exception)
{
// 因name为空被拦截后抛出异常
Assert.AreEqual<string>("string is null or empty", exception.Message);
}
}
自定义代理拦截框架方式
如果希望在.NET平台实现一个侵入性低的框架,则需要通过一些更复杂的、更“动态”的手段实现对待装饰对象的包装,概念上.NET Framework CLR实际执行的是底层MSIL,因此部分主流框架采用的是通过System..Reflection.Emit命名空间下的对象实现动态Assembly,采用该方式的主要意义是根据用户提供的类型实例,动态用MSIL装配出一个新的类型,而新的类型在执行具体方法、属性方法、委托和事件的同时,调用动态装配的外界方法(/方法列表)。
首先,在执行前框架通过System.Reflection.Emit根据目标类型生成新的动态类型,生成动态类型的过程包括创建构造函数、创建方法、属性方法和事件,并且为外部“横切”机制提供入口;
执行过程中,客户程序实际调用的是动态生成的新类型;
C# 定义实际执行其他方法的委托
public delegate object MethodCall(object target, MethodBase method,C# 定义各种装饰属性
object[] parameters, DecoratorAttribute[] attributes);
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property |
AttributeTargets.Interface, Inherited = true)]
public abstract class DecoratorAttribute : Attribute
{
public abstract void Process(object target, MethodBase method, object[] parameters);
}
/// 代表执行前和执行后外部“横切”机制的抽象对象
public abstract class BeforeDecoratorAttribute : DecoratorAttribute { }
public abstract class AfterDecoratorAttribute : DecoratorAttribute { }
C# 定义装饰对象注入器
public class DecoratorInjectorC# 定义外部横切机制
{
public const string AssemblyName = "TEMP_DYNAMIC_ASSEMBLY";
public const string ClassName = "TEMP_CLASS_NAME";
private static TypeBuilder typeBuilder;
private static FieldBuilder target, iface;
public static object InjectHandlerMethod(object target,
MethodBase method,
object[] parameters,
DecoratorAttribute[] attributes)
{
object returnValue = null;
foreach (DecoratorAttribute attribute in attributes)
if(attribute is BeforeDecoratorAttribute)
attribute.Process(target, method, parameters, null);
returnValue = target.GetType().GetMethod(method.Name).Invoke(target, parameters);
foreach (DecoratorAttribute attribute in attributes)
if (attribute is AfterDecoratorAttribute)
attribute.Process(target, method, parameters, null);
return returnValue;
}
public static object Create(object target, Type interfaceType)
{
Type proxyType = EmiProxyType(target.GetType(), interfaceType);
return Activator.CreateInstance(proxyType, new object[] { target, interfaceType });
}
private static Type EmiProxyType(Type targetType, Type interfaceType)
{
AppDomain currentDomain = System.Threading.Thread.GetDomain();
AssemblyName assemblyName = new AssemblyName();
assemblyName.Name = AssemblyName;
//Only save the custom-type dll while debugging
AssemblyBuilder assemblyBuilder =
currentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
ModuleBuilder modBuilder = assemblyBuilder.DefineDynamicModule(ClassName);
string typeName = assemblyName + "__Proxy" + interfaceType.Name + targetType.Name;
Type type = modBuilder.GetType(typeName);
if (type == null)
{
typeBuilder = modBuilder.DefineType(typeName, TypeAttributes.Class | TypeAttributes.Public,
targetType.BaseType, new Type[] { interfaceType });
target = typeBuilder.DefineField("target", interfaceType, FieldAttributes.Private);
iface = typeBuilder.DefineField("iface", typeof(Type), FieldAttributes.Private);
EmitConstructor(typeBuilder, target, iface);
MethodInfo[] methods = interfaceType.GetMethods();
foreach (MethodInfo m in methods)
EmitProxyMethod(m, typeBuilder);
type = typeBuilder.CreateType();
}
return type;
}
private static void EmitProxyMethod(MethodInfo method, TypeBuilder typeBuilder)
{
// 1、定义动态IL 生成对象
Type[] paramTypes = GetParameterTypes(method);
MethodBuilder methodBuilder = typeBuilder.DefineMethod(method.Name,
MethodAttributes.Public | MethodAttributes.Virtual, method.ReturnType, paramTypes);
ILGenerator il = methodBuilder.GetILGenerator();
LocalBuilder parameters = il.DeclareLocal(typeof(object[]));
il.Emit(OpCodes.Ldc_I4, paramTypes.Length);
il.Emit(OpCodes.Newarr, typeof(object));
il.Emit(OpCodes.Stloc, parameters);
for (int i = 0; i < paramTypes.Length; i++)
{
il.Emit(OpCodes.Ldloc, parameters);
il.Emit(OpCodes.Ldc_I4, i);
il.Emit(OpCodes.Ldarg, i + 1);
if (paramTypes[i].IsValueType)
il.Emit(OpCodes.Box, paramTypes[i]);
il.Emit(OpCodes.Stelem_Ref);
}
il.EmitCall(OpCodes.Callvirt,
typeof(DecoratorInjector).GetProperty("InjectHandler").GetGetMethod(), null);
// 2、生成目标对象实例
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, (FieldInfo)target);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, (FieldInfo)target);
il.EmitCall(OpCodes.Call, typeof(object).GetMethod("GetType"), null);
il.EmitCall(OpCodes.Call, typeof(MethodBase).GetMethod("GetCurrentMethod"), null);
// 3、生成目标对象方法
il.EmitCall(OpCodes.Call, typeof(DecoratorInjector).GetMethod("GetMethod"), null);
// 4、生成参数
il.Emit(OpCodes.Ldloc, parameters);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, (FieldInfo)iface);
il.EmitCall(OpCodes.Call, typeof(MethodBase).GetMethod("GetCurrentMethod"), null);
il.EmitCall(OpCodes.Call, typeof(DecoratorInjector).GetMethod("GetMethod"), null);
il.Emit(OpCodes.Ldtoken, typeof(DecoratorAttribute));
il.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"));
il.Emit(OpCodes.Ldc_I4, 1);
il.EmitCall(OpCodes.Callvirt,
typeof(MethodInfo).GetMethod("GetCustomAttributes",
new Type[] { typeof(Type), typeof(bool) }), null);
// 5、导入“横切”对象
il.EmitCall(OpCodes.Callvirt, typeof(DecoratorInjector).GetMethod("UnionDecorators"), null);
il.EmitCall(OpCodes.Callvirt, typeof(MethodCall).GetMethod("Invoke"), null);
if (method.ReturnType == typeof(void))
il.Emit(OpCodes.Pop);
else if (method.ReturnType.IsValueType)
{
il.Emit(OpCodes.Unbox, method.ReturnType);
il.Emit(OpCodes.Ldind_Ref);
}
il.Emit(OpCodes.Ret);
}
/// 通过动态生成的MSIL形成构造方法
private static void EmitConstructor(TypeBuilder typeBuilder, FieldBuilder target, FieldBuilder iface)
{
Type objType = Type.GetType("System.Object");
ConstructorInfo objCtor = objType.GetConstructor(new Type[0]);
ConstructorBuilder pointCtor = typeBuilder.DefineConstructor(MethodAttributes.Public,
CallingConventions.Standard, new Type[] { typeof(object), typeof(Type) });
ILGenerator ctorIL = pointCtor.GetILGenerator();
ctorIL.Emit(OpCodes.Ldarg_0);
ctorIL.Emit(OpCodes.Call, objCtor);
ctorIL.Emit(OpCodes.Ldarg_0);
ctorIL.Emit(OpCodes.Ldarg_1);
ctorIL.Emit(OpCodes.Stfld, target);
ctorIL.Emit(OpCodes.Ldarg_0);
ctorIL.Emit(OpCodes.Ldarg_2);
ctorIL.Emit(OpCodes.Stfld, iface);
ctorIL.Emit(OpCodes.Ret);
}
public static MethodCall InjectHandler
{
get { return new MethodCall(InjectHandlerMethod); }
}
public static Type[] GetParameterTypes(MethodInfo method)
{
if (method == null) return null;
Type[] types = new Type[method.GetParameters().Length];
int i = 0;
foreach (ParameterInfo parameter in method.GetParameters())
types[i++] = parameter.ParameterType;
return types;
}
public static MethodInfo GetMethod(Type type, MethodBase method)
{
return type.GetMethod(method.Name);
}
public static DecoratorAttribute[] UnionDecorators(object[] obj)
{
return (DecoratorAttribute[])obj;
}
}
public class LogAttribute : BeforeDecoratorAttributeUnit Test
{
public override void Process(object target, MethodBase method, object[] parameters)
{
Trace.WriteLine(method.Name);
}
}
public class ParameterCountAttribute : BeforeDecoratorAttribute
{
public override void Process(object target, MethodBase method, object[] parameters)
{
Trace.WriteLine(target.GetType().Name);
}
}
public interface IBizObject进一步分析
{
/// 对对象功能的装饰采用“横切”而非传统的继承方式获得,
/// 中间代理对象的构造是隐式,而且是由DecoratorInjector包装的,
/// 从外部角度看,对象的扩展是在Meta信息部分扩展,而且有关的约
/// 束定义在接口而非实体类层次。
[Log]
[ParameterCount]
int GetValue();
}
public class BizObject : IBizObject
{
public int GetValue() { return 0; }
}
[TestMethod]
public void Test()
{
IBizObject obj = (IBizObject)DecoratorInjector.Create(
new BizObject(), typeof(IBizObject));
int val = obj.GetValue();
Assert.AreEqual<int>(0, val);
}
通过上面两个示例不难看出虽然通过Remoting的MarshalByReference或System.Reflection.Emit命名空间下的对象可以通过透明代理把装饰类型加载到目标对象上。尤其对于后者,类似有关授权控制、有效性检查等内容都可以在不修改外部业务逻辑的情况下,通过修改在具体业务类型甚至他们接口的层次实施新的控制。
不过用于实际项目中还有很多欠缺,主要是性能问题,主要因为反射和动态加载所致。在类似Castle等一些更成熟的框架中会更多借助缓冲,把动态创建的代理类实例保存在内存里,同时把动态生成的Assembly保存在文件系统中,借以最大程度的提高执行效率和Assembly重用性。