<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      .Net中的AOP系列之《AOP實(shí)現(xiàn)類型》

      返回《.Net中的AOP》系列學(xué)習(xí)總目錄


      本篇目錄


      Hi,guys!Long time no see! ??

      本節(jié)的源碼本人已托管于Coding上:點(diǎn)擊查看

      本系列的實(shí)驗(yàn)環(huán)境:VS 2017。


      讀完本章后,可能仍然不能實(shí)現(xiàn)自己的AOP工具,但應(yīng)該對(duì)兩種主要類型(PostSharp和Castle DynamicProxy)的AOP工具的運(yùn)行原理有了基本的理解。PostSharp是一個(gè)在編譯時(shí)編織的后期編譯器,Castle DynamicProxy會(huì)在運(yùn)行時(shí)生成一個(gè)代理類。雖然前面已經(jīng)說了很多如何使用這些工具,但是在項(xiàng)目中如果用的AOP工具越多,那么準(zhǔn)確地理解它們是如何運(yùn)行的就越重要。本章的目的,充分理解編譯時(shí)編織的代表PostSharp和運(yùn)行時(shí)編織的代表DynamicProxy。這些工具都是一流的代表,它們的實(shí)現(xiàn)會(huì)讓我們明白AOP是如何運(yùn)行的。

      AOP是如何跑起來的

      先來回顧下第1章的圖:


      在第1章中使用這張圖的目的是說明AOP能夠?qū)M切關(guān)注點(diǎn)劃分到單獨(dú)的類中,因而與業(yè)務(wù)邏輯分離并實(shí)現(xiàn)了自我封裝。所以,作為一個(gè)開發(fā)者,不必處理相互交織在一起的代碼,只需要把它交給AOP工具的切面就可以了。你作為一個(gè)開發(fā)者,只需要讀、寫和維護(hù)分離的類,然而,需要明白,這些代碼跑起來時(shí)還是會(huì)按照交織到一起的代碼進(jìn)行運(yùn)行。
      直到目前,我們只涉及了這張圖的上半部分,這節(jié)開始我們談?wù)勏掳氩糠帧>幙棧ɑ蚪豢棧琖eaving)是AOP框架將分離的類結(jié)合在一起的過程。在類被使用前,編織必須在某個(gè)時(shí)間點(diǎn)完成。在.Net中,這意味著可以剛好在編譯完成后進(jìn)行編織(編譯時(shí)編織),或者可以在代碼執(zhí)行期間的某個(gè)時(shí)間點(diǎn)進(jìn)行編織(運(yùn)行時(shí)編織)。

      下面先來看看最簡單的運(yùn)行時(shí)編織。

      運(yùn)行時(shí)編織

      運(yùn)行時(shí)編織,即編織發(fā)生在程序開始運(yùn)行之后。在其他代碼應(yīng)用切面代碼的同時(shí),才會(huì)去實(shí)例化一個(gè)切面。這就是為什么Castle DynamicProxy測試友好的原因,沒到運(yùn)行時(shí),什么都不會(huì)發(fā)生。


      運(yùn)行時(shí)編織工作的方式類似于上面的裝飾者/代理模式,但是不需要手動(dòng)創(chuàng)建裝飾類,運(yùn)行時(shí)編織器會(huì)在運(yùn)行時(shí)創(chuàng)建這些類。如上圖所示,我們?nèi)匀恢恍枰獎(jiǎng)?chuàng)建分離的BusinessModuleLogAspect,但是在運(yùn)行時(shí),其他類BusinessModuleProxy(姑且稱之為)會(huì)被創(chuàng)建,用于裝飾BusinessModule
      如果之前使用過代理模式或者裝飾者模式,那么上面的圖你會(huì)很熟悉。關(guān)鍵區(qū)別在于你不需要手動(dòng)創(chuàng)建代理類BusinessModuleProxy。如果不熟悉也沒關(guān)系,下一小節(jié)會(huì)針對(duì)這個(gè)有用的軟件設(shè)計(jì)模式進(jìn)行一個(gè)新手的講解。

      復(fù)習(xí)代理模式

      討論動(dòng)態(tài)代理之前先來復(fù)習(xí)一下代理模式的運(yùn)行原理。代理模式和裝飾者模式都是設(shè)計(jì)模式,只是有稍微不同的目的和實(shí)現(xiàn),但從AOP的角度看,實(shí)際上是一樣的。它們都運(yùn)行你將功能添加到某個(gè)類而不需要改變類本身的代碼。一般情況下,代理用于另一個(gè)對(duì)象的替身,通常對(duì)實(shí)例化真正的對(duì)象負(fù)責(zé),它和真實(shí)的對(duì)象有相同的接口。它可以控制訪問或提供附加的功能,以及對(duì)真實(shí)的對(duì)象進(jìn)行控制。


      對(duì)外來說,所有的程序只知道它正在使用一個(gè)具體的接口調(diào)用一個(gè)對(duì)象上的Method1方法。這個(gè)對(duì)象就是一個(gè)代理,在調(diào)用真正的方法 Method1之前,它有機(jī)會(huì)運(yùn)行自己的代碼。一旦方法Method1執(zhí)行完成,它又有機(jī)會(huì)運(yùn)行自己的代碼,最后才會(huì)返回原始程序的執(zhí)行結(jié)果。

      代理模式通常用于給程序表示外部的對(duì)象或服務(wù)(比如,web service)。在某些程序中,可能會(huì)給你一個(gè)生成的WCF代理類,它表示某個(gè)對(duì)象,你可以想象它就在你的程序中運(yùn)行那樣操作它,但在該接口的背后,該代理會(huì)發(fā)送HTTP調(diào)用來完成你的指令。

      裝飾者模式,在和真實(shí)的對(duì)象都有相同的接口方面,和代理模式是類似的。但是通常它不對(duì)實(shí)例化對(duì)象負(fù)責(zé),因此多個(gè)裝飾器可以分層在真實(shí)對(duì)象的頂部。除了LogAspect,還可以有 CacheAspect。它們都有和 BusinessModule相同的接口,以及自己的 BeginMethodEndMethod代碼。

      從類似AOP功能的角度講,代理模式和裝飾器模式幾乎是一樣的模式。下面通過一個(gè)控制臺(tái)程序演示一下代理模式:

      using static System.Console;
      namespace ProxyPatternReview
      {
          public interface IBusinessModule
          {
              void Method1();
          }
      
          public class BusinessModule : IBusinessModule
          {
              public void Method1()
              {
                  WriteLine(nameof(Method1));//輸出方法名稱
              }
          }
      }
      
      using static System.Console;
      namespace ProxyPatternReview
      {
          class Program
          {
              static void Main(string[] args)
              {
                  IBusinessModule bm = new BusinessModule();
                  bm.Method1();
                  ReadKey();
              }
          }
      }
      
      

      上面代碼很簡單,不多說。現(xiàn)在創(chuàng)建一個(gè)扮演BusinessModule代理的類 BusinessModuleProxy,它實(shí)現(xiàn)了相同的接口 IBusinessModule,這意味著我們只需要修改上面的 new語句代碼即可(現(xiàn)實(shí)中要修改IoC配置)。

      IBusinessModule bmProxy = new BusinessModuleProxy();
      bmProxy.Method1();
      
      

      Main方法而言,它不關(guān)心會(huì)獲得該模塊的任何對(duì)象,只要該對(duì)象的類實(shí)現(xiàn)了 IBusinessModule接口就行。下面是 BusinessModuleProxy的定義,記住它的工作是 BusinessModule的替身,因此它要實(shí)例化 BusinessModule,然后繼續(xù)執(zhí)行 BusinessModule的方法。

      public class BusinessModuleProxy : IBusinessModule
      {
          BusinessModule _bm;
          public BusinessModuleProxy()
          {
              _bm = new BusinessModule();
          }
          public void Method1()
          {
              _bm.Method1();
          }
      }
      
      

      這個(gè)類幾乎是無用的,除了是Main和BusinessModule的中間人之外,沒有其他目的。但是你可以在調(diào)用真實(shí)的方法Method1之前和之后放任何你想執(zhí)行的代碼,如下所示:

      public void Method1()
      {
          WriteLine($"{nameof(Method1)} begin!");
          _bm.Method1();
          WriteLine($"{nameof(Method1)} end!");
      }
      
      

      看著很熟悉吧?這個(gè)代理對(duì)象正在扮演攔截切面的角色。我們可以將它用于緩存、日志、線程以及其他任何攔截切面可以實(shí)現(xiàn)的東西。只要Main方法獲得了一個(gè)IBusiness對(duì)象(很可能通過IoC容器),無論是否使用了代理類對(duì)象,它都會(huì)工作。而且,無需改變BusinessModule的任何代碼就可以將橫切關(guān)注點(diǎn)加入真實(shí)的BusinessModule。

      但等一下,既然代理類能做AOP工具的事情,那么要AOP干什么?在一個(gè)有限的環(huán)境中,單獨(dú)地使用代理模式是有效的。但是如果要寫一個(gè)用于具有不同接口的多個(gè)類,那就需要為每個(gè)接口都要寫代理類了,是不是浪費(fèi)生命?

      如果你只有很少數(shù)量的類并且每個(gè)類有很少數(shù)量的方法,那么使用代理類沒多大問題。對(duì)于像日志和緩存這樣的橫切關(guān)注點(diǎn),編寫大量的相似的功能性代理類會(huì)變得很重復(fù)。比如,為兩個(gè)具有兩個(gè)方法的接口編寫代理類不困難,但想一下,如果有12個(gè)接口呢,每個(gè)接口又有12個(gè)方法,那么就要編寫接近144個(gè)一樣的代理類方法了。
      也想想,在一種不確定數(shù)量的類需要橫切關(guān)注點(diǎn)時(shí),比如日志項(xiàng)目可能本身會(huì)復(fù)用到多個(gè)解決方案。通過使用動(dòng)態(tài)代理,就不需要自己手動(dòng)寫所有的這些代理了,只需要讓動(dòng)態(tài)代理生成器幫助你工作即可。

      動(dòng)態(tài)代理

      雖然代理模式不依賴第三方工具就可以實(shí)現(xiàn)關(guān)注點(diǎn)分離,但是在某些時(shí)候需要確定下代理模式本身會(huì)變得太重復(fù)和模板化。如果你發(fā)現(xiàn)自己經(jīng)常在寫一些幾乎一樣的代理類,只是名字和接口稍微不同而已,那么是時(shí)候讓工具來為你完成這個(gè)工作了。Castle DynamicProxy (以及其他的使用了運(yùn)行時(shí)編織的AOP工具)會(huì)通過Reflection, 特別是 Reflection.Emit來生成這些類。不用再在一個(gè)代碼文件中定義類了,代理生成器會(huì)使用Reflection.Emit API來創(chuàng)建類的。

      來看一個(gè)類似于之前的代理模式的場景,以發(fā)微博為例。定義一個(gè)簡單的接口ISinaService,它有一個(gè)發(fā)送微博的方法,然后創(chuàng)建該接口的實(shí)現(xiàn)類 MySinaService,為了演示需要,只將發(fā)送內(nèi)容輸出到控制臺(tái):

      using static System.Console;
      namespace DynamicProxyPractice
      {
          public interface ISinaService
          {
              void SendMsg(string msg);
          }
      
          public class MySinaService:ISinaService
          {
              public void SendMsg(string msg)
              {
                  WriteLine($"[{msg}] has been sent!");
              }
          }
      }
      
      

      要使用代理模式或裝飾者模式,需要?jiǎng)?chuàng)建一個(gè)實(shí)現(xiàn)了該接口的類,暫且稱之為MySinaServiceProxy,它要對(duì)創(chuàng)建真實(shí)的對(duì)象負(fù)責(zé),并且可以實(shí)現(xiàn)自己的任何的代碼,下面只會(huì)在運(yùn)行真實(shí)的對(duì)象方法之前和之后輸出相應(yīng)信息,但在真實(shí)程序中,你可以實(shí)現(xiàn)日志,緩存等等功能:

      public class MySinaServiceProxy : ISinaService
      {
          private MySinaService _service;
          public MySinaServiceProxy()
          {
              _service = new MySinaService();
          }
          public void SendMsg(string msg)
          {
              WriteLine("Before");
              _service.SendMsg(msg);
              WriteLine("After");
          }
      }
      
      
      

      問題來了,如果這個(gè)服務(wù)類有十幾個(gè)方法,那么意味著我們這個(gè)代理類也要有十幾個(gè)方法。或者,當(dāng)前這個(gè)代理類只適合發(fā)微博,那么發(fā)微信呢,其他社交媒體呢?所以,每次真實(shí)的服務(wù)對(duì)象要添加或修改方法,你的代理類也必須做相應(yīng)修改。相反,我們可以在運(yùn)行時(shí)生成那些類。

      個(gè)人代理生成器

      這一小節(jié)我們會(huì)使用最原始的方法Reflection.Emit生成代理類。它不是動(dòng)態(tài)的,因?yàn)樗唤oMySinaService生成了代理,做個(gè)形象的比喻,如果DynamicProxy是趟快車,那我們這個(gè)工具只是個(gè)石頭輪子(還比不上木頭輪子)。
      這個(gè)例子不是打算教大家從頭開始寫自己的代理生成器,而是讓大家明白像Castle DynamicProxy這樣高級(jí)的工具是如何運(yùn)作的。

      因?yàn)镽eflection.Emit會(huì)生成MySinaServiceProxy,所以不需要在源碼中編寫了。相反,下面創(chuàng)建了一個(gè)返回類型為 MySinaServiceProxy的方法,通過 Activator.CreateInstance 和該這個(gè)返回類型我們可以創(chuàng)建一個(gè)新實(shí)例。下面就在Mian方法中完成這個(gè)代理生成器:

       static void Main(string[] args)
       {
           //生成一個(gè)動(dòng)態(tài)代理類型并返回
           var type = CreateDynamicProxyType();
           //使用Activator和上面的動(dòng)態(tài)代理類型實(shí)例化它的一個(gè)對(duì)象
           var dynamicProxy = Activator.CreateInstance(type,new object[] { new MySinaService()}) as ISinaService;
           //調(diào)用真實(shí)對(duì)象的方法
           dynamicProxy.SendMsg("test msg");
           ReadLine();
       }
       private static Type CreateDynamicProxyType()
       {
           //所有的Reflection.Emit方法都在這里
       }
      
      

      在運(yùn)行時(shí)構(gòu)建新類型和運(yùn)行時(shí)構(gòu)建新類型是相似的:

      1. 創(chuàng)建一個(gè)程序集;
      2. 在程序集中創(chuàng)建一個(gè)模塊;
      3. 使用Reflection.Emit API,創(chuàng)建一個(gè)AssemblyName,然后用它在當(dāng)前域中定義一個(gè)AssemblyBuilder,然后使用該AssemblyBuilder創(chuàng)建一個(gè)ModuleBuilder。如下所示:
              private static Type CreateDynamicProxyType()
              {
                  //所有的Reflection.Emit方法都在這里
                  //1 定義AssemblyName
                  var assemblyName = new AssemblyName("MyProxies");
                  //2 DefineDynamicAssembly為你指定的程序集返回一個(gè)AssemblyBuilder
                  AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
                  //3 使用AssemblyBuilder創(chuàng)建ModuleBuilder
                  ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MyProxies");
              }
      
      
      

      模塊名稱和程序集名稱可以不一樣,這里只是為了簡單。

      程序集和模塊

      • 程序集是編譯后的代碼類庫,包括exe和dll文件。
      • 程序集包含了一些元數(shù)據(jù),并且可能包含一個(gè)或多個(gè)模塊,但在實(shí)踐中很少包含多個(gè)模塊。
      • 模塊包含類。
      • 類包含成員(方法或字段)。

      一旦有了ModuleBuilder,就可以用它來構(gòu)建一個(gè)代理類。要構(gòu)建一個(gè)類型Type,需要一個(gè)類型名稱,特性(public和class),基類(所有類都有基類,即使是object)以及該類型實(shí)現(xiàn)的任何接口(這里我們要實(shí)現(xiàn)ISinaService)。明白了這些,然后使用ModuleBuilder的DefineType方法,它會(huì)返回一個(gè)TypeBuilder對(duì)象,如下代碼:

              private static Type CreateDynamicProxyType()
              {
                  //... 省略上面的代碼
                  TypeBuilder typeBuilder = moduleBuilder.DefineType(
                      "MySinaServiceProxy",//要?jiǎng)?chuàng)建的類型的名稱
                      TypeAttributes.Public|TypeAttributes.Class,//類型的特性
                      typeof(object),//基類
                      new[] {typeof(ISinaService)}//實(shí)現(xiàn)的接口
                      );
              }
      
      

      現(xiàn)在定義了一個(gè)類,但它是空的,我們還需要定義字段,構(gòu)造函數(shù)和方法。先從字段開始,這個(gè)字段是用來存儲(chǔ)真實(shí)對(duì)象的,以便在代理想調(diào)用它時(shí)使用。要?jiǎng)?chuàng)建字段,需要字段名稱,類型(MySinaService)和特性(這里private)。將這些信息在TypeBuilder的DefineField方法中進(jìn)行設(shè)置,就會(huì)返回一個(gè)FieldBuilder對(duì)象,如下:

      
      FieldBuilder fieldBuilder = typeBuilder.DefineField(
          "_realObject",
          typeof(MySinaService),
          FieldAttributes.Private
          );
      
      
      

      此時(shí),這個(gè)方法會(huì)生成相應(yīng)的下面的C#代碼,只是這種方式更冗長,因?yàn)槲覀冏隽撕途幾g器通常會(huì)為我們做的相似的工作。

         public class MySinaServiceProxy : ISinaService
          {
              private MySinaService _realObject;
          }
      
      

      下一步,需要構(gòu)建構(gòu)造函數(shù),它有一個(gè)形參,構(gòu)造函數(shù)體會(huì)把形參賦值給字段。我們可以再使用TypeBuilder定義構(gòu)造函數(shù)。要定義它,需要特性(只能是public),調(diào)用約定(實(shí)例構(gòu)造函數(shù)還是靜態(tài)構(gòu)造函數(shù))以及每個(gè)形參是參數(shù)類型(這里只有一個(gè)類型為SinaService的參數(shù))。然后使用DefineConstructor方法來定義構(gòu)造函數(shù),一旦定義了構(gòu)造函數(shù),我們需要一種方法將代碼放入構(gòu)造函數(shù)中,這里使用GetILGenerator獲得構(gòu)造函數(shù)的 ILGenerator對(duì)象:

                  ConstructorBuilder constructorBuilder = typeBuilder.DefineConstructor(
                      MethodAttributes.Public,
                      CallingConventions.HasThis,
                      new[] {typeof(MySinaService)}
                      );
                  ILGenerator ilgenerator = constructorBuilder.GetILGenerator();
      
      

      在構(gòu)造函數(shù)中,我們只需要一個(gè)語句來將參數(shù)分配給該字段(如果你計(jì)數(shù)return的話,就是兩個(gè)語句,return在C#中是隱含的)。 在調(diào)用DefineConstructor時(shí),我們創(chuàng)建了一個(gè)指定類型數(shù)組的參數(shù),但請(qǐng)注意參數(shù)沒有名稱。 就. NET而言,這只有參數(shù)argument 1。(為什么是參數(shù)1而不是參數(shù)0?因?yàn)閰?shù)0是this - -當(dāng)前的實(shí)例)。

      要將代碼放在構(gòu)造函數(shù)中,我們需要使用constuctorBuilder來發(fā)出公共中間語言(CIL)操作碼。 可能你認(rèn)為到這里的一切都很復(fù)雜,其實(shí)這里真的很難。 沒有多少人是精通Reflection.Emit的專家,但是因?yàn)檫@是一個(gè)簡單的操作,我還是能夠正確分配OpCodes的。 它包含三個(gè)部分:參數(shù)0(this),參數(shù)1(參數(shù)的輸入值)和將被賦值的字段。 它們被發(fā)送到計(jì)算堆棧,所以排序可能看起來很別扭。

      //將this加載到計(jì)算棧
      ilgenerator.Emit(OpCodes.Ldarg_0);
      //將構(gòu)造函數(shù)的形參加載到棧
      ilgenerator.Emit(OpCodes.Ldarg_1);
      //將計(jì)算結(jié)果保存到字段
      ilgenerator.Emit(OpCodes.Stfld, fieldBuilder);
      //從構(gòu)造函數(shù)返回
      ilgenerator.Emit(OpCodes.Ret);
      
      

      現(xiàn)在我們已經(jīng)生成了一個(gè)有名稱,有一個(gè)命名的私有字段和在構(gòu)造函數(shù)中設(shè)置私有字段的類型。 為確保此類型實(shí)現(xiàn)ISinaService接口,我們需要定義一個(gè)名為SendMsg的void方法,它有一個(gè)字符串參數(shù),如下列表所示。 使用TypeBuilder的這個(gè)信息以及DefineMethod和DefineMethodOverride,我們還需要另一個(gè)ILGenerator將代碼發(fā)送到此方法的方法體中。

                  MethodBuilder methodBuilder = typeBuilder.DefineMethod(
                      "SendMsg",//方法名稱
                      MethodAttributes.Public | MethodAttributes.Virtual,//方法修飾符
                      typeof(void),//無返回值
                      new[] { typeof(string) }//有個(gè)字符串參數(shù)
                      );
                  //指定要構(gòu)建的方法實(shí)現(xiàn)了ISinaService接口的SendMsg方法
                  typeBuilder.DefineMethodOverride(
                      methodBuilder,
                      typeof(ISinaService).GetMethod("SendMsg")
                      );
                  //獲取一個(gè)ILGenerator將代碼添加到SendMsg方法
                  ILGenerator sendMsgIlGenerator = methodBuilder.GetILGenerator();
      
      

      現(xiàn)在我們有一個(gè)SendMsg方法,我們需要填寫代碼。 在MySinaServiceProxy中,SendMsg方法將“Before”輸出到Console,然后調(diào)用真實(shí)的SendMsg方法,隨后將“After”寫入控制臺(tái)。 我們需要通過發(fā)射OpCodes來處理所有這些事情,如該列表所示。

      
                  //加載字符串變量到計(jì)算棧
                  sendMsgIlGenerator.Emit(OpCodes.Ldstr, "Before");
                  //調(diào)用Console類的靜態(tài)WriteLine方法
                  sendMsgIlGenerator.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }));
                  //將參數(shù)argument0(this)加載到棧
                  sendMsgIlGenerator.Emit(OpCodes.Ldarg_0);
                  //將字段_realObject加載到棧
                  sendMsgIlGenerator.Emit(OpCodes.Ldfld, fieldBuilder);
                  //加載SendMsg的參數(shù)到棧
                  sendMsgIlGenerator.Emit(OpCodes.Ldarg_1);
                  //調(diào)用字段上的SendMsg方法
                  sendMsgIlGenerator.Emit(OpCodes.Call, fieldBuilder.FieldType.GetMethod("SendMsg"));
                  //加載字符串After到棧
                  sendMsgIlGenerator.Emit(OpCodes.Ldstr, "After");
                  //調(diào)用Console類的靜態(tài)WriteLine方法
                  sendMsgIlGenerator.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }));
                  //返回
                  sendMsgIlGenerator.Emit(OpCodes.Ret);
      
      
      

      就這樣 ,TypeBuilder對(duì)象具有構(gòu)建我們想要的代理所需的所有信息。最后一步是使用構(gòu)建器創(chuàng)建類型(并返回它):

      return typeBuilder.CreateType();
      

      Opcodes MSDN 文檔
      點(diǎn)擊查看

      運(yùn)行結(jié)果見下圖,是不是有種既滿足(按照預(yù)期)又失望(太費(fèi)力了)的感覺?

      正如我之前所說,我們本章遠(yuǎn)遠(yuǎn)還不能構(gòu)建一個(gè)完整的動(dòng)態(tài)代理生成器。 要做這個(gè)小演示成為有點(diǎn)像樣的動(dòng)態(tài)代理生成器將需要大量工作,包括(但不限于):

      • 使其能夠代理任何類型,而不是只有MySinaService對(duì)象。
      • 使其能夠處理這些對(duì)象中的任何方法,而不僅僅是SendMsg方法。
      • 使其能夠執(zhí)行任意的切面代碼,而不是僅僅在控制臺(tái)輸出點(diǎn)東西。
      • 將其全部包裝在一個(gè)漂亮的,封裝的,易于使用的API中。

      幸運(yùn)的是,諸如DynamicProxy這樣的工具為我們打開了這條路,所以我們沒有必要做所有這些繁瑣的管道。 通過向你展示這個(gè)過于簡單的動(dòng)態(tài)代理版本,我希望能夠完成兩件事情:可以看到專門知識(shí)和復(fù)雜的工作,已經(jīng)進(jìn)入這些工具的制作之中,并給予你機(jī)會(huì)看看動(dòng)態(tài)代理生成的底層原理。 當(dāng)實(shí)現(xiàn)一個(gè)IInterceptor并將其提供給DynamicProxy ProxyGenerator時(shí),其實(shí)你正在使用Reflection.Emit在運(yùn)行時(shí)開始一系列復(fù)雜的程序集,模塊,類型,領(lǐng)域和方法構(gòu)建來創(chuàng)建一個(gè)新的類型,但這些不存在于源代碼中。

      編譯時(shí)編織工具的工作原理類似于運(yùn)行時(shí)編織,除了它不會(huì)在運(yùn)行時(shí)創(chuàng)建一個(gè)新的類型,我們?cè)诒菊轮幸恢痹谟懻摗K鼤?huì)在執(zhí)行代碼之前修改由正常的.NET編譯器創(chuàng)建的程序集中的類型。

      編譯時(shí)編織

      當(dāng)你在C#中創(chuàng)建一個(gè).NET項(xiàng)目時(shí),它被編譯成CIL(也稱為MSIL,IL和字節(jié)碼)然后成為程序集(DLL或EXE文件)。 下圖說明了流程的這個(gè)過程。 公共語言運(yùn)行時(shí)(CLR)然后將CIL轉(zhuǎn)換成實(shí)際的機(jī)器指令(通過稱為即時(shí)編譯的過程,或JIT)。 作為.NET開發(fā)人員,這個(gè)過程應(yīng)該是你熟悉的。

      使用編譯時(shí)編織的AOP工具為此過程提供了另一個(gè)步驟稱為后期編譯(因此名稱PostSharp)。 完成編譯后,PostSharp(或其他編譯時(shí)的AOP工具)然后為已經(jīng)創(chuàng)建的切面以及你已經(jīng)指出切面用在了什么地方去檢查程序集。 然后它直接修改程序集中的CIL來執(zhí)行編織,如圖所示。

      這種方法的一個(gè)很好的副作用是PostSharp可以檢測到的任何錯(cuò)誤也可以在Visual Studio中顯示,就好像它們是來自編譯器的錯(cuò)誤(關(guān)于更多詳細(xì)見下一章)

      在編譯器完成創(chuàng)建CIL代碼之后,后期編譯器進(jìn)程將立即根據(jù)你編寫的切面以及在哪里應(yīng)用了那些切面運(yùn)行和修改CIL代碼。 修改CIL的這個(gè)過程是任何編譯時(shí)AOP工具通用基礎(chǔ),但在本節(jié)的其余部分,你將看到一些PostSharp具體運(yùn)行的細(xì)節(jié),以及最終修改后的CIL是什么樣子。

      后期編譯(PostCompiling)

      為了幫助你理解PostSharp,我們先來一步一步看看PostSharp是如何工作的。

      第一步是在編譯之前,當(dāng)然你會(huì)使用PostSharp.dll庫編寫切面,并指出那些方面應(yīng)該用在什么地方(例如,指定具有特性的切入點(diǎn))。所有PostSharp切面都是特性,通常不會(huì)自己執(zhí)行(它們只是元數(shù)據(jù))。下一步是在編譯項(xiàng)目后立即進(jìn)行。

      編譯器會(huì)查看你的源代碼并將其轉(zhuǎn)換成一個(gè)包含CIL的程序集。 之后,PostSharp后期編譯程序接管。 它會(huì)檢查你編寫的切面,指定的切面用在了什么地方,以及程序集的CIL。 然后PostSharp會(huì)做幾件事情:實(shí)例化切面,序列化切面,并修改CIL,以適當(dāng)調(diào)用該切面。

      當(dāng)PostSharp完成工作后,序列化切面將被存儲(chǔ)為匯編中的二進(jìn)制流(作為資源)。 此流將在運(yùn)行時(shí)加載用于執(zhí)行(并且還將執(zhí)行其RuntimeInitialize方法)。為了幫助可視化此過程,下是使用偽代碼來表示項(xiàng)目的三個(gè)主要狀態(tài)的圖:你編寫的源代碼,由編譯器與PostSharp合作創(chuàng)建的程序集,以及由CLR執(zhí)行的執(zhí)行程序。

      所有這些都可能聽起來有點(diǎn)復(fù)雜,所以為了進(jìn)一步演示,讓我們回顧一下PostSharp來龍去脈的工作原理。 我們將通過使用反編譯器比較寫入編譯后的程序集中的源代碼。

      來龍去脈

      反編譯器是一個(gè)可以分析.NET程序集(如DLL或EXE文件)的工具,并將其從CIL轉(zhuǎn)換回C#代碼。 它反過來編譯(CIL到C#而不是C#到CIL)。 你可以使用各種反編譯工具來實(shí)現(xiàn)反編譯,而且它們都傾向于有一組共同的功能,但我在這里使用的工具叫做ILSpy。官網(wǎng)http://ilspy.net 。

      為了演示,我要編寫一個(gè)簡單的程序,編譯它,并使用ILSpy反編譯。 起初,我不會(huì)使用任何AOP,這意味著我希望看到我的C#和我的反編譯的C#是相同的。 這是一個(gè)只有一個(gè)方法簡單的類,在Visual Studio中:

      namespace BeforeAndAfter
      {
          class Program
          {
              static void Main(string[] args)
              {
                  Console.WriteLine("Hello World!");
              }
          }
      }
      
      
      

      然后我將其編譯(在我的項(xiàng)目的bin文件夾中的DLL或EXE文件中)。 如果我使用ILSpy打開它與導(dǎo)航到Program,然后ILSpy將顯示我下圖:

      如果你使用其他工具,你可能看不到完全相同的東西。 可能會(huì)出現(xiàn)一個(gè)默認(rèn)的無參數(shù)構(gòu)造函數(shù)。 每個(gè)類都需要一個(gè)構(gòu)造函數(shù),并且由于我沒有明確定義一個(gè)構(gòu)造函數(shù),所以編譯器假定一個(gè)public,空方法體,無參數(shù)的構(gòu)造函數(shù)。 反編譯時(shí),ILSpy也會(huì)做出相同的假設(shè)。 除此之外,反編譯的C#應(yīng)該看起來和原來的C#相似,不管是你使用哪一個(gè)工具。

      現(xiàn)在讓我們使用PostSharp在這個(gè)項(xiàng)目的代碼中添加一個(gè)切面。 PostSharp會(huì)修改CIL,這意味著我不會(huì)指望反編譯的C#與Visual Studio中看到的C#看起來相同。 以下列表再次顯示Program,這次用一個(gè)簡單的PostSharp方法應(yīng)用到Main方法:

      class Program
          {
              [MyAspect]
              static void Main(string[] args)
              {
                  Console.WriteLine("Hello World!");
              }
          }
      
      using PostSharp.Aspects;
      namespace BeforeAndAfter
      {
          [Serializable]
          public class MyAspect:OnMethodBoundaryAspect
          {
              public override void OnEntry(MethodExecutionArgs args)
              {
                  Console.WriteLine("Before");
              }
      
              public override void OnExit(MethodExecutionArgs args)
              {
                  Console.WriteLine("After");
              }
          }
      }
      
      

      編譯完后,我再次使用ILSpy打開程序集,看看反編譯Main代碼 如果你使用免費(fèi)的PostSharp Express,你會(huì)看到代碼很長的方法(與完整商業(yè)版相比)。

      PostSharp 功能:Aspect切面優(yōu)化器
      PostSharp Express不包括Aspect優(yōu)化器。 Aspect優(yōu)化器會(huì)檢查你編寫的切面,并修改IL只能完成任務(wù)你想做的事情。 例如,如果你根本不使用args對(duì)象,那么這個(gè)切面優(yōu)化器將會(huì)發(fā)現(xiàn)這一點(diǎn),當(dāng)OnEntry和OnExit被調(diào)用時(shí),一個(gè)空值將是傳遞給args參數(shù)。 另一個(gè)例子:因?yàn)槲覀儧]有重寫OnSuccess或OnException,所以aspect優(yōu)化器會(huì)看到這一點(diǎn),并且在編織創(chuàng)建代碼時(shí)不會(huì)調(diào)用那些空的基本方法。
      PostSharp Express沒有這個(gè)優(yōu)化器 - 它假設(shè)你需要所有的東西,這就是為什么反編譯版本的Main方法太長了。

      從上圖可以看到,編譯后的程序集包含兩個(gè)命名空間,一個(gè)是我們定義的命名空間BeforeAndAfter,另一個(gè)是PostSharp生成的隨機(jī)命名空間PostSharp.ImplementationDetails_f1559a2f,里面包含了PostSharp的詳細(xì)實(shí)現(xiàn)代碼,大概過程就是將元數(shù)據(jù)通過二進(jìn)制序列化器反序列化為對(duì)應(yīng)的我們定義的切面對(duì)象,然后再在Program程序中引入 PostSharp.ImplementationDetails_f1559a2f命名空間,調(diào)用我們的切面的方法。Program類編織后的代碼和預(yù)想的差不多,但是PostSharp隨機(jī)生成的命名空間的代碼是AOP實(shí)現(xiàn)的關(guān)鍵。

      命名可能看起來很奇怪。 這些都是怪異的名字,但它們只是名稱,像任何其他類或方法名稱一樣。 OnEntry是一個(gè)名為a0對(duì)象的方法,它是MyAspect類型。 對(duì)象a0是一個(gè)內(nèi)部隱藏類<>z__a_1的內(nèi)部靜態(tài)只讀成員。

      PostSharp在編譯時(shí)通過添加和操作CIL,創(chuàng)建了這段代碼中的幾乎所有內(nèi)容,這些都是根據(jù)你寫的切面而生成的。 一些生成的CIL不直接對(duì)應(yīng)C#。 名稱<>z__a_1在C#中無效。 這些都是ILSpy盡力解讀的表現(xiàn)。

      這部分和前一段可能似乎是深入.NET的底層,但現(xiàn)實(shí)中我們很少接觸到Reflection.Emit和CIL的操縱。 幸運(yùn)的是,我們作為AOP工具的用戶 - 大多數(shù)時(shí)候不需要關(guān)心這樣的復(fù)雜性。 但重要的是要有一些對(duì)這些AOP實(shí)現(xiàn)的內(nèi)部工作的理解,因?yàn)槲覀円獙?duì)下決定使用哪種類型的AOP負(fù)責(zé)。 我們應(yīng)該使用運(yùn)行時(shí)編織,還是應(yīng)該使用編譯時(shí)編織?

      運(yùn)行時(shí)編織 VS. 編譯時(shí)編織

      開發(fā)人員似乎擔(dān)心的一個(gè)因素是性能,所以讓我們從通過比較兩種方法的性能入手。 根據(jù)我的經(jīng)驗(yàn),現(xiàn)實(shí)是,程序中的性能瓶頸很少由使用AOP工具引起,而與在開發(fā)人員的生產(chǎn)力和可維護(hù)的代碼受益方面相比,AOP造成的任何性能問題都不重要。

      如你所見,運(yùn)行時(shí)AOP工具如DynamicProxy使用Reflection.Emit,這可能是用戶注意到的慢操作,但一旦類型創(chuàng)建,它不需要再次創(chuàng)建,所以這個(gè)性能點(diǎn)相對(duì)可以忽略不計(jì)。 編譯時(shí)工具不會(huì)使用緩慢的Reflection.Emit操作,因?yàn)樗诰幾g時(shí)執(zhí)行其工作。 可以看得到,開發(fā)人員在解決方案中擁有大量使用了PostSharp項(xiàng)目時(shí),這會(huì)增加構(gòu)建時(shí)間。這是最常見的關(guān)于后期編譯工具的抱怨。 但隨著PostSharp新版本的性能不斷提高,你可以配置大型多項(xiàng)目解決方案,以使PostSharp不執(zhí)行那些不使用切面的項(xiàng)目。 如果性能是你的主要關(guān)注點(diǎn),這兩種類型的工具都會(huì)以某種方式降低性能,盡管可能在實(shí)踐中注意到不是足夠慢。

      因此,你如何決定哪個(gè)AOP實(shí)現(xiàn)更好:運(yùn)行時(shí)編織或編譯時(shí)編織,只基于性能考慮? 你應(yīng)該使用哪一個(gè)? 雖然很討厭這種回答,但它是真實(shí)的:這視情況而定。

      如果你沒有使用很多切面,或者你沒有在許多class上使用它們,就可以用寫代理或裝飾器類,根本不用任何第三方的AOP工具。

      但是,如果你的項(xiàng)目使用了很多橫切關(guān)注點(diǎn),AOP肯定會(huì)對(duì)你有好處。 也許在運(yùn)行時(shí)動(dòng)態(tài)生成的類型,也許在編譯時(shí)修改CIL。 也許兩者都行。 讓我們看下每種方法的好處。

      運(yùn)行時(shí)編織優(yōu)點(diǎn)

      使用你已經(jīng)看過的DynamicProxy等工具的主要優(yōu)點(diǎn)之一是它很容易測試(參見單元測試章節(jié))。 一個(gè)DynamicProxy攔截器可以在運(yùn)行時(shí)輕松注入依賴關(guān)系,方便編寫切面獨(dú)立的測試。

      第二,與PostSharp這樣的工具相比,像DynamicProxy這樣的運(yùn)行時(shí)工具不需要后編譯過程。 你不需要單獨(dú)的EXE,使其在每個(gè)團(tuán)隊(duì)成員的計(jì)算機(jī)和構(gòu)建服務(wù)器上正確編譯。 因此可能更容易將AOP引入項(xiàng)目團(tuán)隊(duì)和/或項(xiàng)目的構(gòu)建服務(wù)器。

      第三,因?yàn)榉矫嬖谶\(yùn)行時(shí)才被實(shí)例化,你也可以保留在構(gòu)建完成后配置切面的能力。 運(yùn)行時(shí)你擁有一定的靈活性 - 比如,可以使用XML文件更改切面配置。

      最后,雖然許可和成本是復(fù)雜的問題,但是DynamicProxy是一個(gè)世界一流的AOP框架,是一個(gè)免費(fèi)的開源工具,所以我一定會(huì)將它作為運(yùn)行時(shí)編織陣營的頭牌。 這些是運(yùn)行時(shí)優(yōu)于編譯時(shí)編織的關(guān)鍵領(lǐng)域。

      編譯時(shí)編織優(yōu)點(diǎn)

      編譯時(shí)編織有一些不同的好處。 由于PostSharp這樣的工具運(yùn)行的本質(zhì)(通過在程序集文件中直接操作CIL),它們可以更強(qiáng)大。

      首先,通過運(yùn)行時(shí)編織,攔截器通常被應(yīng)用于類的每個(gè)方法,即使你只對(duì)一個(gè)類感興趣。 使用PostSharp等工具可以使用更細(xì)粒度的控制來應(yīng)用切面。

      其次,使用運(yùn)行時(shí)編織,你通常需要使用IoC容器來使用攔截方面。 但是,程序中的每個(gè)對(duì)象并不總是這樣通過IoC工具實(shí)例化。 例如,UI對(duì)象和域?qū)ο罂赡懿贿m合或不能用容器實(shí)例化。 因此,PostSharp等工具具有運(yùn)行時(shí)AOP工具不具備的附加功能。

      如果你正在開發(fā)的項(xiàng)目沒有使用IoC工具,那么為了使用運(yùn)行時(shí)AOP,你需要重新構(gòu)建代碼才能使用IoC工具,然后才能開始使用AOP。 通過編譯時(shí)AOP工具,你可以立即開始獲得AOP的優(yōu)勢。 我不是說你不應(yīng)該使用IoC或其他依賴注入工具。無論你是否使用AOP, 依賴注入是一個(gè)非常有用的工具,可以讓你創(chuàng)建松散耦合,易于測試的代碼。 但不是你從事的每個(gè)代碼庫都有使用DI來構(gòu)建,而且重構(gòu)過程可能會(huì)慢而昂貴。

      編譯時(shí)工具更強(qiáng)大的最后一點(diǎn)是,它允許任何代碼使用AOP:包括靜態(tài)方法,私有方法和字段(在第5章中見位置攔截)。

      小結(jié)

      最終,我不能單方面決定使用一種方法:只有你可以做出這個(gè)決定。 除了我提到的技術(shù)利弊之外,還要考慮整個(gè)非技術(shù)因素,包括許可,價(jià)格和支持。 我用了很多篇幅描述兩種主要的方法:運(yùn)行時(shí)編織和編譯時(shí)編織。 在實(shí)踐中,你評(píng)估的各個(gè)工具可能有所不同。 這個(gè)工具有多成熟? 它的API可能會(huì)改變嗎? 它的API是否有意義? 你的團(tuán)隊(duì)其余成員最適合什么? 你是從舊版代碼庫開始還是從頭開始創(chuàng)建一個(gè)新項(xiàng)目?

      這些都是在你下決定時(shí)必須考慮的關(guān)鍵屬性。 但是,這些都在技術(shù)之外,因?yàn)槊總€(gè)團(tuán)隊(duì),每個(gè)公司,每一個(gè)AOP工具,每個(gè)項(xiàng)目都不同。 現(xiàn)在你熟悉使用AOP和AOP工具如何工作,你對(duì)于項(xiàng)目或代碼庫的架構(gòu)就會(huì)有整體的決定。

      除了我一直在描述的常見橫切關(guān)注點(diǎn)的切面,AOP具有架構(gòu)師感興趣的功能。由于PostSharp在編譯之后會(huì)對(duì)代碼進(jìn)行檢查,因此它可以提供許多其他AOP工具無法提供的額外功能。 在下一章,我們來看看如何將PostSharp引入到你的架構(gòu)。

      posted @ 2017-06-12 07:25  tkbSimplest  閱讀(4443)  評(píng)論(4)    收藏  舉報(bào)
      主站蜘蛛池模板: 欧洲无码一区二区三区在线观看| 青青草无码免费一二三区| 不卡av电影在线| 久久国产自拍一区二区三区| 国产精品久久久久不卡绿巨人| 午夜免费啪视频| 成人免费看片又大又黄| 久久丫精品久久丫| 亚洲一区二区约美女探花| 久久国产成人高清精品亚洲| 色一情一区二区三区四区| 亚洲无线码一区二区三区| 欧美肥老太牲交大战| 日韩精品一区二区三区影院| 国产av一区二区三区无码野战 | 人人爽天天碰天天躁夜夜躁| 久久精品国产色蜜蜜麻豆| 真实国产乱啪福利露脸| 动漫AV纯肉无码AV电影网| 久久99久国产精品66| 久青草精品视频在线观看| 亚洲中文字幕一区二区| 乱码视频午夜在线观看| 动漫AV纯肉无码AV电影网| 风韵丰满妇啪啪区老老熟女杏吧| 巴里| 日韩毛片在线视频x| 在线a亚洲老鸭窝天堂| 亚洲精品一区二区18禁| 韩国无码AV片午夜福利| 日韩精品福利视频在线观看| 国产精品先锋资源在线看| 四虎永久免费高清视频| 国产乱理伦片在线观看| 岛国大片在线免费播放| 亚洲日韩国产成网在线观看| 在线观看国产成人av天堂| 午夜在线观看成人av| 精品久久久久无码| 成人免费亚洲av在线| 久久婷婷五月综合色丁香花|