大家一起Aop


一、前言

  1.在項目中無處不充斥着記錄日志的代碼,各種try catch,實在是有點看着不爽。這不,果斷要想法子偷個懶兒。

二、摘要

鄙人不才,先總結一下個人想到的可實現AOP的幾種思路:

  1.通過繼承特定實例,重寫虛方法(C#中如virtual、override方法),動態構建一個該實例的子類,進行調用。

  2.通過實現特定實例上的接口,動態構建一個該接口的實現類,切入AOP代碼,內部包裹特定實例的方法。

  3.最簡單的一種方式,通過給特定實例繼承MarshalByRefObject類,並且用繼承RealProxy的代理類進行構造包裹。

代碼比較少,有些Emit基礎的童鞋應該很容易看懂,接下去直接上核心代碼。

三、繼承類模式

  1 if (!method.IsPublic || !method.IsVirtual/*非虛方法無法重寫*/|| method.IsFinal /*Final方法無法重寫,如interface的實現方法標記為 virtual final*/ || IsObjectMethod(method)) return;
  2 
  3             const MethodAttributes methodattributes = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual;
  4             Type[] paramTypes = method.GetParameters().Select(ent => ent.ParameterType).ToArray();
  5             MethodBuilder mb = _typeBuilder.DefineMethod(method.Name, methodattributes, method.ReturnType, paramTypes);
  6             ILGenerator il = mb.GetILGenerator();
  7 
  8             #region 初始化本地變量和返回值
  9             //加載所有參數到本地object[],實例方法第一個參數是this,已排除
 10             LoadArgsIntoLocalField(il, paramTypes);
 11 
 12             //如果有返回值,定義返回值變量
 13             bool isReturnVoid = method.ReturnType == typeof(void);
 14             LocalBuilder result = null;
 15             if (!isReturnVoid)
 16                 result = il.DeclareLocal(method.ReturnType);
 17 
 18             //定義MethodInfo變量,下面會用到(傳遞到Aop的上下文中)
 19             var methodInfo = il.DeclareLocal(typeof(MethodBase));
 20             il.Emit(OpCodes.Call, typeof(MethodBase).GetMethod("GetCurrentMethod", Type.EmptyTypes));
 21             il.Emit(OpCodes.Stloc, methodInfo);
 22             #endregion
 23 
 24             #region 初始化AspectContext
 25             Type contextType = typeof(AspectContext);
 26             var context = il.DeclareLocal(contextType);
 27             ConstructorInfo info = contextType.GetConstructor(Type.EmptyTypes);
 28             il.Emit(OpCodes.Newobj, info);
 29             il.Emit(OpCodes.Stloc, context);
 30             #endregion
 31 
 32             #region 給AspectContext的參數值屬性ParameterArgs,MethodInfo賦值
 33             il.Emit(OpCodes.Ldloc, context);
 34             il.Emit(OpCodes.Ldloc_0);
 35             il.Emit(OpCodes.Call, contextType.GetMethod("set_ParameterArgs"));
 36 
 37             il.Emit(OpCodes.Ldloc, context);
 38             il.Emit(OpCodes.Ldloc, methodInfo);
 39             il.Emit(OpCodes.Call, contextType.GetMethod("set_MethodInfo"));
 40             #endregion
 41 
 42             AspectAttribute[] attrs = GetAspectAttributes(method);
 43             int attrLen = attrs.Length;
 44             LocalBuilder[] lbs = new LocalBuilder[attrLen];
 45             MethodInfo[] endInvokeMethods = new MethodInfo[attrLen];
 46             MethodInfo[] invokingExceptionMethods = new MethodInfo[attrLen];
 47 
 48             #region 初始化標記的切面對象,並調用切面對象的BeforeInvoke方法
 49             for (int i = 0; i < attrLen; i++)
 50             {
 51                 var tmpAttrType = attrs[i].GetType();
 52                 var tmpAttr = il.DeclareLocal(tmpAttrType);
 53                 ConstructorInfo tmpAttrCtor = tmpAttrType.GetConstructor(Type.EmptyTypes);
 54 
 55                 il.Emit(OpCodes.Newobj, tmpAttrCtor);
 56                 il.Emit(OpCodes.Stloc, tmpAttr);
 57 
 58                 var beforeInvokeMethod = tmpAttrType.GetMethod("BeforeInvoke");
 59                 endInvokeMethods[i] = tmpAttrType.GetMethod("AfterInvoke");
 60                 invokingExceptionMethods[i] = tmpAttrType.GetMethod("InvokingException");
 61 
 62                 il.Emit(OpCodes.Ldloc, tmpAttr);
 63                 il.Emit(OpCodes.Ldloc, context);
 64                 il.Emit(OpCodes.Callvirt, beforeInvokeMethod);
 65                 il.Emit(OpCodes.Nop);
 66 
 67                 lbs[i] = tmpAttr;
 68             }
 69             #endregion
 70 
 71             //try
 72             il.BeginExceptionBlock();
 73 
 74             #region 調用實現方法
 75             if (!method.IsAbstract)
 76             {
 77                 //類對象,參數值依次入棧
 78                 for (int i = 0; i <= paramTypes.Length; i++)
 79                     il.Emit(OpCodes.Ldarg, i);
 80 
 81                 //調用基類的方法
 82                 il.Emit(OpCodes.Call, method);
 83 
 84                 if (!isReturnVoid)
 85                 {
 86                     il.Emit(OpCodes.Stloc, result);
 87 
 88                     //
 89                     il.Emit(OpCodes.Ldloc, context);
 90                     il.Emit(OpCodes.Ldloc, result);
 91                     if (method.ReturnType.IsValueType)
 92                         il.Emit(OpCodes.Box, method.ReturnType);
 93                     il.Emit(OpCodes.Call, contextType.GetMethod("set_ReturnObj"));
 94                 }
 95             }
 96             #endregion
 97 
 98             //catch
 99             il.BeginCatchBlock(typeof(Exception));
100             var exception = il.DeclareLocal(typeof(Exception));
101             il.Emit(OpCodes.Stloc, exception);
102 
103             #region 初始化ExceptionContext
104             var exceptionContentType = typeof(ExceptionContext);
105             var exceptionContent = il.DeclareLocal(exceptionContentType);
106             var exceptionContentCtor = exceptionContentType.GetConstructor(Type.EmptyTypes);
107             il.Emit(OpCodes.Newobj, exceptionContentCtor);
108             il.Emit(OpCodes.Stloc, exceptionContent);
109             #endregion
110 
111             #region 給ExceptionContext的參數值屬性ParameterArgs,MethodInfo,ExceptionInfo,賦值
112             il.Emit(OpCodes.Ldloc, exceptionContent);
113             il.Emit(OpCodes.Ldloc_0);
114             il.Emit(OpCodes.Call, exceptionContentType.GetMethod("set_ParameterArgs"));
115 
116             il.Emit(OpCodes.Ldloc, exceptionContent);
117             il.Emit(OpCodes.Ldloc, methodInfo);
118             il.Emit(OpCodes.Call, exceptionContentType.GetMethod("set_MethodInfo"));
119 
120             il.Emit(OpCodes.Ldloc, exceptionContent);
121             il.Emit(OpCodes.Ldloc, exception);
122             il.Emit(OpCodes.Call, exceptionContentType.GetMethod("set_ExceptionInfo"));
123             #endregion
124 
125             #region 調用切面對象的InvokingException方法
126             for (int i = 0; i < attrLen; i++)
127             {
128                 il.Emit(OpCodes.Ldloc, lbs[i]);
129                 il.Emit(OpCodes.Ldloc, exceptionContent);
130                 il.Emit(OpCodes.Callvirt, invokingExceptionMethods[i]);
131                 il.Emit(OpCodes.Nop);
132             }
133             #endregion
134             //try end
135             il.EndExceptionBlock();
136 
137             #region 調用切面對象的AfterInvoke方法
138             for (int i = 0; i < attrLen; i++)
139             {
140                 il.Emit(OpCodes.Ldloc, lbs[i]);
141                 il.Emit(OpCodes.Ldloc, context);
142                 il.Emit(OpCodes.Callvirt, endInvokeMethods[i]);
143                 il.Emit(OpCodes.Nop);
144             }
145             #endregion
146 
147             if (!isReturnVoid)
148                 il.Emit(OpCodes.Ldloc, result);
149 
150             //返回
151             il.Emit(OpCodes.Ret);
繼承類模式

 

該種方式,建立在使用base.XXXMethod(arg1,arg2,...)模式來調用被Aop的對象的業務方法,關鍵點是使用BeginExceptionBlock(),BeginCatchBlock(typeof(Exception)),EndExceptionBlock();來動態創建try catch代碼塊,進行異常處理。

四、實現接口模式

  1 if (!method.IsPublic || IsObjectMethod(method))
  2                 return;
  3 
  4             string methodName = method.Name;
  5 
  6             const MethodAttributes methodattributes = MethodAttributes.Public | MethodAttributes.Virtual;
  7             Type[] paramTypes = method.GetParameters().Select(ent => ent.ParameterType).ToArray();
  8             MethodBuilder methodBuilder = _typeBuilder.DefineMethod(methodName, methodattributes, method.ReturnType, paramTypes.ToArray());
  9             var il = methodBuilder.GetILGenerator();
 10 
 11             #region 初始化本地變量和返回值
 12             //加載所有參數到本地object[]
 13             LoadArgsIntoLocalField(il, paramTypes);
 14 
 15             //如果有返回值,定義返回值變量
 16             bool isReturnVoid = method.ReturnType == typeof(void);
 17             LocalBuilder resultLocal = null;
 18             if (!isReturnVoid)
 19                 resultLocal = il.DeclareLocal(method.ReturnType);
 20 
 21             //定義MethodInfo變量,下面會用到(傳遞到Aop的上下文中)
 22             var methodInfo = il.DeclareLocal(typeof(MethodBase));
 23             il.Emit(OpCodes.Call, typeof(MethodBase).GetMethod("GetCurrentMethod", Type.EmptyTypes));
 24             il.Emit(OpCodes.Stloc, methodInfo);
 25             #endregion
 26 
 27             #region 初始化AspectContext
 28 
 29             Type contextType = typeof(AspectContext);
 30             var context = il.DeclareLocal(contextType);
 31             il.Emit(OpCodes.Newobj, contextType.GetConstructor(Type.EmptyTypes));
 32             il.Emit(OpCodes.Stloc, context);
 33 
 34             #endregion
 35 
 36             #region 給AspectContext的參數值屬性ParameterArgs,MethodInfo賦值
 37             il.Emit(OpCodes.Ldloc, context);
 38             il.Emit(OpCodes.Ldloc_0);
 39             il.Emit(OpCodes.Call, contextType.GetMethod("set_ParameterArgs"));
 40 
 41             il.Emit(OpCodes.Ldloc, context);
 42             il.Emit(OpCodes.Ldloc, methodInfo);
 43             il.Emit(OpCodes.Call, contextType.GetMethod("set_MethodInfo"));
 44             #endregion
 45 
 46             AspectAttribute[] attrs = GetAspectAttributes(method);
 47             int attrLen = attrs.Length;
 48             LocalBuilder[] lbs = new LocalBuilder[attrLen];
 49             MethodInfo[] endInvokeMethods = new MethodInfo[attrLen];
 50             MethodInfo[] invokingExceptionMethods = new MethodInfo[attrLen];
 51 
 52             #region 初始化標記的切面對象,並調用切面對象的BeforeInvoke方法
 53             for (int i = 0; i < attrLen; i++)
 54             {
 55                 var tmpAttrType = attrs[i].GetType();
 56                 var tmpAttr = il.DeclareLocal(tmpAttrType);
 57                 ConstructorInfo tmpAttrCtor = tmpAttrType.GetConstructor(Type.EmptyTypes);
 58 
 59                 il.Emit(OpCodes.Newobj, tmpAttrCtor);
 60                 il.Emit(OpCodes.Stloc, tmpAttr);
 61 
 62                 var beforeInvokeMethod = tmpAttrType.GetMethod("BeforeInvoke");
 63                 endInvokeMethods[i] = tmpAttrType.GetMethod("AfterInvoke");
 64                 invokingExceptionMethods[i] = tmpAttrType.GetMethod("InvokingException");
 65 
 66                 il.Emit(OpCodes.Ldloc, tmpAttr);
 67                 il.Emit(OpCodes.Ldloc, context);
 68                 il.Emit(OpCodes.Callvirt, beforeInvokeMethod);
 69                 il.Emit(OpCodes.Nop);
 70 
 71                 lbs[i] = tmpAttr;
 72             }
 73             #endregion
 74 
 75             il.BeginExceptionBlock();
 76 
 77             #region 調用實現方法
 78             if (!method.IsAbstract)
 79             {
 80                 il.Emit(OpCodes.Ldarg_0);
 81                 il.Emit(OpCodes.Ldfld, _realProxyField);
 82                 for (int i = 0; i < paramTypes.Length; i++)
 83                     il.Emit(OpCodes.Ldarg, i + 1);  //arg_0為當前實例,故不添加到棧。
 84 
 85                 il.Emit(OpCodes.Call, method);
 86 
 87                 if (!isReturnVoid)
 88                 {
 89                     il.Emit(OpCodes.Stloc, resultLocal);
 90 
 91                     //
 92                     il.Emit(OpCodes.Ldloc, context);
 93                     il.Emit(OpCodes.Ldloc, resultLocal);
 94                     if (method.ReturnType.IsValueType)
 95                         il.Emit(OpCodes.Box, method.ReturnType);
 96                     il.Emit(OpCodes.Call, contextType.GetMethod("set_ReturnObj"));
 97                 }
 98             }
 99             #endregion
100 
101             //catch
102             il.BeginCatchBlock(typeof(Exception));
103             var exception = il.DeclareLocal(typeof(Exception));
104             il.Emit(OpCodes.Stloc, exception);
105 
106             #region 初始化ExceptionContext
107             var exceptionContentType = typeof(ExceptionContext);
108             var exceptionContent = il.DeclareLocal(exceptionContentType);
109             var exceptionContentCtor = exceptionContentType.GetConstructor(Type.EmptyTypes);
110             il.Emit(OpCodes.Newobj, exceptionContentCtor);
111             il.Emit(OpCodes.Stloc, exceptionContent);
112             #endregion
113 
114             #region 給ExceptionContext的參數值屬性ParameterArgs,MethodInfo,ExceptionInfo,賦值
115             il.Emit(OpCodes.Ldloc, exceptionContent);
116             il.Emit(OpCodes.Ldloc_0);
117             il.Emit(OpCodes.Call, exceptionContentType.GetMethod("set_ParameterArgs"));
118 
119             il.Emit(OpCodes.Ldloc, exceptionContent);
120             il.Emit(OpCodes.Ldloc, methodInfo);
121             il.Emit(OpCodes.Call, exceptionContentType.GetMethod("set_MethodInfo"));
122 
123             il.Emit(OpCodes.Ldloc, exceptionContent);
124             il.Emit(OpCodes.Ldloc, exception);
125             il.Emit(OpCodes.Call, exceptionContentType.GetMethod("set_ExceptionInfo"));
126             #endregion
127 
128             #region 調用切面對象的InvokingException方法
129             for (int i = 0; i < attrLen; i++)
130             {
131                 il.Emit(OpCodes.Ldloc, lbs[i]);
132                 il.Emit(OpCodes.Ldloc, exceptionContent);
133                 il.Emit(OpCodes.Callvirt, invokingExceptionMethods[i]);
134                 il.Emit(OpCodes.Nop);
135             }
136             #endregion
137 
138             il.EndExceptionBlock();
139 
140             #region 調用切面對象的AfterInvoke方法
141             for (int i = 0; i < attrLen; i++)
142             {
143                 il.Emit(OpCodes.Ldloc, lbs[i]);
144                 il.Emit(OpCodes.Ldloc, context);
145                 il.Emit(OpCodes.Callvirt, endInvokeMethods[i]);
146                 il.Emit(OpCodes.Nop);
147             }
148             #endregion
149 
150             if (!isReturnVoid)
151             {
152                 il.Emit(OpCodes.Ldloc, resultLocal);
153             }
154 
155             il.Emit(OpCodes.Ret);
實現接口模式

 

該種方式,與繼承類模式十分類似,唯一的區別是其保存了被Aop的實例到一個全局變量,通過該全局變量進行相應的業務方法調用。

五、通過MarshalByRefObject和RealProxy

 1 public sealed class ProxyMarshalByRefObject<TClass> : RealProxy where TClass : class
 2     {
 3         private readonly MarshalByRefObject _target;
 4 
 5         public ProxyMarshalByRefObject()
 6             : base(typeof(TClass))
 7         {
 8             _target = (MarshalByRefObject)Activator.CreateInstance(typeof(TClass));
 9 
10 #if DEBUG
11             // Get 'ObjRef', for transmission serialization between application domains.
12             ObjRef myObjRef = RemotingServices.Marshal(_target);
13             // Get the 'URI' property of 'ObjRef' and store it.
14             Console.WriteLine("URI :{0}", myObjRef.URI);
15 #endif
16         }
17 
18         public TClass CreateProxyType()
19         {
20             return (TClass)GetTransparentProxy();
21         }
22 
23         #region Invoke
24         public override IMessage Invoke(IMessage msg)
25         {
26             var call = (IMethodCallMessage)msg;
27             var attributes = GetAspectAttributes(call.MethodBase);
28             var context = new AspectContext
29             {
30                 MethodInfo = call.MethodBase,
31                 ParameterArgs = call.Args
32             };
33 
34             PreProcess(call, attributes, context);
35 
36             var ctor = call as IConstructionCallMessage;
37             if (ctor != null)
38             {
39                 var defaultProxy = RemotingServices.GetRealProxy(this._target);
40                 defaultProxy.InitializeServerObject(ctor);
41                 var tp = (MarshalByRefObject)this.GetTransparentProxy();
42                 return EnterpriseServicesHelper.CreateConstructionReturnMessage(ctor, tp);
43             }
44 
45             IMethodReturnMessage resultMsg = default(IMethodReturnMessage);
46             var methodInfo = (this._target as TClass).GetType().GetMethod(call.MethodName);
47             var newArray = call.Args.ToArray();  //拷貝一份參數本地副本,用於從實際方法中接收out ,ref參數的值
48             try
49             {
50                 var resultValue = methodInfo.Invoke(_target, newArray);
51                 context.ReturnObj = resultValue;
52                 resultMsg = new ReturnMessage(context.ReturnObj, newArray, newArray.Length, call.LogicalCallContext, call);
53             }
54             catch (Exception ex)
55             {
56                 var exceptionContext = new ExceptionContext
57                 {
58                     MethodInfo = context.MethodInfo,
59                     ParameterArgs = context.ParameterArgs,
60                     ReturnObj = context.ReturnObj,
61                     ExceptionInfo = ex
62                 };
63 
64                 ProcessException(attributes, exceptionContext);
65 
66                 var resultValue = methodInfo.ReturnType.IsValueType ? Activator.CreateInstance(methodInfo.ReturnType) : null;
67                 resultMsg = new ReturnMessage(resultValue, newArray, newArray.Length, call.LogicalCallContext, call);
68             }
69 
70             PostProcess(call, resultMsg, attributes, context);
71 
72             return resultMsg;
73         }
74         #endregion
75 }
MarshalByRefObject+RealProxy

 

該種方式,通過一個繼承了RealProxy的類,來包裹一個繼承了MarshalByRefObject的類,進行攔截該被Aop的實例的方法的調用。

該方式有個難點是異常的捕獲,如果使用RemotingServices.ExecuteMessage(MarshalByRefObject target, IMethodCallMessage reqMsg),那么異常捕獲不到,會直接拋出到外層。那么需要使用其他的方式來進行,並且要與方法調用的上下文對應。實現思路:

1.反射出指定的Method

2.進行Try catch

3.通過重新實例化ReturnMessage返回給是調用實例。

六、性能測試

CPU:i7-3770K CPU 3.50Hz

 

1000次調用

1萬次調用

10萬次調用

100萬次調用

 

可以看到,在調用10萬次之前,一直是繼承模式遙遙領先,而在10萬次之后,實現接口模式效率漸漸開始憂於繼承模式。

七、尾聲

   代碼雖簡單,但實現的過程和思路曲折,絕對干貨,幫您的節省寫代碼的數量和時間。節省了您的時間,那么您也花幾秒鍾點一下右下角的推薦吧。:)

  最后附上源碼點此下載源碼

 

 

作者:Zachary
出處:https://zacharyfan.com/archives/27.html

 

▶關於作者:張帆(Zachary,個人微信號:Zachary-ZF)。堅持用心打磨每一篇高質量原創。歡迎掃描右側的二維碼~。

定期發表原創內容:架構設計丨分布式系統丨產品丨運營丨一些思考。

 

如果你是初級程序員,想提升但不知道如何下手。又或者做程序員多年,陷入了一些瓶頸想拓寬一下視野。歡迎關注我的公眾號「跨界架構師」,回復「技術」,送你一份我長期收集和整理的思維導圖。

如果你是運營,面對不斷變化的市場束手無策。又或者想了解主流的運營策略,以豐富自己的“倉庫”。歡迎關注我的公眾號「跨界架構師」,回復「運營」,送你一份我長期收集和整理的思維導圖。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM