1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        C#動(dòng)態(tài)方法攔截(AOP)的5種解決方案!

        共 8607字,需瀏覽 18分鐘

         ·

        2021-02-26 21:50


        轉(zhuǎn)自:Artech
        cnblogs.com/artech/archive

        前言


        AOP的本質(zhì)是方法攔截(將針對(duì)目標(biāo)方法調(diào)用劫持下來(lái),進(jìn)而執(zhí)行執(zhí)行的操作),置于方法攔截的實(shí)現(xiàn)方案,不外乎兩種代碼注入類型,即編譯時(shí)的靜態(tài)注入和運(yùn)行時(shí)的動(dòng)態(tài)注入,本篇文章列出了幾種常用的動(dòng)態(tài)注入方案。


        這篇文章的目標(biāo)并不是提供完整的AOP框架的解決方案,而是說(shuō)明各種解決方案后面的原理,所以我們提供的實(shí)例代碼會(huì)盡可能簡(jiǎn)單。


        為了確定攔截操作是否執(zhí)行,我們定義了如下這個(gè)Indicator類型,我們的攔截操作會(huì)將其靜態(tài)屬性Injected屬性設(shè)置為True,我們演示的代碼最終通過(guò)這個(gè)屬性來(lái)確定攔截是否成功。源代碼下載:https://files.cnblogs.com/files/artech/Interception.7z


        public static class Indicator
        {
        public static bool Injected { get; set; }
        }



        一、IL Emit(接口)


        IL Emit是實(shí)現(xiàn)AOP的首選方案。如果方法調(diào)用時(shí)針對(duì)接口完成,我們可以生成一個(gè)代理類型來(lái)封裝對(duì)象,并且這個(gè)代理類型同時(shí)實(shí)現(xiàn)目標(biāo)接口,那么只要我們能夠?qū)⑨槍?duì)目標(biāo)對(duì)象的方法調(diào)用轉(zhuǎn)換成針對(duì)代理對(duì)象的調(diào)用,就能實(shí)現(xiàn)針對(duì)目標(biāo)對(duì)象的方法攔截。


        舉個(gè)簡(jiǎn)單的例子,F(xiàn)oobar實(shí)現(xiàn)了IFoobar接口,如果我們需要攔截接口方法Invoke,我們可以生成一個(gè)FoobarProxy類型。


        如代碼片段所示,F(xiàn)oobarProxy封裝了一個(gè)IFoobar對(duì)象,并實(shí)現(xiàn)了IFoobar接口。在實(shí)現(xiàn)的Invoke方法中,它在調(diào)用封裝對(duì)象的同名方法之前率先執(zhí)行了攔截操作。


        public interface IFoobar
        {
        int Invoke();
        }
        public class Foobar : IFoobar
        {
        public int Invoke() => 1;
        }
        public class FoobarProxy : IFoobar
        {
        private readonly IFoobar _target;
        public FoobarProxy(IFoobar target)=>_target = target
        public int Invoke()
        {
        Indicator.Injected = true;
        return _target.Invoke();
        }
        }



        上述的這個(gè)FoobarProxy類型就可以按照如下的方式利用GenerateProxyClass方法來(lái)生成。在Main方法中,我們創(chuàng)建一個(gè)Foobar對(duì)象,讓據(jù)此創(chuàng)建這個(gè)動(dòng)態(tài)生成的FoobarProxy,當(dāng)該對(duì)象的Invoke方法執(zhí)行的時(shí)候,我們期望的攔截操作自然會(huì)自動(dòng)執(zhí)行。


        class Program
        {
        static void Main(string[] args)
        {
        var foobar = new Foobar();
        var proxy = (IFoobar)Activator.CreateInstance(GenerateProxyClass(), foobar);
        Debug.Assert(Indicator.Injected == false);
        Debug.Assert(proxy.Invoke() == 1);
        Debug.Assert(Indicator.Injected == true);
        }
        static Type GenerateProxyClass()
        {
        var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("Proxy"), AssemblyBuilderAccess.Run);
        var moduleBuilder = assemblyBuilder.DefineDynamicModule("Proxy.dll");
        var typeBuilder = moduleBuilder.DefineType("FoobarProxy", TypeAttributes.Public, null, new Type[] { typeof(IFoobar) });
        var targetField = typeBuilder.DefineField("_target", typeof(IFoobar), FieldAttributes.Private | FieldAttributes.InitOnly);
        var constructor = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new Type[] { typeof(IFoobar) });
        var il = constructor.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldarg_1);
        il.Emit(OpCodes.Stfld, targetField);
        il.Emit(OpCodes.Ret);
        var attributes = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual | MethodAttributes.Final;
        var invokeMethod = typeBuilder.DefineMethod("Invoke", attributes, typeof(int), null);
        il = invokeMethod.GetILGenerator();
        il.Emit(OpCodes.Ldc_I4_1);
        il.Emit(OpCodes.Call, typeof(Indicator).GetProperty("Injected").SetMethod);
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldfld, targetField);
        il.Emit(OpCodes.Callvirt, typeof(IFoobar).GetMethod("Invoke"));
        il.Emit(OpCodes.Ret);
        return typeBuilder.CreateType();
        }
        }



        二、IL Emit(虛方法)


        如果待攔截的并非接口方法,而是一個(gè)虛方法,我們可以利用IL Emit的方式動(dòng)態(tài)生成一個(gè)派生類,并重寫這個(gè)虛方法的方式來(lái)完成攔截。以下面的代碼片段為例,我們需要攔截定義在Foobar中的虛方法Invoke,我們可以生成如下這個(gè)派生與Foobar的Foobar的FoobarProxy類型,在重寫的Invoke方法中,我們?cè)谡{(diào)用基類同名方法之前,率先執(zhí)行攔截操作。


        public class Foobar 
        {
        public virtual int Invoke() => 1;
        }
        public class FoobarProxy : Foobar {
        public override int Invoke()
        {
        Indicator.Injected = true;
        return base.Invoke();
        }
        }



        上面這個(gè)FoobarProxy類型就可以通過(guò)如下這個(gè)GenerateProxyClass生成出來(lái)。


        class Program
        {
        static void Main(string[] args)
        {
        var proxy = (Foobar)Activator.CreateInstance(GenerateProxyClass());
        Debug.Assert(Indicator.Injected == false);
        Debug.Assert(proxy.Invoke() == 1);
        Debug.Assert(Indicator.Injected == true);
        }
        static Type GenerateProxyClass()
        {
        var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("Proxy"), AssemblyBuilderAccess.Run);
        var moduleBuilder = assemblyBuilder.DefineDynamicModule("Proxy.dll");
        var typeBuilder = moduleBuilder.DefineType("FoobarProxy", TypeAttributes.Public, typeof(Foobar));
        var attributes = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.Final;
        var invokeMethod = typeBuilder.DefineMethod("Invoke", attributes, typeof(int), null);
        var il = invokeMethod.GetILGenerator();
        il.Emit(OpCodes.Ldc_I4_1);
        il.Emit(OpCodes.Call, typeof(Indicator).GetProperty("Injected").SetMethod);
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Call, typeof(Foobar).GetMethod("Invoke"));
        il.Emit(OpCodes.Ret);
        return typeBuilder.CreateType();
        }
        }



        三、方法替換(跳轉(zhuǎn))


        上面兩種方案都具有一個(gè)局限性:需要將針對(duì)目標(biāo)對(duì)象的方法調(diào)用轉(zhuǎn)換成針對(duì)代理對(duì)象的調(diào)用。如果我們能夠直接將目標(biāo)方法替換成另一個(gè)包含攔截操作的方案(或者說(shuō)從原來(lái)的方法調(diào)轉(zhuǎn)到具有攔截操作的方法),那么即使我們不改變方法的調(diào)用方式,方法依舊能夠攔截。Harmony框架就是采用這樣的方案實(shí)現(xiàn)的,我們可以通過(guò)下面這個(gè)簡(jiǎn)單的實(shí)例來(lái)模擬其實(shí)現(xiàn)原理(下面演示的程序引用了HarmonyLib包)。


        class Program
        {
        static void Main(string[] args)
        { HarmonyLib.Memory.DetourMethod(typeof(Foobar).GetMethod("Invoke"), GenerateNewMethod());
        Debug.Assert(Indicator.Injected == false);
        Debug.Assert(new Foobar().Invoke() == 1);
        Debug.Assert(Indicator.Injected == true);
        }
        static MethodBase GenerateNewMethod()
        {
        var dynamicMethod = new DynamicMethodDefinition(typeof(Foobar).GetMethod("Invoke"));
        var il = dynamicMethod.GetILProcessor();
        var ldTrue = il.Create(OpCodes.Ldc_I4_1);
        var setIndicator = il.Create(OpCodes.Call, dynamicMethod.Module.ImportReference(typeof(Indicator).GetProperty("Injected").SetMethod));il.InsertBefore(dynamicMethod.Definition.Body.Instructions.First(), setIndicator);
        il.InsertBefore(setIndicator, ldTrue);
        return dynamicMethod.Generate();
        }
        }
        public class Foobar
        {
        public virtual int Invoke() => 1;
        }



        如上面的代碼片段所示,為了攔截Foobar的Invoke方法,我們?cè)贕enerateNewMethod方法中根據(jù)這個(gè)方法創(chuàng)建了一個(gè)DynamicMethodDefinition對(duì)象(定義在MonoMod.Common包中),并在方法體的前面添加了兩個(gè)IL指令將Indicator的Injected屬性設(shè)置為True,該方法最終返回通過(guò)這個(gè)DynamicMethodDefinition對(duì)象生成的MethodBase對(duì)象。


        在Main方法中,我們利用HarmonyLib.Memory的靜態(tài)方法DetourMethod將原始的Invoke方法“轉(zhuǎn)移”到生成的方法上。即使我們調(diào)用的依然是Foobar對(duì)象的Invoke方法,但是攔截操作依然會(huì)被執(zhí)行。


        四、RealProxy/TransparentProxy


        RealProxy/TransparentProxy是.NET Framework時(shí)代一種常用的方法攔截方案。如果目標(biāo)類型實(shí)現(xiàn)了某個(gè)接口或者派生于MarshalByRefObject類型,我們就可以采用這種攔截方案。


        如果需要攔截某個(gè)類型的方法,我們可以定義如下這么一個(gè)FoobarProxy 類型,泛型參數(shù)T代表目標(biāo)類型或者接口。


        和第一種方案一樣,我們的代理對(duì)象依舊是封裝目標(biāo)對(duì)象,在實(shí)現(xiàn)的Invoke方案中,我們利用作為參數(shù)的IMessage 方法得到代表目標(biāo)方法的MethodBase對(duì)象,進(jìn)而利用它實(shí)現(xiàn)針對(duì)目標(biāo)方法的調(diào)用。在目標(biāo)方法調(diào)用之前,我們可以執(zhí)行攔截操作。


        public interface IFoobar
        {
        int Invoke();
        }
        public class Foobar : IFoobar
        {
        public int Invoke() => 1;
        }
        public class FoobarProxy : RealProxy
        {
        public T _target;
        public FoobarProxy(T target) :base(typeof(T))
        => _target = target;
        public override IMessage Invoke(IMessage msg)
        {
        Indicator.Injected = true;
        IMethodCallMessage methodCall = (IMethodCallMessage)msg;
        IMethodReturnMessage methodReturn = null;
        object[] copiedArgs = Array.CreateInstance(typeof(object), methodCall.Args.Length) as object[];
        methodCall.Args.CopyTo(copiedArgs, 0);
        try
        {
        object returnValue = methodCall.MethodBase.Invoke(_target, copiedArgs);
        methodReturn = new ReturnMessage(returnValue, copiedArgs, copiedArgs.Length, methodCall.LogicalCallContext, methodCall);
        }
        catch (Exception ex)
        {
        methodReturn = new ReturnMessage(ex, methodCall);
        }
        return methodReturn;
        }
        }



        在Main方法中,我們創(chuàng)建目標(biāo)Foobar對(duì)象,然后將其封裝成一個(gè)FoobarProxy 對(duì)象。我們最終調(diào)用GetTransparentProxy方法創(chuàng)建出透明代理,并將其轉(zhuǎn)換成IFoobar類型。


        當(dāng)我們調(diào)用這個(gè)透明對(duì)象的任何一個(gè)方法的時(shí)候,定義在FoobarProxy 中的Invoke方法均會(huì)執(zhí)行。


        class Program
        {
        static void Main(string[] args)
        {
        var proxy = (IFoobar)(new FoobarProxy ( new Foobar()).GetTransparentProxy());
        Debug.Assert(Indicator.Injected == false);
        Debug.Assert(proxy.Invoke() == 1);
        Debug.Assert(Indicator.Injected == true);
        }
        }



        五、DispatchProxy


        RealProxy/TransparentProxy僅限于.NET Framework項(xiàng)目中實(shí)現(xiàn),在.NET Core中它具有一個(gè)替代類型,那就是DispatchProxy。


        我們可以采用如下的方式利用DispatchProxy實(shí)現(xiàn)我們所需的攔截功能。


        class Program
        {
        static void Main(string[] args)
        {
        var proxy = DispatchProxy.Create >();
        ((FoobarProxy )proxy).Target = new Foobar();
        Debug.Assert(Indicator.Injected == false);
        Debug.Assert(proxy.Invoke() == 1);
        Debug.Assert(Indicator.Injected == true);
        }
        }
        public interface IFoobar
        {
        int Invoke();
        }
        public class Foobar : IFoobar
        {
        public int Invoke() => 1;
        }
        public class FoobarProxy : DispatchProxy
        {
        public T Target { get; set; }
        protected override object Invoke(MethodInfo targetMethod, object[] args)
        {
        Indicator.Injected = true;
        return targetMethod.Invoke(Target, args);
        }
        }


        - EOF -







        回復(fù) 【關(guān)閉】學(xué)關(guān)
        回復(fù) 【實(shí)戰(zhàn)】獲取20套實(shí)戰(zhàn)源碼
        回復(fù) 【被刪】學(xué)個(gè)
        回復(fù) 【訪客】學(xué)
        回復(fù) 【小程序】學(xué)獲取15套【入門+實(shí)戰(zhàn)+賺錢】小程序源碼
        回復(fù) 【python】學(xué)微獲取全套0基礎(chǔ)Python知識(shí)手冊(cè)
        回復(fù) 【2019】獲取2019 .NET 開發(fā)者峰會(huì)資料PPT
        回復(fù) 【加群】加入dotnet微信交流群

        終于可以DIY個(gè)人微信紅包封面了!還免費(fèi)!


        臥槽:微信可以這樣換個(gè)字體了!


        點(diǎn)贊和在看就是最大的支持??

        瀏覽 176
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            国产精品久久久久久久久毛片 | 欧美精品不卡免费在线 | 性xxxxfreexxxxx高跟鞋 | 婷婷五情天综合免费 | ppypp电影频道 | 日韩高清无码内射 | 北条麻妃亚洲无码 | 操逼视频黄色 | 口述与子做过爱过程性 | 久久久国产精品黄毛片 |