0%

MEF核心笔记(6)让 MEF 拥抱 AOP

最近推荐同事在项目中使用起了 MEF,用其构建一个插件式的多人开发框架,因为该框架不是让我去设计了,所以对于 MEF 和 IOC 等概念不是很了解的同事,便会出现各种问题。接入 AOP 便是其中的问题之一,看在大家都是一起工作的同事,能帮的我自然会尽量去帮,不过,过不了多久我就会离职了,所以,且行且珍惜吧。

IOC 和 AOP 的概念

对于 IOC 和 AOP,这应该又算是一个老生常谈的问题,相应的说明和资料比比皆是,所以,我在这里也不多做讲解,只是概括性的总结下。

  • IOC:从英文意思来说,叫“控制反转”,即原来我们的各种操作只能依赖于抽象的具体实现,而通过 DI(依赖注入),我们的操作只依赖于抽象本身,具体实现是在运行时动态注入的。这样我们的依赖被倒置了,这就是 IOC,它不是组件和框架,它是设计模式上的东西。
  • AOP:从英文意思来说,叫“面向切面编程”。在面向对象的编程里,正常情况下,我们代码的执行顺序都是纵向的,即由一个个类中的方法顺序执行。而切面,便是垂直于纵向的一面,即贯穿顺序执行中的每个单元。AOP 便是让我们从切面的角度来书写代码,而这些代码大多数都是贯穿于系统中很多类和方法中,比如日志记录、异常处理、权限控制等。AOP 和 IOC 类似,它也是设计层的东西,并不是一个实际的组件或框架。

扩展 MEF,使其支持 AOP

其实 IOC 和 AOP 真的是很好的朋友,以至于很多 IOC 的框架里原生就支持 AOP,因为 IOC 在运行时注入具体实现的时候,便是创建代理对象的最佳时机。当然,在 MEF 里,获取导出(Export)时,便是创建代理对象的契机。这里反复提及的代理对象,是目前 .NET 中实现 AOP 的一种手段。据我所知,在 .NET 中目前大体有以下两种 AOP 的实现方式:

动态代理:在运行时,动态的创建某个类或对象的代理,在代理中重写目标类(被创建成代理的类)的虚方法(可重写的方法),从而在调用代理对象的方法时,执行特定的切面代码。目前圈子里大多数实现都是使用 Emit 来动态构建一个代理类,也有使用 Dynamic 来构建的(注:这种方式并不是重写虚方法,有兴趣的同学可以查看NiceWk同学的文章)。动态代理的好处是实现起来简单,缺点便是要拦截(插入切面代码)的方法或属性必须申明为可重写的,如果你可以容忍,那么你可以选择这种方式。

使用动态代理构建的AOP框架:Castle Dynamic ProxyUnityLOOM.NET

IL 编织:这种实现方式并不是在运行时,而是在程序集编译时或生成完毕后,在目标方法中,织入切面代码对应的 IL。目前这种实现方式,大多数是对编译器的扩展,或者是在生成完毕后加入后续的一个处理过程。IL 编织可以将 AOP 运行时的性能损耗降到最小,并且目标类中不需要定义虚方法,缺点是实现起来比较复杂,调试起来也不方便。

使用IL编织的AOP框架:Postsharp(收费),EosLinFu.AOP

如果是使用 IL 编织的 AOP 框架,那么对于 MEF 而言,是不需要做任何扩展操作即可使用的,原因很简单,MEF 最后发现的导出,必定是已经完成 IL 编织的类的实例。而动态代理不同,动态代理必须要有一个创建代理的过程。这里所说的扩展 MEF,便是让 MEF 将这个过程自动化的去执行,以便我们获取到的导出便已是我们想要的代理。

对于代理,它有两种方式,一种是类的代理,一种是对象的代理,它和适配器模式、装饰者模式很相识(_可笑的是最近一次面试中,技术官问我适配器模式中“类适配”和“对象适配”的区别时,我尽然没答上来,果然技术不用则退,脑袋也也一样,各位同学一起铭记啊_)。所谓类代理,便是在创建时,直接创建的便是目标类的代理类,代理类中不会包含目标类的实例;对象代理,便是使用了装饰者模式,代理类中包含目标类的实例,代理类只是做了一层装饰。以下是这两种代理的简要代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 目标类
public class TargetClass
{
public virtual void Method() { }
}

// 类代理
public class ClassProxy : TargetClass
{
public override void Method()
{
// ...
base.Method();
// ...
}
}

// 对象代理
public class ObjectProxy : TargetClass
{
private TargetClass _targetObj;
public ObjectProxy(TargetClass targetObj)
{
_targetObj = targetObj;
}

public override void Method()
{
// ...
_targetObj.Method();
// ...
}
}

创建上面代理的代码如下,各位同学应该很容易看出它们的区别:

1
2
3
4
var clsProxy = new ClassProxy();

var targetObj = new TargetClass();
var objProxy = new ObjectProxy(targetObj);

