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)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM