国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 編程 > C# > 正文

AOP從靜態(tài)代理到動(dòng)態(tài)代理(Emit實(shí)現(xiàn))詳解

2020-01-24 00:15:06
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

【前言】

AOP(Aspect Orient Programming),我們一般稱為面向方面(切面)編程,作為面向?qū)ο蟮囊环N補(bǔ)充,用于處理系統(tǒng)中分布于各個(gè)模塊的橫切關(guān)注點(diǎn),比如事務(wù)管理、日志、緩存等等。AOP實(shí)現(xiàn)的關(guān)鍵在于AOP框架自動(dòng)創(chuàng)建的AOP代理,AOP代理主要分為靜態(tài)代理和動(dòng)態(tài)代理,靜態(tài)代理的代表為AspectJ;而動(dòng)態(tài)代理則以Spring AOP為代表。

何為切面?

一個(gè)和業(yè)務(wù)沒(méi)有任何耦合相關(guān)的代碼段,諸如:調(diào)用日志,發(fā)送郵件,甚至路由分發(fā)。一切能為代碼所有且能和代碼充分解耦的代碼都可以作為一個(gè)業(yè)務(wù)代碼的切面。

我們?yōu)槭裁匆狝OP?

那我們從一個(gè)場(chǎng)景舉例說(shuō)起:

如果想要采集用戶操作行為,我們需要掌握用戶調(diào)用的每一個(gè)接口的信息。這時(shí)候的我們要怎么做?

如果不采用AOP技術(shù),也是最簡(jiǎn)單的,所有方法體第一句話先調(diào)用一個(gè)日志接口將方法信息傳遞記錄。

有何問(wèn)題?

實(shí)現(xiàn)業(yè)務(wù)沒(méi)有任何問(wèn)題,但是隨之而來(lái)的是代碼臃腫不堪,難以調(diào)整維護(hù)的諸多問(wèn)題(可自行腦補(bǔ))。

如果我們采用了AOP技術(shù),我們就可以在系統(tǒng)啟動(dòng)的地方將所有將要采集日志的類(lèi)注入,每一次調(diào)用方法前,AOP框架會(huì)自動(dòng)調(diào)用我們的日志代碼。

是不是省去了很多重復(fù)無(wú)用的勞動(dòng)?代碼也將變得非常好維護(hù)(有朝一日不需要了,只需將切面代碼注釋掉即可)

接下來(lái)我們看看AOP框架的工作原理以及實(shí)過(guò)程。

【實(shí)現(xiàn)思路】

AOP框架呢,一般通過(guò)靜態(tài)代理和動(dòng)態(tài)代理兩種實(shí)現(xiàn)方式。

  

何為靜態(tài)代理?

靜態(tài)代理,又叫編譯時(shí)代理,就是在編譯的時(shí)候,已經(jīng)存在代理類(lèi),運(yùn)行時(shí)直接調(diào)用的方式。說(shuō)的通俗一點(diǎn),就是自己手動(dòng)寫(xiě)代碼實(shí)現(xiàn)代理類(lèi)的方式。

我們通過(guò)一個(gè)例子來(lái)展現(xiàn)一下靜態(tài)代理的實(shí)現(xiàn)過(guò)程:

我們這里有一個(gè)業(yè)務(wù)類(lèi),里面有方法Test(),我們要在Test調(diào)用前和調(diào)用后分別輸出日志。

我們既然要將Log當(dāng)作一個(gè)切面,我們肯定不能去動(dòng)原有的業(yè)務(wù)代碼,那樣也違反了面向?qū)ο笤O(shè)計(jì)之開(kāi)閉原則。

那么我們要怎么做呢?我們定義一個(gè)新類(lèi) BusinessProxy 去包裝一下這個(gè)類(lèi)。為了便于在多個(gè)方法的時(shí)候區(qū)分和辨認(rèn),方法也叫 Test()

這樣,我們?nèi)绻谒械腂usiness類(lèi)中的方法都添加Log,我們就在BusinessProxy代理類(lèi)中添加對(duì)應(yīng)的方法去包裝。既不破壞原有邏輯,又可以實(shí)現(xiàn)前后日志的功能。

當(dāng)然,我們可以有更優(yōu)雅的實(shí)現(xiàn)方式:

我們可以定義代理類(lèi),繼承自業(yè)務(wù)類(lèi)。將業(yè)務(wù)類(lèi)中的方法定義為虛方法。那么我們可以重寫(xiě)父類(lèi)的方法并且在加入日志以后再調(diào)用父類(lèi)的原方法。

當(dāng)然,我們還有更加優(yōu)雅的實(shí)現(xiàn)方式:

我們可以使用發(fā)射的技術(shù),寫(xiě)一個(gè)通用的Invoke方法,所有的方法都可以通過(guò)該方法調(diào)用。

我們這樣便實(shí)現(xiàn)了一個(gè)靜態(tài)代理。

那我們既然有了靜態(tài)代理,為什么又要有動(dòng)態(tài)代理呢?

我們仔細(xì)回顧靜態(tài)代理的實(shí)現(xiàn)過(guò)程。我們要在所有的方法中添加切面,我們就要在代理類(lèi)中重寫(xiě)所有的業(yè)務(wù)方法。更有甚者,我們有N個(gè)業(yè)務(wù)類(lèi),就要定義N個(gè)代理類(lèi)。這是很龐大的工作量。

這就是動(dòng)態(tài)代理出現(xiàn)的背景,相比都可以猜得到,動(dòng)態(tài)代理就是將這一系列繁瑣的步驟自動(dòng)化,讓程序自動(dòng)為我們生成代理類(lèi)。

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

動(dòng)態(tài)代理,又成為運(yùn)行時(shí)代理。在程序運(yùn)行的過(guò)程中,調(diào)用了生成代理類(lèi)的代碼,將自動(dòng)生成業(yè)務(wù)類(lèi)的代理類(lèi)。不需要我們手共編寫(xiě),極高的提高了工作效率和調(diào)整了程序員的心態(tài)。

原理不必多說(shuō),就是動(dòng)態(tài)生成靜態(tài)代理的代碼。我們要做的,就是選用一種生成代碼的方式去生成。

今天我分享一個(gè)簡(jiǎn)單的AOP框架,代碼使用Emit生成。當(dāng)然,Emit 代碼的寫(xiě)法不是今天要講的主要內(nèi)容,需要提前去學(xué)習(xí)。

先說(shuō)效果:

定義一個(gè)Action特性類(lèi) ActionAttribute繼承自 ActionBaseAttribute,里面在Before和After方法中輸出兩條日志;

定義一個(gè)Action特性類(lèi)InterceptorAttribute 繼承自InterceptorBaseAttribute,里面捕獲了方法調(diào)用異常,以及執(zhí)行前后分別輸出日志;

然后定義一個(gè)業(yè)務(wù)類(lèi)BusinessClass 實(shí)現(xiàn)了IBusinessClass 接口,定義了各種類(lèi)型的方法

多余的方法不貼圖了。

我們把上面定義的方法調(diào)用切面標(biāo)簽放在業(yè)務(wù)類(lèi)上,表示該類(lèi)下所有的方法都執(zhí)行異常過(guò)濾;

我們把Action特性放在Test方法上,表明要在 Test() 方法的 Before 和 After 調(diào)用時(shí)記錄日志;

我們定義測(cè)試類(lèi):

調(diào)用一下試試:

可見(jiàn),全類(lèi)方法標(biāo)簽 Interceptor 在 Test 和 GetInt 方法調(diào)用前后都打出了對(duì)應(yīng)的日志;

Action方法標(biāo)簽只在 Test 方法上做了標(biāo)記,那么Test方法 Before 和 After 執(zhí)行時(shí)打出了日志;

【實(shí)現(xiàn)過(guò)程】

實(shí)現(xiàn)的思路在上面已經(jīng)有詳細(xì)的講解,可以參考靜態(tài)代理的實(shí)現(xiàn)思路。

我們定義一個(gè)動(dòng)態(tài)代理生成類(lèi) DynamicProxy,用于原業(yè)務(wù)代碼的掃描和代理類(lèi)代碼的生成;

定義兩個(gè)過(guò)濾器標(biāo)簽,ActionBaseAttribute,提供Before和After切面方法;InterceptorBaseAttribute,提供 Invoke “全調(diào)用”包裝的切面方法;

Before可以獲取到當(dāng)前調(diào)用的方法和參數(shù)列表,After可以獲取到當(dāng)前方法調(diào)用以后的結(jié)果。

Invoke 可以拿到當(dāng)前調(diào)用的對(duì)象和方法名,參數(shù)列表。在這里進(jìn)行反射動(dòng)態(tài)調(diào)用。

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

代理生成類(lèi)采用Emit的方式生成運(yùn)行時(shí)IL代碼。

先把代碼放在這里:

public class DynamicProxy { public static TInterface CreateProxyOfRealize<TInterface, TImp>() where TImp : class, new() where TInterface : class {  return Invoke<TInterface, TImp>(); } public static TProxyClass CreateProxyOfInherit<TProxyClass>() where TProxyClass : class, new() {  return Invoke<TProxyClass, TProxyClass>(true); } private static TInterface Invoke<TInterface, TImp>(bool inheritMode = false) where TImp : class, new() where TInterface : class {  var impType = typeof(TImp);  string nameOfAssembly = impType.Name + "ProxyAssembly";  string nameOfModule = impType.Name + "ProxyModule";  string nameOfType = impType.Name + "Proxy";  var assemblyName = new AssemblyName(nameOfAssembly);  var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);  var moduleBuilder = assembly.DefineDynamicModule(nameOfModule);  //var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);  //var moduleBuilder = assembly.DefineDynamicModule(nameOfModule, nameOfAssembly + ".dll");  TypeBuilder typeBuilder;  if (inheritMode)  typeBuilder = moduleBuilder.DefineType(nameOfType, TypeAttributes.Public, impType);  else  typeBuilder = moduleBuilder.DefineType(nameOfType, TypeAttributes.Public, null, new[] { typeof(TInterface) });  InjectInterceptor<TImp>(typeBuilder, impType.GetCustomAttribute(typeof(InterceptorBaseAttribute))?.GetType(), inheritMode);  var t = typeBuilder.CreateType();  //assembly.Save(nameOfAssembly + ".dll");  return Activator.CreateInstance(t) as TInterface; } private static void InjectInterceptor<TImp>(TypeBuilder typeBuilder, Type interceptorAttributeType, bool inheritMode = false) {  var impType = typeof(TImp);  // ---- define fields ----  FieldBuilder fieldInterceptor = null;  if (interceptorAttributeType != null)  {  fieldInterceptor = typeBuilder.DefineField("_interceptor", interceptorAttributeType, FieldAttributes.Private);  }  // ---- define costructors ----  if (interceptorAttributeType != null)  {  var constructorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, null);  var ilOfCtor = constructorBuilder.GetILGenerator();  ilOfCtor.Emit(OpCodes.Ldarg_0);  ilOfCtor.Emit(OpCodes.Newobj, interceptorAttributeType.GetConstructor(new Type[0]));  ilOfCtor.Emit(OpCodes.Stfld, fieldInterceptor);  ilOfCtor.Emit(OpCodes.Ret);  }  // ---- define methods ----  var methodsOfType = impType.GetMethods(BindingFlags.Public | BindingFlags.Instance);  string[] ignoreMethodName = new[] { "GetType", "ToString", "GetHashCode", "Equals" };  foreach (var method in methodsOfType)  {  //ignore method  if (ignoreMethodName.Contains(method.Name))   return;  var methodParameterTypes = method.GetParameters().Select(p => p.ParameterType).ToArray();  MethodAttributes methodAttributes;  if (inheritMode)   methodAttributes = MethodAttributes.Public | MethodAttributes.Virtual;  else   methodAttributes = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual | MethodAttributes.Final;  var methodBuilder = typeBuilder.DefineMethod(method.Name, methodAttributes, CallingConventions.Standard, method.ReturnType, methodParameterTypes);  var ilMethod = methodBuilder.GetILGenerator();  // set local field  var impObj = ilMethod.DeclareLocal(impType);  //instance of imp object  var methodName = ilMethod.DeclareLocal(typeof(string)); //instance of method name  var parameters = ilMethod.DeclareLocal(typeof(object[])); //instance of parameters  var result = ilMethod.DeclareLocal(typeof(object));  //instance of result  LocalBuilder actionAttributeObj = null;  //attribute init  Type actionAttributeType = null;  if (method.GetCustomAttribute(typeof(ActionBaseAttribute)) != null || impType.GetCustomAttribute(typeof(ActionBaseAttribute)) != null)  {   //method can override class attrubute   if (method.GetCustomAttribute(typeof(ActionBaseAttribute)) != null)   {   actionAttributeType = method.GetCustomAttribute(typeof(ActionBaseAttribute)).GetType();   }   else if (impType.GetCustomAttribute(typeof(ActionBaseAttribute)) != null)   {   actionAttributeType = impType.GetCustomAttribute(typeof(ActionBaseAttribute)).GetType();   }   actionAttributeObj = ilMethod.DeclareLocal(actionAttributeType);   ilMethod.Emit(OpCodes.Newobj, actionAttributeType.GetConstructor(new Type[0]));   ilMethod.Emit(OpCodes.Stloc, actionAttributeObj);  }  //instance imp  ilMethod.Emit(OpCodes.Newobj, impType.GetConstructor(new Type[0]));  ilMethod.Emit(OpCodes.Stloc, impObj);  //if no attribute  if (fieldInterceptor != null || actionAttributeObj != null)  {   ilMethod.Emit(OpCodes.Ldstr, method.Name);   ilMethod.Emit(OpCodes.Stloc, methodName);   ilMethod.Emit(OpCodes.Ldc_I4, methodParameterTypes.Length);   ilMethod.Emit(OpCodes.Newarr, typeof(object));   ilMethod.Emit(OpCodes.Stloc, parameters);   // build the method parameters   for (var j = 0; j < methodParameterTypes.Length; j++)   {   ilMethod.Emit(OpCodes.Ldloc, parameters);   ilMethod.Emit(OpCodes.Ldc_I4, j);   ilMethod.Emit(OpCodes.Ldarg, j + 1);   //box   ilMethod.Emit(OpCodes.Box, methodParameterTypes[j]);   ilMethod.Emit(OpCodes.Stelem_Ref);   }  }  //dynamic proxy action before  if (actionAttributeType != null)  {   //load arguments   ilMethod.Emit(OpCodes.Ldloc, actionAttributeObj);   ilMethod.Emit(OpCodes.Ldloc, methodName);   ilMethod.Emit(OpCodes.Ldloc, parameters);   ilMethod.Emit(OpCodes.Call, actionAttributeType.GetMethod("Before"));  }  if (interceptorAttributeType != null)  {   //load arguments   ilMethod.Emit(OpCodes.Ldarg_0);//this   ilMethod.Emit(OpCodes.Ldfld, fieldInterceptor);   ilMethod.Emit(OpCodes.Ldloc, impObj);   ilMethod.Emit(OpCodes.Ldloc, methodName);   ilMethod.Emit(OpCodes.Ldloc, parameters);   // call Invoke() method of Interceptor   ilMethod.Emit(OpCodes.Callvirt, interceptorAttributeType.GetMethod("Invoke"));  }  else  {   //direct call method   if (method.ReturnType == typeof(void) && actionAttributeType == null)   {   ilMethod.Emit(OpCodes.Ldnull);   }   ilMethod.Emit(OpCodes.Ldloc, impObj);   for (var j = 0; j < methodParameterTypes.Length; j++)   {   ilMethod.Emit(OpCodes.Ldarg, j + 1);   }   ilMethod.Emit(OpCodes.Callvirt, impType.GetMethod(method.Name));   //box   if (actionAttributeType != null)   {   if (method.ReturnType != typeof(void))    ilMethod.Emit(OpCodes.Box, method.ReturnType);   else    ilMethod.Emit(OpCodes.Ldnull);   }  }  //dynamic proxy action after  if (actionAttributeType != null)  {   ilMethod.Emit(OpCodes.Stloc, result);   //load arguments   ilMethod.Emit(OpCodes.Ldloc, actionAttributeObj);   ilMethod.Emit(OpCodes.Ldloc, methodName);   ilMethod.Emit(OpCodes.Ldloc, result);   ilMethod.Emit(OpCodes.Call, actionAttributeType.GetMethod("After"));  }  // pop the stack if return void  if (method.ReturnType == typeof(void))  {   ilMethod.Emit(OpCodes.Pop);  }  else  {   //unbox,if direct invoke,no box   if (fieldInterceptor != null || actionAttributeObj != null)   {   if (method.ReturnType.IsValueType)    ilMethod.Emit(OpCodes.Unbox_Any, method.ReturnType);   else    ilMethod.Emit(OpCodes.Castclass, method.ReturnType);   }  }  // complete  ilMethod.Emit(OpCodes.Ret);  } } }

里面實(shí)現(xiàn)了兩種代理方式,一種是 面向接口實(shí)現(xiàn) 的方式,另一種是 繼承重寫(xiě) 的方式。

但是繼承重寫(xiě)的方式需要把業(yè)務(wù)類(lèi)的所有方法寫(xiě)成virtual虛方法,動(dòng)態(tài)類(lèi)會(huì)重寫(xiě)該方法。

我們從上一節(jié)的Demo中獲取到運(yùn)行時(shí)生成的代理類(lèi)dll,用ILSpy反編譯查看源代碼:

可以看到,我們的代理類(lèi)分別調(diào)用了我們特性標(biāo)簽中的各項(xiàng)方法。

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

解釋:如果該方法存在Action標(biāo)簽,那么加載 action 標(biāo)簽實(shí)例化對(duì)象,加載參數(shù),執(zhí)行Before方法;如果該方法存在Interceptor標(biāo)簽,那么使用類(lèi)字段this._interceptor調(diào)用該標(biāo)簽的Invoke方法。

解釋:如果面的Interceptor特性標(biāo)簽不存在,那么會(huì)加載當(dāng)前掃描的方法對(duì)應(yīng)的參數(shù),直接調(diào)用方法;如果Action標(biāo)簽存在,則將剛才調(diào)用的結(jié)果包裝成object對(duì)象傳遞到After方法中。

這里如果目標(biāo)參數(shù)是object類(lèi)型,而實(shí)際參數(shù)是直接調(diào)用返回的明確的值類(lèi)型,需要進(jìn)行裝箱操作,否則運(yùn)行時(shí)報(bào)調(diào)用內(nèi)存錯(cuò)誤異常。

解釋:如果返回值是void類(lèi)型,則直接結(jié)束并返回結(jié)果;如果返回值是值類(lèi)型,則需要手動(dòng)拆箱操作,如果是引用類(lèi)型,那么需要類(lèi)型轉(zhuǎn)換操作。

IL實(shí)現(xiàn)的細(xì)節(jié),這里不做重點(diǎn)討論。

【系統(tǒng)測(cè)試】  

1.接口實(shí)現(xiàn)方式,Api測(cè)試(各種標(biāo)簽使用方式對(duì)應(yīng)的不同類(lèi)型的方法調(diào)用):

結(jié)論:對(duì)于上述窮舉的類(lèi)型,各種標(biāo)簽使用方式皆成功打出了日志;

2.繼承方式,Api測(cè)試(各種標(biāo)簽使用方式對(duì)應(yīng)的不同類(lèi)型的方法調(diào)用):

結(jié)論:繼承方式和接口實(shí)現(xiàn)方式的效果是一樣的,只是方法上需要不同的實(shí)現(xiàn)調(diào)整;

3.直接調(diào)用三個(gè)方法百萬(wàn)次性能結(jié)果:

結(jié)論:直接調(diào)用三個(gè)方法百萬(wàn)次調(diào)用耗時(shí) 58ms

4.使用實(shí)現(xiàn)接口方式三個(gè)方法百萬(wàn)次調(diào)用結(jié)果

結(jié)論:結(jié)果見(jiàn)上圖,需要注意是三個(gè)方法百萬(wàn)次調(diào)用,也就是300w次的方法調(diào)用

5.使用繼承方式三個(gè)方法百萬(wàn)次調(diào)用結(jié)果

結(jié)論:結(jié)果見(jiàn)上圖,需要注意是三個(gè)方法百萬(wàn)次調(diào)用,也就是300w次的方法調(diào)用

事實(shí)證明,IL Emit的實(shí)現(xiàn)方式性能還是很高的。

綜合分析:

通過(guò)各種的調(diào)用分析,可以看出使用代理以后和原生方法調(diào)用相比性能損耗在哪里。性能差距最大的,也是耗時(shí)最多的實(shí)現(xiàn)方式就是添加了全類(lèi)方法代理而且是使用Invoke進(jìn)行全方法切面方式。該方式耗時(shí)的原因是使用了反射Invoke的方法。

直接添加Action代理類(lèi)實(shí)現(xiàn) Before和After的方式和原生差距不大,主要損耗在After觸發(fā)時(shí)的拆裝箱上。

綜上分析,我們使用的時(shí)候,盡量針對(duì)性地對(duì)某一個(gè)方法進(jìn)行AOP注入,而盡量不要全類(lèi)方法進(jìn)行AOP注入。

【總結(jié)】

通過(guò)自己實(shí)現(xiàn)一個(gè)AOP的動(dòng)態(tài)注入框架,對(duì)Emit有了更加深入的了解,最重要的是,對(duì)CLR IL代碼的執(zhí)行過(guò)程有了一定的認(rèn)知,受益匪淺。

該方法在使用的過(guò)程中也發(fā)現(xiàn)了問(wèn)題,比如有ref和out類(lèi)型的參數(shù)時(shí),會(huì)出現(xiàn)問(wèn)題,需要后續(xù)繼續(xù)改進(jìn)

本文的源代碼已托管在GitHub上,又需要可以自行拿取(順手Star哦~):https://github.com/sevenTiny/CodeArts (本地下載

該代碼的位置在 CodeArts.CSharp 分區(qū)下

VS打開(kāi)后,可以在 EmitDynamicProxy 分區(qū)下找到;本博客所有的測(cè)試項(xiàng)目都在項(xiàng)目中可以找到。

再次放上源代碼地址,供一起學(xué)習(xí)的朋友參考,希望能幫助到你:https://github.com/sevenTiny/CodeArts (本地下載

總結(jié)

以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)武林網(wǎng)的支持。

發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 南江县| 莫力| 龙岩市| 洪洞县| 图片| 五寨县| 岑溪市| 嘉义县| 丘北县| 丽水市| 梧州市| 正宁县| 郁南县| 尼木县| 阿克| 南皮县| 钟山县| 北辰区| 汽车| 和政县| 龙门县| 蒙阴县| 冕宁县| 新建县| 墨玉县| 龙山县| 曲沃县| 上饶市| 枣阳市| 伊春市| 灯塔市| 四子王旗| 沛县| 广昌县| 樟树市| 攀枝花市| 海口市| 张掖市| 绍兴县| 文成县| 涞源县|