理解这两种代理的不同是很重要的,这关系到我们选择何种 AOP 实现方式,对于 MEF 的扩展,我还是推荐使用对象代理的方式,原因也很简单,因为我们可以直接在 MEF 获取导出后,对导出进行一个处理,这样实现起来比较简单,如果使用类代理的话,我们需要深入到 MEF 对象创建,这可能比较麻烦。如何对导出进行一个后处理呢?这相当简单,我们只要继承CompositionContainer,重写它的GetExportsCore,现实我们自己的 Container 即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class AOPCompositionContainer : CompositionContainer
{
#region ctor
public AOPCompositionContainer()
: base() { }
public AOPCompositionContainer(params ExportProvider[] providers)
: base(providers) { }

public AOPCompositionContainer(ComposablePartCatalog catalog, params ExportProvider[] providers)
: base(catalog, providers) { }

public AOPCompositionContainer(ComposablePartCatalog catalog, bool isThreadSafe, params ExportProvider[] providers)
: base(catalog, isThreadSafe, providers) { }

#endregion

protected override IEnumerable<Export> GetExportsCore(ImportDefinition definition, AtomicComposition atomicComposition)
{
var exports = base.GetExportsCore(definition, atomicComposition);
return exports.Select(GetAopExportCore);
}

protected virtual Export GetAopExportCore(Export export)
{
if (!export.Metadata.ContainsKey("AOPEnabled"))
return export;

var aspectsEnabled = export.Metadata["AOPEnabled"];

if ((bool)aspectsEnabled == false)
return export;

return new Export(export.Definition, () => Aop.ProxyGenerator.CreateProxy(this, export.Value));
}
}

代码相当的简单,我们在获取到导出后,对导出逐个的进行了一个后处理,我们判定导出的元数据中是否包含AOPEnabled,如果有,且该值为true,则返回使用ProxyGenerator创建的代理构建的导出,否则直接返回原始获取到的导出。这里使用到了导出的元数据,不清楚的同学,可以看先前的博文记录,如此一来,如果我们想导出自动为代理的话,就必须在导出时指定AOPEnabled元数据,并且要设置为true。还有其它的实现方式么?有!我们可以拿 exportValue来做文章,通过exportValue,我们可以反射获取到导出对象的类型,通过类型我们就可以反射获取到是否有做什么特殊的Attribute标记,可能说得不是很清晰,那么下面这段代码是另外一种选择:

1
2
3
4
5
6
7
protected virtual Export GetAopExportCore(Export export)
{
var aopEnabledAttr = export.Value.GetType().GetCustomAttributes(typeof(AopEnabledAttribute), true);
if (aopEnabledAttr == null || aopEnabledAttr.Length == 0) return export;

return new Export(export.Definition, () => Aop.ProxyGenerator.CreateProxy(this, export.Value));
}

相比这两种实现,我推荐使用元数据实现的方式,原因很简单,因为少去了一次反射消耗,毕竟在当前代码块里,元数据是已经完成解析了的,不用也是浪费。为了方便后续的使用,我们自定义两种带元数据导出的ExportAttribute

AOPExportAttribute

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/// <summary>
/// 支持 AOP 的导出
/// </summary>
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class AOPExportAttribute : ExportAttribute
{
#region ctor

public AOPExportAttribute() : base() { this.AOPEnabled = true; }

public AOPExportAttribute(string contractName) : base(contractName) { this.AOPEnabled = true; }
public AOPExportAttribute(Type contractType) : base(contractType) { this.AOPEnabled = true; }
public AOPExportAttribute(string contractName, Type contractType) : base(contractName, contractType) { this.AOPEnabled = true; }

#endregion


/// <summary>
/// 是否启用 AOP
/// </summary>
[DefaultValue(true)]
public bool AOPEnabled { get; set; }

}

InheritedAOPExportAttribute

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/// <summary>
/// 支持 AOP 的导出,且子类也继承该设定
/// </summary>
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = false, Inherited = true)]
public class InheritedAOPExportAttribute : InheritedExportAttribute
{
#region ctor

public InheritedAOPExportAttribute() : base() { this.AOPEnabled = true; }
public InheritedAOPExportAttribute(string contractName) : base(contractName) { this.AOPEnabled = true; }
public InheritedAOPExportAttribute(Type contractType) : base(contractType) { this.AOPEnabled = true; }
public InheritedAOPExportAttribute(string contractName, Type contractType) : base(contractName, contractType) { this.AOPEnabled = true; }

#endregion

/// <summary>
/// 是否启用 AOP
/// </summary>
[DefaultValue(true)]
public bool AOPEnabled { get; set; }
}

这两段实现都很简单,只是作为ExportAttributeInheritedExportAttribute 的AOP替代版本。

简单的 AOP 框架实现

在上面我们扩展 MEF 时,有使用到ProxyGenerator来创建代理,这便是我们 AOP 最核心的部分,你完全可以使用其它第三方AOP框架来实现这个ProxyGenerator,但这里我选择自己通过 Emit 来实现一个简单的 AOP 框架。主要是为了学习,也不想引入太多太复杂的框架,简单实用才是最好的,实现上有借鉴 Castle ,当然,我不可能比它做得更好。

