<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 8607字,需瀏覽 18分鐘

           ·

          2021-02-26 21:50


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

          前言


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


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


          為了確定攔截操作是否執(zhí)行,我們定義了如下這個Indicator類型,我們的攔截操作會將其靜態(tài)屬性Injected屬性設(shè)置為True,我們演示的代碼最終通過這個屬性來確定攔截是否成功。源代碼下載: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í)現(xiàn)目標(biāo)接口,那么只要我們能夠?qū)⑨槍δ繕?biāo)對象的方法調(diào)用轉(zhuǎn)換成針對代理對象的調(diào)用,就能實(shí)現(xiàn)針對目標(biāo)對象的方法攔截。


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


          如代碼片段所示,F(xiàn)oobarProxy封裝了一個IFoobar對象,并實(shí)現(xiàn)了IFoobar接口。在實(shí)現(xiàn)的Invoke方法中,它在調(diào)用封裝對象的同名方法之前率先執(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();
          }
          }



          上述的這個FoobarProxy類型就可以按照如下的方式利用GenerateProxyClass方法來生成。在Main方法中,我們創(chuàng)建一個Foobar對象,讓據(jù)此創(chuàng)建這個動態(tài)生成的FoobarProxy,當(dāng)該對象的Invoke方法執(zhí)行的時候,我們期望的攔截操作自然會自動執(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(虛方法)


          如果待攔截的并非接口方法,而是一個虛方法,我們可以利用IL Emit的方式動態(tài)生成一個派生類,并重寫這個虛方法的方式來完成攔截。以下面的代碼片段為例,我們需要攔截定義在Foobar中的虛方法Invoke,我們可以生成如下這個派生與Foobar的Foobar的FoobarProxy類型,在重寫的Invoke方法中,我們在調(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();
          }
          }



          上面這個FoobarProxy類型就可以通過如下這個GenerateProxyClass生成出來。


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


          上面兩種方案都具有一個局限性:需要將針對目標(biāo)對象的方法調(diào)用轉(zhuǎn)換成針對代理對象的調(diào)用。如果我們能夠直接將目標(biāo)方法替換成另一個包含攔截操作的方案(或者說從原來的方法調(diào)轉(zhuǎn)到具有攔截操作的方法),那么即使我們不改變方法的調(diào)用方式,方法依舊能夠攔截。Harmony框架就是采用這樣的方案實(shí)現(xiàn)的,我們可以通過下面這個簡單的實(shí)例來模擬其實(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方法,我們在GenerateNewMethod方法中根據(jù)這個方法創(chuàng)建了一個DynamicMethodDefinition對象(定義在MonoMod.Common包中),并在方法體的前面添加了兩個IL指令將Indicator的Injected屬性設(shè)置為True,該方法最終返回通過這個DynamicMethodDefinition對象生成的MethodBase對象。


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


          四、RealProxy/TransparentProxy


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


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


          和第一種方案一樣,我們的代理對象依舊是封裝目標(biāo)對象,在實(shí)現(xiàn)的Invoke方案中,我們利用作為參數(shù)的IMessage 方法得到代表目標(biāo)方法的MethodBase對象,進(jìn)而利用它實(shí)現(xiàn)針對目標(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對象,然后將其封裝成一個FoobarProxy 對象。我們最終調(diào)用GetTransparentProxy方法創(chuàng)建出透明代理,并將其轉(zhuǎn)換成IFoobar類型。


          當(dāng)我們調(diào)用這個透明對象的任何一個方法的時候,定義在FoobarProxy 中的Invoke方法均會執(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項目中實(shí)現(xiàn),在.NET Core中它具有一個替代類型,那就是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é)
          回復(fù) 【訪客】學(xué)
          回復(fù) 【小程序】學(xué)獲取15套【入門+實(shí)戰(zhàn)+賺錢】小程序源碼
          回復(fù) 【python】學(xué)微獲取全套0基礎(chǔ)Python知識手冊
          回復(fù) 【2019】獲取2019 .NET 開發(fā)者峰會資料PPT
          回復(fù) 【加群】加入dotnet微信交流群

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


          臥槽:微信可以這樣換個字體了!


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

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

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  五月丁香激情四射 | www.人人撸 | 国产精品怡红院 | 国产人妻人伦精品一区 | 视频二区中文字幕 |