一、前言
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 }
該種方式,通過一個繼承了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)。堅持用心打磨每一篇高質量原創。歡迎掃描右側的二維碼~。
定期發表原創內容:架構設計丨分布式系統丨產品丨運營丨一些思考。
如果你是初級程序員,想提升但不知道如何下手。又或者做程序員多年,陷入了一些瓶頸想拓寬一下視野。歡迎關注我的公眾號「跨界架構師」,回復「技術」,送你一份我長期收集和整理的思維導圖。
如果你是運營,面對不斷變化的市場束手無策。又或者想了解主流的運營策略,以豐富自己的“倉庫”。歡迎關注我的公眾號「跨界架構師」,回復「運營」,送你一份我長期收集和整理的思維導圖。