首先我们仿照Caslte,定义如下一个拦截器的公共接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/// <summary>
/// 拦截器
/// </summary>
public interface IInterceptor
{
/// <summary>
/// 执行拦截方法
/// </summary>
/// <param name="invocation">拦截的一些上下文信息</param>
void Intercept(IInvocation invocation);

/// <summary>
/// 拦截器的执行顺序
/// </summary>
int Order { get; }

}

即,我们所有的切面代码,都是在Intercept这里执行, Intercept会提供执行时的一些上下文信息,即IInvocation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/// <summary>
/// 拦截时的一些信息
/// </summary>
public interface IInvocation
{
/// <summary>
/// 获取拦截方法的参数列表
/// </summary>
object[] Arguments { get; }

/// <summary>
/// 获取拦截方法的泛型参数
/// </summary>
Type[] GenericArguments { get; }

/// <summary>
/// 获取拦截方法的信息
/// </summary>
MethodInfo Method { get; }

/// <summary>
/// 获取代理对象
/// </summary>
object Proxy { get; }

/// <summary>
/// 被创建成代理的对象,即原始对象
/// </summary>
object Target { get; }

/// <summary>
/// 获取或设定返回值
/// </summary>
object ReturnValue { get; set; }

/// <summary>
/// 获取目标类型,即被拦截的类型(非代理类型)
/// </summary>
Type TargetType { get; }

/// <summary>
/// 获取指定所以的参数值
/// </summary>
/// <param name="index">参数索引</param>
/// <returns>返回指定的参数值,或者抛出异常</returns>
/// <exception cref="IndexOutOfRangeException">IndexOutOfRangeException</exception>
object GetArgumentValue(int index);

/// <summary>
/// 设定指定索引的参数值
/// </summary>
/// <param name="index">参数索引</param>
/// <param name="value">要设定的值</param>
/// <exception cref="IndexOutOfRangeException">IndexOutOfRangeException</exception>
void SetArgumentValue(int index, object value);

/// <summary>
/// 执行下一个拦截器,如果没有拦截器了,则执行被拦截的方法
/// </summary>
void Proceed();
}

具体方法的作用,熟悉 Castle 的应该都很清楚,我这里做了些许简化,即便你不熟悉,我的注释也写得很清晰。需要特别注意的是 Proceed 这个方法,如果在某一个拦截器中未执行该方法,则目标方法就不会得到调用,如果该目标方法有返回值,而目前的 ReturnValue 为 null的话,则会抛出异常。

有了这两个核心接口定以后,我们就应该考虑代理是如何来实现了,在我们使用 Emit 来构建代理的时候,我推荐的做法是先用 C# 代码手动写出这样一个代理,然后通过反编译工具(ILDasm、ILSpy 等)反编译成 MSIL 后,对照着用 Emit 来实现。那么我们先手动写一个代理:

目标类(Target Class)

1
2
3
4
5
6
class Target
{
public virtual void Method(string a, bool b)
{
}
}

代理类(Proxy Class)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Proxy : Target
{
private Target _target;
private IInterceptor[] _interceptors;

public Proxy(Target target, IInterceptor[] interceptors)
{
_target = target;
_interceptors = interceptors;
}

public override void Method(string a, bool b)
{
object[] arguments = new object[] { a, b };
Type[] argumentTypes = new Type[] { a.GetType(), b.GetType() };
MethodInfo method = ReflectionHelper.GetMethod(_target.GetType(), "Method", argumentTypes, false);

var invocation = new Invocation(_target, this, method, arguments, _interceptors);

invocation.Proceed();
}
}

代理类大体就如上面,可以看出使用的是对象代理,但在实际的 Emit 中也有些不同,我们还需要注意返回值和泛型等问题。值得一提的是,我们为了方便 Emit,所以故意将很多代码写得很朴实,这样在反编译参照时才有价值。在这里,我们将方法的最终调用都放给InvocationProceed去处理了(如果放在代理里面,Emit 起来太复杂),Invocation的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
public class Invocation : IInvocation
{
private object _target;
private object _proxy;
private MethodInfo _method;

private List<object> _arguments;

private Queue<Action<IInvocation>> _invokeQuery;

public Invocation(object target, object proxy, MethodInfo method, object[] arguments, IInterceptor[] interceptors)
{
_target = target;
_proxy = proxy;
_method = method;

_arguments = new List<object>(arguments);


_invokeQuery = new Queue<Action<IInvocation>>();
foreach (var item in interceptors)
_invokeQuery.Enqueue(x => item.Intercept(x));

_invokeQuery.Enqueue(x => x.ReturnValue = Method.Invoke(Target, Arguments));

}

public object[] Arguments
{
get { return _arguments.ToArray(); }
}

public Type[] GenericArguments
{
get { return _method.GetGenericArguments(); }
}

public MethodInfo Method
{
get { return _method; }
}

public object Proxy
{
get { return _proxy; }
}

public object Target
{
get { return _target; }
}

public object ReturnValue { get; set; }

public Type TargetType
{
get { return _target.GetType(); }
}

public object GetArgumentValue(int index)
{
return _arguments[index];
}

public void SetArgumentValue(int index, object value)
{
_arguments[index] = value;
}


public void Proceed()
{
if (_invokeQuery.Count > 0)
_invokeQuery.Dequeue().Invoke(this);
}


}

