AOP從靜態代理到動態代理 Emit實現

【前言】

  AOP爲Aspect Oriented Programming的縮寫,意思是面向切面編程的技術。git

  何爲切面?程序員

  一個和業務沒有任何耦合相關的代碼段,諸如:調用日誌,發送郵件,甚至路由分發。一切能爲代碼全部且能和代碼充分解耦的代碼均可以做爲一個業務代碼的切面。github

  咱們爲何要AOP?編程

  那咱們從一個場景舉例提及:框架

  若是想要採集用戶操做行爲,咱們須要掌握用戶調用的每個接口的信息。這時候的咱們要怎麼作?ide

  若是不採用AOP技術,也是最簡單的,全部方法體第一句話先調用一個日誌接口將方法信息傳遞記錄。性能

  有何問題?學習

  實現業務沒有任何問題,可是隨之而來的是代碼臃腫不堪,難以調整維護的諸多問題(可自行腦補)。測試

  若是咱們採用了AOP技術,咱們就能夠在系統啓動的地方將全部將要採集日誌的類注入,每一次調用方法前,AOP框架會自動調用咱們的日誌代碼。ui

  是否是省去了不少重複無用的勞動?代碼也將變得很是好維護(有朝一日不須要了,只需將切面代碼註釋掉便可)

  接下來咱們看看AOP框架的工做原理以及實過程。

【實現思路】

  AOP框架呢,通常經過靜態代理和動態代理兩種實現方式。

  

  何爲靜態代理? 

  靜態代理,又叫編譯時代理,就是在編譯的時候,已經存在代理類,運行時直接調用的方式。說的通俗一點,就是本身手動寫代碼實現代理類的方式。

  咱們經過一個例子來展示一下靜態代理的實現過程:

  咱們這裏有一個業務類,裏面有方法Test(),咱們要在Test調用前和調用後分別輸出日誌。

  

  咱們既然要將Log看成一個切面,咱們確定不能去動原有的業務代碼,那樣也違反了面向對象設計之開閉原則。

  那麼咱們要怎麼作呢?咱們定義一個新類 BusinessProxy 去包裝一下這個類。爲了便於在多個方法的時候區分和辨認,方法也叫 Test()

 

 

 

  這樣,咱們若是要在全部的Business類中的方法都添加Log,咱們就在BusinessProxy代理類中添加對應的方法去包裝。既不破壞原有邏輯,又能夠實現先後日誌的功能。

  固然,咱們能夠有更優雅的實現方式:

  

 

  咱們能夠定義代理類,繼承自業務類。將業務類中的方法定義爲虛方法。那麼咱們能夠重寫父類的方法而且在加入日誌之後再調用父類的原方法。

  固然,咱們還有更加優雅的實現方式:

  

  

  咱們可使用發射的技術,寫一個通用的Invoke方法,全部的方法均可以經過該方法調用。

  咱們這樣便實現了一個靜態代理。

  那咱們既然有了靜態代理,爲何又要有動態代理呢?

  咱們仔細回顧靜態代理的實現過程。咱們要在全部的方法中添加切面,咱們就要在代理類中重寫全部的業務方法。更有甚者,咱們有N個業務類,就要定義N個代理類。這是很龐大的工做量。

  

  這就是動態代理出現的背景,相比均可以猜獲得,動態代理就是將這一系列繁瑣的步驟自動化,讓程序自動爲咱們生成代理類。

  何爲動態代理?

  動態代理,又成爲運行時代理。在程序運行的過程當中,調用了生成代理類的代碼,將自動生成業務類的代理類。不須要咱們手共編寫,極高的提升了工做效率和調整了程序員的心態

  原理沒必要多說,就是動態生成靜態代理的代碼。咱們要作的,就是選用一種生成代碼的方式去生成。

  今天我分享一個簡單的AOP框架,代碼使用Emit生成。固然,Emit 代碼的寫法不是今天要講的主要內容,須要提早去學習。

  先說效果:

  定義一個Action特性類 ActionAttribute 繼承自 ActionBaseAttribute,裏面在Before和After方法中輸出兩條日誌;

  

  定義一個Action特性類 InterceptorAttribute 繼承自 InterceptorBaseAttribute,裏面捕獲了方法調用異常,以及執行先後分別輸出日誌;

  

  而後定義一個業務類 BusinessClass 實現了 IBusinessClass 接口,定義了各類類型的方法

  

  

  多餘的方法不貼圖了。

  咱們把上面定義的方法調用切面標籤放在業務類上,表示該類下全部的方法都執行異常過濾;

  咱們把Action特性放在Test方法上,代表要在 Test() 方法的 Before 和 After 調用時記錄日誌;

  咱們定義測試類:

  

  調用一下試試:

  

  可見,全類方法標籤 Interceptor Test GetInt 方法調用先後都打出了對應的日誌;

  Action方法標籤只在 Test 方法上作了標記,那麼Test 方法 Before After 執行時打出了日誌;

