zoukankan      html  css  js  c++  java
  • 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 将这个过程自动化的去执行,以便我们获取到的导出便已是我们想要的代理。

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

     // 目标类
        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();
                // ...
            }
        }

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

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

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

        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。还有其它的实现方式么?有!我们可以拿 export 的 Value 来做文章,通过 export 的 Value,我们可以反射获取到导出对象的类型,通过类型我们就可以反射获取到是否有做什么特殊的 Attribute 标记,可能说得不是很清晰,那么下面这段代码是另外一种选择:

            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:

        /// <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:

        /// <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; }
        }

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

    简单的 AOP 框架实现

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

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

        /// <summary>
        /// 拦截器
        /// </summary>
        public interface IInterceptor
        {
            /// <summary>
            /// 执行拦截方法
            /// </summary>
            /// <param name="invocation">拦截的一些上下文信息</param>
            void Intercept(IInvocation invocation);
    
            /// <summary>
            /// 拦截器的执行顺序
            /// </summary>
            int Order { get; }
    
        }

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

        /// <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):

        class Target
        {
            public virtual void Method(string a, bool b)
            {
            }
        }

    代理类(Proxy Class):

        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,所以故意将很多代码写得很朴实,这样在反编译参照时才有价值。在这里,我们将方法的最终调用都放给 Invocation 的 Proceed 去处理了(如果放在代理里面,Emit 起来太复杂),Invocation 的实现如下:

        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 方法:

            /// <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 的那个调用:

            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 这个方法里了:

            /// <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 类:

        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:

            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);
    
            }

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

            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 ,因为后面我们赋值和取值时会用到。

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

            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);
            }

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

    构建代理方法:

            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 。这里需要注意的有几个地方:

    泛型定义:

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

    获取泛型实际传入类型:

                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 才可以正常调用):

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

    返回值处理:

                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 :

        [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 的微型框架了:

        [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 示例分析

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

    image

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

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

        /// <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 实现的一个服务定位器,用来获取服务的具体实现:

        // 一个简单的 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:

        public interface ILogService
        {
            void Log(string log);
        }

    IAccountService:

        public interface IAccountService
        {
            bool CreateUser(string username, string password);
    
            bool ExistsUser(string username);
    
        }

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

    然后是定义模型:

        public class User
        {
            public Guid Id { get; set; }
    
            public string Name { get; set; }
    
            public string Password { get; set; }
        }

    仓储 IUserRepository :

        public interface IUserRepository
        {
            bool Add(string username, string password);
    
            bool Exists(Func<User, bool> predicate);
        }

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

    image

    现在我们先实现 Service :

    LogServiceImpl:

        [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:

        [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,在具体的方法里直接抛出了异常。

    我们再实现仓储:

        [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 ,依赖关系就是这么简单。那么我们来看看使用部分代码的定义吧:

        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 来处理几个非常常见的问题:异常、日志和权限。

    异常处理:

        [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 自己去实现,这里就不作修改了,有兴趣的同学自己动手实践下吧。

    日志处理:

        [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:

        [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:

        [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:

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

    UserRepositoryImpl:

        [SecurityInterceptor]
        [AOPExport(typeof(IUserRepository))]
        public class UserRepositoryImpl : IUserRepository
        {
    
            [Security("Admin")]
            public virtual bool Add(string username, string password)
            {
                // ...

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

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

    image

    时候也不早了,这一篇就到这里吧!附上项目源码下载地址:请点击下载(访问密码:4728)

    作者:Sun.M
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
  • 相关阅读:
    [转]C#、VB.NET使用HttpWebRequest访问https地址(SSL)的实现
    C#设置System.Net.ServicePointManager.DefaultConnectionLimit,突破Http协议的并发连接数限制
    [转]WebBrowser控件禁用超链接转向、脚本错误提示、默认右键菜单和快捷键
    [转]C#打印DataGridView的例子源码
    c# TreeView 父节点选中/不选时子节点都同步选中/不选
    C#中PictureBox异步加载图片
    [转]FusionCharts 3.1 破解版 – 非常好用的Flash图表控件
    配合JavaScript拖动页面中控件
    在ThinkPad T400上安装win2003 所遇问题
    C# 抛弃MoveTo来实现文件重命名
  • 原文地址:https://www.cnblogs.com/prinsun/p/let_mef_hugs_aop.html
Copyright © 2011-2022 走看看