我们将所有IInterceptor中的Intercept方法包装成Action<IInvocation>按顺序存放到一个队列里面了,并将目标方法的调用放到了队列最后。在Proceed方法里,我们出队,并对方法进行了调用,所以,只要有一个拦截器未对Proceed进行调用,那么我们就无法执行到目标方法。针对这样的一个设计,我们的代理类就变得比较简单,这也方便我们使用 Emit 来构建,毕竟用 Emit 来构建复杂逻辑还是很繁琐的。

那么现在就来揭开ProxyGenerator这个静态类的神秘面纱吧,首先是CreateProxy方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/// <summary>
/// 创建对象的代理
/// </summary>
/// <param name="obj">要创建代理的对象</param>
/// <returns>创建完成的代理</returns>
public static object CreateProxy(AOPCompositionContainer container, object obj)
{
var attrs = obj.GetType().GetCustomAttributes(typeof(IInterceptor), true);

if (attrs == null || attrs.Length == 0) return obj;

var interceptors = attrs.Select(x => { container.ComposeParts(x); return (IInterceptor)x; })
.OrderBy(x => x.Order)
.ToArray();

return CreateProxy(obj, interceptors);
}

通过代码,我们很容易就看出来,IInterceptor最终是以CustomAttribute的形式标记在需要拦截的类上的,并且我们对获取到的IInterceptor还做了一次ComposeParts,这样一来,我们可以在实现IInterceptor时使用Import来导入依赖。接下来,我们再看该方法的一个内部重载版本,即该方法最后return的那个调用:

1
2
3
4
5
6
7
8
9
private static object CreateProxy(object target, IInterceptor[] interceptors)
{
var targetType = target.GetType();

if (!_proxyTypeCache.ContainsKey(targetType))
_proxyTypeCache[targetType] = GenerateProxyType(targetType);

return Activator.CreateInstance(_proxyTypeCache[targetType], target, interceptors);
}

_proxyTypeCache是一个简单ConcurrentDictionary<Type, Type>用来做代理类型的缓存,这样不必每次都去 Emit 一个代理类型,所以,核心的 Emit 部分都在GenerateProxyType这个方法里了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/// <summary>
/// 根据目标类型,生成代理类型
/// </summary>
/// <param name="targetType"></param>
/// <returns></returns>
private static Type GenerateProxyType(Type targetType)
{
var assemblyName = new AssemblyName("System.ComponentModel.Composition.Aop.Proxies");
#if DEBUG
var assemblyDef = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);
var moduleDef = assemblyDef.DefineDynamicModule(assemblyName.Name, "System.ComponentModel.Composition.Aop.Proxies.dll");
#else

var assemblyDef = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
var moduleDef = assemblyDef.DefineDynamicModule(assemblyName.Name);
#endif

// 继承至 targetType 的一个代理类型
var typeDef = moduleDef.DefineType(targetType.FullName + "__Proxy", TypeAttributes.Public, targetType);

var context = new EmitContext(targetType, typeDef);

EmitProxyTypeCustomeAttributes(context);
EmitProxyTypeFields(context);
EmitProxyTypeConstructor(context);

var targetMethods = targetType.GetMethods();

foreach (var method in targetMethods)
{
if (method.IsFinal) continue;
if (!method.IsVirtual && !method.IsAbstract) continue;
if (method.Name == "ToString") continue;
if (method.Name == "GetHashCode") continue;
if (method.Name == "Equals") continue;

EmitProxyTypeMethod(context, method);
}


var result = typeDef.CreateType();
#if DEBUG
assemblyDef.Save("System.ComponentModel.Composition.Aop.Proxies.dll");
#endif
return result;
}

对于 Emit 或则 MSIL 不熟悉的同学,推荐去看这一篇博文,上面的方法里,我们加了一个预编译指令,这是为了方便调试,在 Debug 模式下会把 Emit 生成的程序集保存到文件,这样可以使用反编译工具查看是否构建的是自己所期望的。上面代码里,在构建代理的时候,主要是这样几个步骤:

  1. 创建代理类型
  2. 设定代理类型的CustomAttribute
  3. 构建代理类型的成员字段
  4. 构建代理类型的构造函数
  5. 构建代理方法