【實現過程】

  實現的思路在上面已經有詳細的講解,能夠參考靜態代理的實現思路。

  咱們定義一個動態代理生成類 DynamicProxy,用於原業務代碼的掃描和代理類代碼的生成;

  定義兩個過濾器標籤,ActionBaseAttribute,提供 Before 和 After 切面方法;InterceptorBaseAttribute,提供 Invoke 「全調用」包裝的切面方法;

  Before能夠獲取到當前調用的方法和參數列表,After能夠獲取到當前方法調用之後的結果。

  Invoke 能夠拿到當前調用的對象和方法名,參數列表。在這裏進行反射動態調用。

1 [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
2     public class ActionBaseAttribute : Attribute
3     {
4         public virtual void Before(string @method, object[] parameters) { }
5 
6         public virtual object After(string @method, object result) { return result; }
7     }
1 [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
2     public class InterceptorBaseAttribute : Attribute
3     {
4         public virtual object Invoke(object @object, string @method, object[] parameters)
5         {
6             return @object.GetType().GetMethod(@method).Invoke(@object, parameters);
7         }
8     }

  代理生成類採用Emit的方式生成運行時IL代碼。

  先把代碼放在這裏:

  1 public class DynamicProxy
  2     {
  3         public static TInterface CreateProxyOfRealize<TInterface, TImp>() where TImp : class, new() where TInterface : class
  4         {
  5             return Invoke<TInterface, TImp>();
  6         }
  7 
  8         public static TProxyClass CreateProxyOfInherit<TProxyClass>() where TProxyClass : class, new()
  9         {
 10             return Invoke<TProxyClass, TProxyClass>(true);
 11         }
 12 
 13         private static TInterface Invoke<TInterface, TImp>(bool inheritMode = false) where TImp : class, new() where TInterface : class
 14         {
 15             var impType = typeof(TImp);
 16 
 17             string nameOfAssembly = impType.Name + "ProxyAssembly";
 18             string nameOfModule = impType.Name + "ProxyModule";
 19             string nameOfType = impType.Name + "Proxy";
 20 
 21             var assemblyName = new AssemblyName(nameOfAssembly);
 22 
 23             var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
 24             var moduleBuilder = assembly.DefineDynamicModule(nameOfModule);
 25 
 26             //var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);
 27             //var moduleBuilder = assembly.DefineDynamicModule(nameOfModule, nameOfAssembly + ".dll");
 28 
 29             TypeBuilder typeBuilder;
 30             if (inheritMode)
 31                 typeBuilder = moduleBuilder.DefineType(nameOfType, TypeAttributes.Public, impType);
 32             else
 33                 typeBuilder = moduleBuilder.DefineType(nameOfType, TypeAttributes.Public, null, new[] { typeof(TInterface) });
 34 
 35             InjectInterceptor<TImp>(typeBuilder, impType.GetCustomAttribute(typeof(InterceptorBaseAttribute))?.GetType(), inheritMode);
 36 
 37             var t = typeBuilder.CreateType();
 38 
 39             //assembly.Save(nameOfAssembly + ".dll");
 40 
 41             return Activator.CreateInstance(t) as TInterface;
 42         }
 43 
 44         private static void InjectInterceptor<TImp>(TypeBuilder typeBuilder, Type interceptorAttributeType, bool inheritMode = false)
 45         {
 46             var impType = typeof(TImp);
 47             // ---- define fields ----
 48             FieldBuilder fieldInterceptor = null;
 49             if (interceptorAttributeType != null)
 50             {
 51                 fieldInterceptor = typeBuilder.DefineField("_interceptor", interceptorAttributeType, FieldAttributes.Private);
 52             }
 53             // ---- define costructors ----
 54             if (interceptorAttributeType != null)
 55             {
 56                 var constructorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, null);
 57                 var ilOfCtor = constructorBuilder.GetILGenerator();
 58 
 59                 ilOfCtor.Emit(OpCodes.Ldarg_0);
 60                 ilOfCtor.Emit(OpCodes.Newobj, interceptorAttributeType.GetConstructor(new Type[0]));
 61                 ilOfCtor.Emit(OpCodes.Stfld, fieldInterceptor);
 62                 ilOfCtor.Emit(OpCodes.Ret);
 63             }
 64 
 65             // ---- define methods ----
 66 
 67             var methodsOfType = impType.GetMethods(BindingFlags.Public | BindingFlags.Instance);
 68 
 69             string[] ignoreMethodName = new[] { "GetType", "ToString", "GetHashCode", "Equals" };
 70 
 71             foreach (var method in methodsOfType)
 72             {
 73                 //ignore method
 74                 if (ignoreMethodName.Contains(method.Name))
 75                     return;
 76 
 77                 var methodParameterTypes = method.GetParameters().Select(p => p.ParameterType).ToArray();
 78 
 79                 MethodAttributes methodAttributes;
 80 
 81                 if (inheritMode)
 82                     methodAttributes = MethodAttributes.Public | MethodAttributes.Virtual;
 83                 else
 84                     methodAttributes = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual | MethodAttributes.Final;
 85 
 86                 var methodBuilder = typeBuilder.DefineMethod(method.Name, methodAttributes, CallingConventions.Standard, method.ReturnType, methodParameterTypes);
 87 
 88                 var ilMethod = methodBuilder.GetILGenerator();
 89 
 90                 // set local field
 91                 var impObj = ilMethod.DeclareLocal(impType);                //instance of imp object
 92                 var methodName = ilMethod.DeclareLocal(typeof(string));     //instance of method name
 93                 var parameters = ilMethod.DeclareLocal(typeof(object[]));   //instance of parameters
 94                 var result = ilMethod.DeclareLocal(typeof(object));         //instance of result
 95                 LocalBuilder actionAttributeObj = null;
 96 
 97                 //attribute init
 98                 Type actionAttributeType = null;
 99                 if (method.GetCustomAttribute(typeof(ActionBaseAttribute)) != null || impType.GetCustomAttribute(typeof(ActionBaseAttribute)) != null)
100                 {
101                     //method can override class attrubute
102                     if (method.GetCustomAttribute(typeof(ActionBaseAttribute)) != null)
103                     {
104                         actionAttributeType = method.GetCustomAttribute(typeof(ActionBaseAttribute)).GetType();
105                     }
106                     else if (impType.GetCustomAttribute(typeof(ActionBaseAttribute)) != null)
107                     {
108                         actionAttributeType = impType.GetCustomAttribute(typeof(ActionBaseAttribute)).GetType();
109                     }
110 
111                     actionAttributeObj = ilMethod.DeclareLocal(actionAttributeType);
112                     ilMethod.Emit(OpCodes.Newobj, actionAttributeType.GetConstructor(new Type[0]));
113                     ilMethod.Emit(OpCodes.Stloc, actionAttributeObj);
114                 }
115 
116                 //instance imp
117                 ilMethod.Emit(OpCodes.Newobj, impType.GetConstructor(new Type[0]));
118                 ilMethod.Emit(OpCodes.Stloc, impObj);
119 
120                 //if no attribute
121                 if (fieldInterceptor != null || actionAttributeObj != null)
122                 {
123                     ilMethod.Emit(OpCodes.Ldstr, method.Name);
124                     ilMethod.Emit(OpCodes.Stloc, methodName);
125 
126                     ilMethod.Emit(OpCodes.Ldc_I4, methodParameterTypes.Length);
127                     ilMethod.Emit(OpCodes.Newarr, typeof(object));
128                     ilMethod.Emit(OpCodes.Stloc, parameters);
129 
130                     // build the method parameters
131                     for (var j = 0; j < methodParameterTypes.Length; j++)
132                     {
133                         ilMethod.Emit(OpCodes.Ldloc, parameters);
134                         ilMethod.Emit(OpCodes.Ldc_I4, j);
135                         ilMethod.Emit(OpCodes.Ldarg, j + 1);
136                         //box
137                         ilMethod.Emit(OpCodes.Box, methodParameterTypes[j]);
138                         ilMethod.Emit(OpCodes.Stelem_Ref);
139                     }
140                 }
141 
142                 //dynamic proxy action before
143                 if (actionAttributeType != null)
144                 {
145                     //load arguments
146                     ilMethod.Emit(OpCodes.Ldloc, actionAttributeObj);
147                     ilMethod.Emit(OpCodes.Ldloc, methodName);
148                     ilMethod.Emit(OpCodes.Ldloc, parameters);
149                     ilMethod.Emit(OpCodes.Call, actionAttributeType.GetMethod("Before"));
150                 }
151 
152                 if (interceptorAttributeType != null)
153                 {
154                     //load arguments
155                     ilMethod.Emit(OpCodes.Ldarg_0);//this
156                     ilMethod.Emit(OpCodes.Ldfld, fieldInterceptor);
157                     ilMethod.Emit(OpCodes.Ldloc, impObj);
158                     ilMethod.Emit(OpCodes.Ldloc, methodName);
159                     ilMethod.Emit(OpCodes.Ldloc, parameters);
160                     // call Invoke() method of Interceptor
161                     ilMethod.Emit(OpCodes.Callvirt, interceptorAttributeType.GetMethod("Invoke"));
162                 }
163                 else
164                 {
165                     //direct call method
166                     if (method.ReturnType == typeof(void) && actionAttributeType == null)
167                     {
168                         ilMethod.Emit(OpCodes.Ldnull);
169                     }
170 
171                     ilMethod.Emit(OpCodes.Ldloc, impObj);
172                     for (var j = 0; j < methodParameterTypes.Length; j++)
173                     {
174                         ilMethod.Emit(OpCodes.Ldarg, j + 1);
175                     }
176                     ilMethod.Emit(OpCodes.Callvirt, impType.GetMethod(method.Name));
177                     //box
178                     if (actionAttributeType != null)
179                     {
180                         if (method.ReturnType != typeof(void))
181                             ilMethod.Emit(OpCodes.Box, method.ReturnType);
182                         else
183                             ilMethod.Emit(OpCodes.Ldnull);
184                     }
185                 }
186 
187                 //dynamic proxy action after
188                 if (actionAttributeType != null)
189                 {
190                     ilMethod.Emit(OpCodes.Stloc, result);
191                     //load arguments
192                     ilMethod.Emit(OpCodes.Ldloc, actionAttributeObj);
193                     ilMethod.Emit(OpCodes.Ldloc, methodName);
194                     ilMethod.Emit(OpCodes.Ldloc, result);
195                     ilMethod.Emit(OpCodes.Call, actionAttributeType.GetMethod("After"));
196                 }
197 
198                 // pop the stack if return void
199                 if (method.ReturnType == typeof(void))
200                 {
201                     ilMethod.Emit(OpCodes.Pop);
202                 }
203                 else
204                 {
205                     //unbox,if direct invoke,no box
206                     if (fieldInterceptor != null || actionAttributeObj != null)
207                     {
208                         if (method.ReturnType.IsValueType)
209                             ilMethod.Emit(OpCodes.Unbox_Any, method.ReturnType);
210                         else
211                             ilMethod.Emit(OpCodes.Castclass, method.ReturnType);
212                     }
213                 }
214                 // complete
215                 ilMethod.Emit(OpCodes.Ret);
216             }
217         }
218     }
DynamicProxy

  裏面實現了兩種代理方式,一種是 面向接口實現 的方式,另外一種是 繼承重寫 的方式。

  可是繼承重寫的方式須要把業務類的全部方法寫成virtual虛方法,動態類會重寫該方法。

  咱們從上一節的Demo中獲取到運行時生成的代理類dll,用ILSpy反編譯查看源代碼:

  

  能夠看到,咱們的代理類分別調用了咱們特性標籤中的各項方法。

  核心代碼分析(源代碼在上面摺疊部位已經貼出):

  

  解釋:若是該方法存在Action標籤,那麼加載 action 標籤實例化對象,加載參數,執行Before方法;若是該方法存在Interceptor標籤,那麼使用類字段this._interceptor調用該標籤的Invoke方法。

  

  解釋:若是面的Interceptor特性標籤不存在,那麼會加載當前掃描的方法對應的參數,直接調用方法;若是Action標籤存在,則將剛纔調用的結果包裝成object對象傳遞到After方法中。

  這裏若是目標參數是object類型,而實際參數是直接調用返回的明確的值類型,須要進行裝箱操做,不然運行時報調用內存錯誤異常。

  

  解釋:若是返回值是void類型,則直接結束並返回結果;若是返回值是值類型,則須要手動拆箱操做,若是是引用類型,那麼須要類型轉換操做。

  IL實現的細節,這裏不作重點討論。

【系統測試】

   1.接口實現方式,Api測試(各類標籤使用方式對應的不一樣類型的方法調用):

  

  結論:對於上述窮舉的類型,各類標籤使用方式皆成功打出了日誌;

  2.繼承方式,Api測試(各類標籤使用方式對應的不一樣類型的方法調用):

  

  結論:繼承方式和接口實現方式的效果是同樣的,只是方法上須要不一樣的實現調整;

  3.直接調用三個方法百萬次性能結果:

  

  結論:直接調用三個方法百萬次調用耗時 58ms

  4.使用實現接口方式三個方法百萬次調用結果

  

  結論:結果見上圖,須要注意是三個方法百萬次調用,也就是300w次的方法調用

  5.使用繼承方式三個方法百萬次調用結果

  

  結論:結果見上圖,須要注意是三個方法百萬次調用,也就是300w次的方法調用

  事實證實,IL Emit的實現方式性能仍是很高的。

  綜合分析:

  經過各類的調用分析,能夠看出使用代理之後和原生方法調用相比性能損耗在哪裏。性能差距最大的,也是耗時最多的實現方式就是添加了全類方法代理並且是使用Invoke進行全方法切面方式。該方式耗時的緣由是使用了反射Invoke的方法。

  直接添加Action代理類實現 Before和After的方式和原生差距不大,主要損耗在After觸發時的拆裝箱上。

  綜上分析,咱們使用的時候,儘可能針對性地對某一個方法進行AOP注入,而儘可能不要全類方法進行AOP注入。

【總結】

  經過本身實現一個AOP的動態注入框架,對Emit有了更加深刻的瞭解,最重要的是,對CLR IL代碼的執行過程有了必定的認知,受益不淺。

  該方法在使用的過程當中也發現了問題,好比有ref和out類型的參數時,會出現問題,須要後續繼續改進

  本文的源代碼已託管在GitHub上,又須要能夠自行拿取(順手Star哦~):https://github.com/sevenTiny/CodeArts (能夠訪問下面連接獲取.Net Standard 跨平臺版本代碼)

  最新SevenTiny.Bantina.Aop組件代碼(跨平臺):https://github.com/sevenTiny/SevenTiny.Bantina

  該代碼的位置在 CodeArts.CSharp 分區下

  

  VS打開後,能夠在 EmitDynamicProxy 分區下找到;本博客全部的測試項目都在項目中能夠找到。

  

  再次放上源代碼地址,供一塊兒學習的朋友參考,但願能幫助到你:https://github.com/sevenTiny/CodeArts (能夠訪問下面連接獲取.Net Standard 跨平臺版本代碼)

  最新SevenTiny.Bantina.Aop組件代碼(跨平臺):https://github.com/sevenTiny/SevenTiny.Bantina

相關文章
相關標籤/搜索