值得一提的是,在 Emit 的过程中,为了方便参数和构建结果的传递,我们还是简单的定义了一个EmitContext类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class EmitContext
{
public EmitContext(Type baseType, TypeBuilder typeBuilder)
{
this.BaseType = baseType;
this.TypeBuilder = typeBuilder;
}

public Type BaseType { get; protected set; }

public TypeBuilder TypeBuilder { get; protected set; }


private readonly Dictionary<string, FieldBuilder> _fields = new Dictionary<string, FieldBuilder>();

public Dictionary<string, FieldBuilder> FieldBuilders { get { return _fields; } }

}

设定代理类型的CustomAttribute

1
2
3
4
5
6
7
8
9
10
11
private static void EmitProxyTypeCustomeAttributes(EmitContext context)
{
var constructorInfo = typeof(System.ComponentModel.Composition.PartNotDiscoverableAttribute)
.GetConstructor(Type.EmptyTypes);

var customAttributeBuilder = new CustomAttributeBuilder(constructorInfo, new object[] { });

// 设置 PartNotDiscoverableAttribute, 使得代理不会被 MEF 找到
context.TypeBuilder.SetCustomAttribute(customAttributeBuilder);

}

构建代理类型的成员字段:

1
2
3
4
5
private static void EmitProxyTypeFields(EmitContext context)
{
context.FieldBuilders["__obj"] = context.TypeBuilder.DefineField("__obj", typeof(object), FieldAttributes.Private);
context.FieldBuilders["__interceptors"] = context.TypeBuilder.DefineField("__interceptors", typeof(IInterceptor[]), FieldAttributes.Private);
}

我们将构建好的字段,存进了EmitContext,因为后面我们赋值和取值时会用到。

构建代理类型的构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private static void EmitProxyTypeConstructor(EmitContext context)
{
var ctorDef = context.TypeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard,
new Type[] { context.BaseType, typeof(IInterceptor[]) });

var il = ctorDef.GetILGenerator();

il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, context.BaseType.GetConstructor(Type.EmptyTypes));
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Stfld, context.FieldBuilders["__obj"]);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Stfld, context.FieldBuilders["__interceptors"]);

il.Emit(OpCodes.Ret);
}

在构造函数里,我们对成员字段进行了赋值操作,即把构造函数的参数,赋值给了相应的成员变量。

构建代理方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
private static readonly Type VoidType = Type.GetType("System.Void");
// Emit 拦截的方法,必须为 virtual 或 abstract
private static void EmitProxyTypeMethod(EmitContext context, MethodInfo method)
{
var parameterInfos = method.GetParameters();
var parameterTypes = parameterInfos.Select(p => p.ParameterType).ToArray();
var parameterLength = parameterTypes.Length;

var hasResult = method.ReturnType != VoidType;

var methodBuilder = context.TypeBuilder.DefineMethod(method.Name,
MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual,
method.ReturnType, parameterTypes);

if (method.IsGenericMethod)
{
// 简单支持泛型,未配置泛型约束
methodBuilder.DefineGenericParameters(method.GetGenericArguments().Select(x => x.Name).ToArray());
}

var il = methodBuilder.GetILGenerator();

il.DeclareLocal(typeof(object[])); // arguments
il.DeclareLocal(typeof(Type[])); // argumentTypes
il.DeclareLocal(typeof(MethodInfo)); // method
il.DeclareLocal(typeof(Invocation)); // invocation

if (method.IsGenericMethod) // 定义泛型参数的实际类型
{
il.DeclareLocal(typeof(Type[])); // genericTypes

var genericArguments = methodBuilder.GetGenericArguments();

il.Emit(OpCodes.Ldc_I4, genericArguments.Length);
il.Emit(OpCodes.Newarr, typeof(Type));
il.Emit(OpCodes.Stloc_S, 4);

for (int i = 0; i < genericArguments.Length; i++)
{
il.Emit(OpCodes.Ldloc_S, 4);
il.Emit(OpCodes.Ldc_I4, i);
il.Emit(OpCodes.Ldtoken, genericArguments[i]);
il.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle", new Type[] { typeof(RuntimeTypeHandle) }));
il.Emit(OpCodes.Stelem_Ref);
}
}


// arguments = new object[parameterLength]
il.Emit(OpCodes.Ldc_I4, parameterLength);
il.Emit(OpCodes.Newarr, typeof(object));
il.Emit(OpCodes.Stloc_0);

//argumentTypes = new Type[parameterLength]
il.Emit(OpCodes.Ldc_I4, parameterLength);
il.Emit(OpCodes.Newarr, typeof(Type));
il.Emit(OpCodes.Stloc_1);

for (int i = 0; i < parameterLength; i++)
{
// arguments[i] = arg[i]
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ldc_I4, i);
il.Emit(OpCodes.Ldarg, i + 1); // arg[0] == this
if (parameterTypes[i].IsValueType || parameterTypes[i].IsGenericParameter)
il.Emit(OpCodes.Box, parameterTypes[i]);
il.Emit(OpCodes.Stelem_Ref);

// argumentTypes[i] = arg[i].GetType()
il.Emit(OpCodes.Ldloc_1);
il.Emit(OpCodes.Ldc_I4, i);
il.Emit(OpCodes.Ldarg, i + 1); // arg[0] == this
if (parameterTypes[i].IsValueType || parameterTypes[i].IsGenericParameter)
il.Emit(OpCodes.Box, parameterTypes[i]);
il.Emit(OpCodes.Callvirt, typeof(object).GetMethod("GetType"));
il.Emit(OpCodes.Stelem_Ref);

}

// method = ReflectionHelper.GetMethod(this.__obj.GetType(), "TestMethod", array2, true);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, context.FieldBuilders["__obj"]);
il.Emit(OpCodes.Callvirt, context.BaseType.GetMethod("GetType"));
il.Emit(OpCodes.Ldstr, method.Name);
il.Emit(OpCodes.Ldloc_1);
if (method.IsGenericMethod)
il.Emit(OpCodes.Ldc_I4_1);
else
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Call, typeof(ReflectionHelper).GetMethod("GetMethod"));

if (method.IsGenericMethod)
{
il.Emit(OpCodes.Ldloc_S, 4);
il.Emit(OpCodes.Callvirt, typeof(MethodInfo).GetMethod("MakeGenericMethod"));
}

il.Emit(OpCodes.Stloc_2);


// invocation = new Invocation(_obj, this, method, arguments, _interceptors)
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, context.FieldBuilders["__obj"]);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldloc_2);
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, context.FieldBuilders["__interceptors"]);
il.Emit(OpCodes.Newobj, typeof(Invocation).GetConstructor(new Type[] { typeof(object), typeof(object), typeof(MethodInfo), typeof(object[]), typeof(IInterceptor[]) }));
il.Emit(OpCodes.Stloc_3);

// invocation.Proceed()
il.Emit(OpCodes.Ldloc_3);
il.Emit(OpCodes.Callvirt, typeof(Invocation).GetMethod("Proceed"));

// 返回值
if (hasResult)
{
il.Emit(OpCodes.Ldloc_3);
il.Emit(OpCodes.Callvirt, typeof(Invocation).GetMethod("get_ReturnValue"));
if (method.ReturnType.IsValueType || method.ReturnType.IsGenericParameter)
il.Emit(OpCodes.Unbox_Any, method.ReturnType);
}

il.Emit(OpCodes.Ret);

}

代理方法的构建是整个 AOP 的核心,也是相对复杂的地方,在GenerateProxyType方法里,我们对目标类的方法进行了一个过滤,找出了符合要求的方法再进行的 Emit 。这里需要注意的有几个地方:

泛型定义:

1
2
3
4
5
if (method.IsGenericMethod)
{
// 简单支持泛型,未配置泛型约束
methodBuilder.DefineGenericParameters(method.GetGenericArguments().Select(x => x.Name).ToArray());
}

获取泛型实际传入类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if (method.IsGenericMethod) // 定义泛型参数的实际类型
{
il.DeclareLocal(typeof(Type[])); // genericTypes

var genericArguments = methodBuilder.GetGenericArguments();

il.Emit(OpCodes.Ldc_I4, genericArguments.Length);
il.Emit(OpCodes.Newarr, typeof(Type));
il.Emit(OpCodes.Stloc_S, 4);

for (int i = 0; i < genericArguments.Length; i++)
{
il.Emit(OpCodes.Ldloc_S, 4);
il.Emit(OpCodes.Ldc_I4, i);
il.Emit(OpCodes.Ldtoken, genericArguments[i]);
il.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle", new Type[] { typeof(RuntimeTypeHandle) }));
il.Emit(OpCodes.Stelem_Ref);
}
}

泛型方法,返回的MethodInfo是经过MakeGenericMethod处理的(这样Invocation才可以正常调用):

1
2
3
4
5
if (method.IsGenericMethod)
{
il.Emit(OpCodes.Ldloc_S, 4);
il.Emit(OpCodes.Callvirt, typeof(MethodInfo).GetMethod("MakeGenericMethod"));
}

返回值处理:

1
2
3
4
5
6
7
if (hasResult)
{
il.Emit(OpCodes.Ldloc_3);
il.Emit(OpCodes.Callvirt, typeof(Invocation).GetMethod("get_ReturnValue"));
if (method.ReturnType.IsValueType || method.ReturnType.IsGenericParameter)
il.Emit(OpCodes.Unbox_Any, method.ReturnType);
}

可以看出针对返回值的处理,值类型和泛型都会有个拆箱的过程。

为了方便后续的使用,我们定义一个基本的拦截器Attribute

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class InterceptorAttribute : Attribute, IInterceptor
{
/// <summary>
/// 拦截器类型
/// </summary>
public Type Type { get; set; }

private IInterceptor _interceptor;

public InterceptorAttribute() { }
public InterceptorAttribute(Type interceptorType)
{
this.Type = interceptorType;
}

public virtual void Intercept(IInvocation invocation)
{
if (this.Type != null)
{
if (_interceptor == null)
_interceptor = Activator.CreateInstance(this.Type) as IInterceptor;

_interceptor.Intercept(invocation);
}
}


/// <summary>
/// 拦截器的执行顺序
/// </summary>
public virtual int Order { get; set; }

}

这样一来,我们可以如下使用这个结合了 MEF 和 AOP 的微型框架了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[Interceptor(typeof(MockInterceptor))]
public class MockTarget
{
public virtual string GetString(int a, bool b, string c)
{
return c;
}

public virtual T Get<T>(T a)
{
return a;
}

}


public class MockInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
if (invocation.Method.Name == "GetString")
invocation.ReturnValue = "Intercepted";
else
invocation.Proceed();

}

public int Order
{
get { return 0; }
}
}

但我还是推荐你继承InterceptorAttribute,重写Intercept方法来使用,因为这样我们可以使用Import特性,而上诉代码中的MockInterceptor里是无法使用的。具体的使用,我们看接下来的一节。

综合 AOP 示例分析

上面的文字里,我们讲了那么一大堆,又花费了些气力构建出了这样一个微型框架,框架并不强大,比如对泛型的支持并不完善,性能也不是最优,可我们应该要有的就是探索和不怕折腾的精神。在我们这个圈子里,没有所谓的天才,只有永远的苦才,越能吃苦的,得到和学会的才会越多。接下来,我便用这个框架,来实现一个非常简单的示例,示例项目的结构如下图:

示例项目的结构

这是一个控制台应用程序,Aspects 中存放的是所有切面代码,Models 存放的是模型, Repositories 存放的是仓储,Services 则是服务的存放路径。

AppContext是一个全局的上下文信息,用来存放和应用程序生命周期相同的信息,这里存放的是用户名和用户所属角色:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/// <summary>
/// 当前整个应用的上下文
/// </summary>
sealed class AppContext
{
private static readonly AppContext _current = new AppContext();
public static AppContext Current { get { return _current; } }

/// <summary>
/// 执行该应用的用户,登录用户
/// </summary>
public string User { get; set; }

/// <summary>
/// 角色
/// </summary>
public string Role { get; set; }

}

ServiceLocator则是使用我们自定义的Container实现的一个服务定位器,用来获取服务的具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 一个简单的 ServiceLocator
static class ServiceLocator
{
private static readonly AOPCompositionContainer _container;
static ServiceLocator()
{
var catalog = new AssemblyCatalog(typeof(ServiceLocator).Assembly);
_container = new AOPCompositionContainer(catalog);
}

public static TService GetService<TService>()
{
return _container.GetExportedValue<TService>();
}

public static IEnumerable<TService> GetServices<TService>()
{
return _container.GetExportedValues<TService>();
}
}

可以看出,我们只在当前的程序集里发现导出,这也是因为只是作为示例的原因。接下来,我们定义系统中可以使用的服务:

ILogService

1
2
3
4
public interface ILogService
{
void Log(string log);
}

IAccountService

1
2
3
4
5
6
7
public interface IAccountService
{
bool CreateUser(string username, string password);

bool ExistsUser(string username);

}

这些都是简单到不需要任何解释的东西,我就一笔带过了。

然后是定义模型:

1
2
3
4
5
6
7
8
public class User
{
public Guid Id { get; set; }

public string Name { get; set; }

public string Password { get; set; }
}

仓储IUserRepository

1
2
3
4
5
6
public interface IUserRepository
{
bool Add(string username, string password);

bool Exists(Func<User, bool> predicate);
}

再来看一下目前我们项目的整体结构:

示例项目的结构

现在我们先实现 Service :

LogServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[Export(typeof(ILogService))]
public class LogServiceImpl : ILogService
{
public void Log(string log)
{
var currentColor = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Green;

Console.WriteLine("Log:");
Console.WriteLine(log);
Console.WriteLine();

Console.ForegroundColor = currentColor;
}
}

只是在控制台输入信息。

AccountServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[AOPExport(typeof(IAccountService))]
public class AccountServiceImpl : IAccountService
{
[Import]
protected IUserRepository UserRepository { get; set; }


public virtual bool CreateUser(string username, string password)
{
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
throw new Exception("用户名或密码不可为空!");

if (ExistsUser(username))
throw new Exception("用户名已存在!");

return UserRepository.Add(username, password);
}

public virtual bool ExistsUser(string username)
{
if (string.IsNullOrWhiteSpace(username))
throw new Exception("要检测的用户名不可为空!");

return UserRepository.Exists(x => x.Name == username);
}
}

注意:这里我们使用的是AOPExport,方法定义为virtual,并且导入了IUserRepository,在具体的方法里直接抛出了异常。

我们再实现仓储:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[AOPExport(typeof(IUserRepository))]
public class UserRepositoryImpl : IUserRepository
{

public virtual bool Add(string username, string password)
{
// ...
return true;
}

public virtual bool Exists(Func<Models.User, bool> predicate)
{
var user = new Models.User()
{
Id = Guid.NewGuid(),
Name = "Sun.M",
Password = "123789"
};

return predicate.Invoke(user);
}
}

同样,我们使用了AOPExport,方法也是定义成了virtual,这些都是为了后面我们能够插入切面代码。现在我们的主要代码都已经写得差不多了,回顾下这些代码,其实很简单,我们定义了两个 Service,并在其中的一个 Service 中导入了一个 Repository ,依赖关系就是这么简单。那么我们来看看使用部分代码的定义吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Program
{
static void Main(string[] args)
{
AppContext.Current.User = "Sun.M";
AppContext.Current.Role = "Admin";


var accoutService = ServiceLocator.GetService<Services.IAccountService>();

var createResult = accoutService.CreateUser("Sun.M", "1343434");
Console.WriteLine(createResult); // throw exception : 名称已存在

AppContext.Current.Role = "Other";
createResult = accoutService.CreateUser("M.K", "1343434");
Console.WriteLine(createResult); // throw exception : 没有权限

AppContext.Current.Role = "Admin";
createResult = accoutService.CreateUser("M.K", "1343434");
Console.WriteLine(createResult); // 添加成功


Console.ReadKey();

}
}

现在运行程序,会收到异常,并且程序无法继续执行。下面,我们就来使用 AOP 来处理几个非常常见的问题:异常、日志和权限。

异常处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
public class ExceptionInterceptorAttribute : InterceptorAttribute
{
public override void Intercept(IInvocation invocation)
{
try
{
invocation.Proceed();
}
catch (System.Exception ex)
{
var currentColor = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Red;

Console.WriteLine("Error:");
Console.WriteLine(ex.InnerException.Message);
Console.WriteLine();

Console.ForegroundColor = currentColor;


if (invocation.Method.ReturnType != Type.GetType("System.Void"))
invocation.ReturnValue = CreateTypeDefaultValue(invocation.Method.ReturnType);
}
}

private object CreateTypeDefaultValue(Type type)
{
if (type.IsValueType)
return Activator.CreateInstance(type);

return null;
}

}

注意这里对于返回值的处理,其实我们可以把这个返回默认值的功能,由Invocation自己去实现,这里就不作修改了,有兴趣的同学自己动手实践下吧。

日志处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
public class LogInterceptorAttribute : InterceptorAttribute
{
[Import]
protected ILogService LogService { get; set; }

public override void Intercept(IInvocation invocation)
{
LogService.Log(string.Format("Method:{0} User:{1}", invocation.Method.Name, AppContext.Current.User));

invocation.Proceed();

if (invocation.ReturnValue != null)
LogService.Log(string.Format("Method:{0} ReturnValue:{1}", invocation.Method.Name, invocation.ReturnValue));

}
}

对于日志的处理,就比较简单了,但这里使用了 Import 特性,这是一个非常实用的功能。这里,我们记录下了方法调用的一些信息。

权限处理:

SecurityInterceptorAttribute

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
public class SecurityInterceptorAttribute : InterceptorAttribute
{
public override void Intercept(IInvocation invocation)
{

var securityAttrs = invocation.Method.GetCustomAttributes(typeof(SecurityAttribute), true);
if (securityAttrs == null || securityAttrs.Length == 0)
{
invocation.Proceed(); return;
}


var requiredRoles = securityAttrs.Select(x => ((SecurityAttribute)x).Role);

if (requiredRoles.Any(x => x == AppContext.Current.Role) == false)
{
throw new System.Exception(string.Format("对{0}的访问没有权限", invocation.Method.Name));
}

invocation.Proceed();

}

}

SecurityAttribute

1
2
3
4
5
6
7
8
9
10
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true)]
public class SecurityAttribute : Attribute
{
public SecurityAttribute(string role)
{
this.Role = role;
}

public string Role { get; private set; }
}

对于权限的处理,我们多定义出了一Attribute,用来标识那些方法是需要权限判定的,并且指定了判定条件,即 Role 。

现在我们定义好了这么些个拦截器(切面代码),具体使用也非常简单,我们在需要的地方标记上去即可:

AccountServiceImpl

1
2
3
4
[LogInterceptor(Order = 0)]
[ExceptionInterceptor(Order = 1)]
[AOPExport(typeof(IAccountService))]
public class AccountServiceImpl : IAccountService

UserRepositoryImpl

1
2
3
4
5
6
7
8
9
[SecurityInterceptor]
[AOPExport(typeof(IUserRepository))]
public class UserRepositoryImpl : IUserRepository
{

[Security("Admin")]
public virtual bool Add(string username, string password)
{
// ...

如此一来,我们的拦截器已经能正常工作了。上述代码中,定义了UserRepositoryImpl中的Add方法只能被 Role 为 Admin 的用户调用。这里需要注意的是拦截器的 Order ,对于异常拦截器而言,最好是越先执行越安全,而权限则最好放在所有拦截器执行完后再执行,具体原因不说大家也都明白。

最后我们看一下执行结果:

执行结果图

时候也不早了,这一篇就到这里吧!附上项目源码地址: https://github.com/prinsun/BlogDemo.MEF.AOP