C#中的三种AOP实现思路

一、前言

  1.在项目中无处不充斥着记录日志的代码,各种try catch,实在是有点看着不爽。这不,果断要想法子偷个懒儿。

二、摘要

鄙人不才,先总结一下个人想到的可实现AOP的几种思路:

  1.通过继承特定实例,重写虚方法(C#中如virtual、override方法),动态构建一个该实例的子类,进行调用。

  2.通过实现特定实例上的接口,动态构建一个该接口的实现类,切入AOP代码,内部包裹特定实例的方法。

  3.最简单的一种方式,通过给特定实例继承MarshalByRefObject类,并且用继承RealProxy的代理类进行构造包裹。

代码比较少,有些Emit基础的童鞋应该很容易看懂,接下去直接上核心代码。

三、继承类模式

if (!method.IsPublic || !method.IsVirtual/*非虚方法无法重写*/|| method.IsFinal /*Final方法无法重写,如interface的实现方法标记为 virtual final*/ || IsObjectMethod(method)) return;

            const MethodAttributes methodattributes = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual;
            Type[] paramTypes = method.GetParameters().Select(ent => ent.ParameterType).ToArray();
            MethodBuilder mb = _typeBuilder.DefineMethod(method.Name, methodattributes, method.ReturnType, paramTypes);
            ILGenerator il = mb.GetILGenerator();

            #region 初始化本地变量和返回值
            //加载所有参数到本地object[],实例方法第一个参数是this,已排除
            LoadArgsIntoLocalField(il, paramTypes);

            //如果有返回值,定义返回值变量
            bool isReturnVoid = method.ReturnType == typeof(void);
            LocalBuilder result = null;
            if (!isReturnVoid)
                result = il.DeclareLocal(method.ReturnType);

            //定义MethodInfo变量,下面会用到(传递到Aop的上下文中)
            var methodInfo = il.DeclareLocal(typeof(MethodBase));
            il.Emit(OpCodes.Call, typeof(MethodBase).GetMethod("GetCurrentMethod", Type.EmptyTypes));
            il.Emit(OpCodes.Stloc, methodInfo);
            #endregion

            #region 初始化AspectContext
            Type contextType = typeof(AspectContext);
            var context = il.DeclareLocal(contextType);
            ConstructorInfo info = contextType.GetConstructor(Type.EmptyTypes);
            il.Emit(OpCodes.Newobj, info);
            il.Emit(OpCodes.Stloc, context);
            #endregion

            #region 给AspectContext的参数值属性ParameterArgs,MethodInfo赋值
            il.Emit(OpCodes.Ldloc, context);
            il.Emit(OpCodes.Ldloc_0);
            il.Emit(OpCodes.Call, contextType.GetMethod("set_ParameterArgs"));

            il.Emit(OpCodes.Ldloc, context);
            il.Emit(OpCodes.Ldloc, methodInfo);
            il.Emit(OpCodes.Call, contextType.GetMethod("set_MethodInfo"));
            #endregion

            AspectAttribute[] attrs = GetAspectAttributes(method);
            int attrLen = attrs.Length;
            LocalBuilder[] lbs = new LocalBuilder[attrLen];
            MethodInfo[] endInvokeMethods = new MethodInfo[attrLen];
            MethodInfo[] invokingExceptionMethods = new MethodInfo[attrLen];

            #region 初始化标记的切面对象,并调用切面对象的BeforeInvoke方法
            for (int i = 0; i < attrLen; i++)
            {
                var tmpAttrType = attrs[i].GetType();
                var tmpAttr = il.DeclareLocal(tmpAttrType);
                ConstructorInfo tmpAttrCtor = tmpAttrType.GetConstructor(Type.EmptyTypes);

                il.Emit(OpCodes.Newobj, tmpAttrCtor);
                il.Emit(OpCodes.Stloc, tmpAttr);

                var beforeInvokeMethod = tmpAttrType.GetMethod("BeforeInvoke");
                endInvokeMethods[i] = tmpAttrType.GetMethod("AfterInvoke");
                invokingExceptionMethods[i] = tmpAttrType.GetMethod("InvokingException");

                il.Emit(OpCodes.Ldloc, tmpAttr);
                il.Emit(OpCodes.Ldloc, context);
                il.Emit(OpCodes.Callvirt, beforeInvokeMethod);
                il.Emit(OpCodes.Nop);

                lbs[i] = tmpAttr;
            }
            #endregion

            //try
            il.BeginExceptionBlock();

            #region 调用实现方法
            if (!method.IsAbstract)
            {
                //类对象,参数值依次入栈
                for (int i = 0; i <= paramTypes.Length; i++)
                    il.Emit(OpCodes.Ldarg, i);

                //调用基类的方法
                il.Emit(OpCodes.Call, method);

                if (!isReturnVoid)
                {
                    il.Emit(OpCodes.Stloc, result);

                    //
                    il.Emit(OpCodes.Ldloc, context);
                    il.Emit(OpCodes.Ldloc, result);
                    if (method.ReturnType.IsValueType)
                        il.Emit(OpCodes.Box, method.ReturnType);
                    il.Emit(OpCodes.Call, contextType.GetMethod("set_ReturnObj"));
                }
            }
            #endregion

            //catch
            il.BeginCatchBlock(typeof(Exception));
            var exception = il.DeclareLocal(typeof(Exception));
            il.Emit(OpCodes.Stloc, exception);

            #region 初始化ExceptionContext
            var exceptionContentType = typeof(ExceptionContext);
            var exceptionContent = il.DeclareLocal(exceptionContentType);
            var exceptionContentCtor = exceptionContentType.GetConstructor(Type.EmptyTypes);
            il.Emit(OpCodes.Newobj, exceptionContentCtor);
            il.Emit(OpCodes.Stloc, exceptionContent);
            #endregion

            #region 给ExceptionContext的参数值属性ParameterArgs,MethodInfo,ExceptionInfo,赋值
            il.Emit(OpCodes.Ldloc, exceptionContent);
            il.Emit(OpCodes.Ldloc_0);
            il.Emit(OpCodes.Call, exceptionContentType.GetMethod("set_ParameterArgs"));

            il.Emit(OpCodes.Ldloc, exceptionContent);
            il.Emit(OpCodes.Ldloc, methodInfo);
            il.Emit(OpCodes.Call, exceptionContentType.GetMethod("set_MethodInfo"));

            il.Emit(OpCodes.Ldloc, exceptionContent);
            il.Emit(OpCodes.Ldloc, exception);
            il.Emit(OpCodes.Call, exceptionContentType.GetMethod("set_ExceptionInfo"));
            #endregion

            #region 调用切面对象的InvokingException方法
            for (int i = 0; i < attrLen; i++)
            {
                il.Emit(OpCodes.Ldloc, lbs[i]);
                il.Emit(OpCodes.Ldloc, exceptionContent);
                il.Emit(OpCodes.Callvirt, invokingExceptionMethods[i]);
                il.Emit(OpCodes.Nop);
            }
            #endregion
            //try end
            il.EndExceptionBlock();

            #region 调用切面对象的AfterInvoke方法
            for (int i = 0; i < attrLen; i++)
            {
                il.Emit(OpCodes.Ldloc, lbs[i]);
                il.Emit(OpCodes.Ldloc, context);
                il.Emit(OpCodes.Callvirt, endInvokeMethods[i]);
                il.Emit(OpCodes.Nop);
            }
            #endregion

            if (!isReturnVoid)
                il.Emit(OpCodes.Ldloc, result);

            //返回
            il.Emit(OpCodes.Ret);

该种方式,建立在使用base.XXXMethod(arg1,arg2,…)模式来调用被Aop的对象的业务方法,关键点是使用BeginExceptionBlock(),BeginCatchBlock(typeof(Exception)),EndExceptionBlock();来动态创建try catch代码块,进行异常处理。

四、实现接口模式

if (!method.IsPublic || IsObjectMethod(method))
                return;

            string methodName = method.Name;

            const MethodAttributes methodattributes = MethodAttributes.Public | MethodAttributes.Virtual;
            Type[] paramTypes = method.GetParameters().Select(ent => ent.ParameterType).ToArray();
            MethodBuilder methodBuilder = _typeBuilder.DefineMethod(methodName, methodattributes, method.ReturnType, paramTypes.ToArray());
            var il = methodBuilder.GetILGenerator();

            #region 初始化本地变量和返回值
            //加载所有参数到本地object[]
            LoadArgsIntoLocalField(il, paramTypes);

            //如果有返回值,定义返回值变量
            bool isReturnVoid = method.ReturnType == typeof(void);
            LocalBuilder resultLocal = null;
            if (!isReturnVoid)
                resultLocal = il.DeclareLocal(method.ReturnType);

            //定义MethodInfo变量,下面会用到(传递到Aop的上下文中)
            var methodInfo = il.DeclareLocal(typeof(MethodBase));
            il.Emit(OpCodes.Call, typeof(MethodBase).GetMethod("GetCurrentMethod", Type.EmptyTypes));
            il.Emit(OpCodes.Stloc, methodInfo);
            #endregion

            #region 初始化AspectContext

            Type contextType = typeof(AspectContext);
            var context = il.DeclareLocal(contextType);
            il.Emit(OpCodes.Newobj, contextType.GetConstructor(Type.EmptyTypes));
            il.Emit(OpCodes.Stloc, context);

            #endregion

            #region 给AspectContext的参数值属性ParameterArgs,MethodInfo赋值
            il.Emit(OpCodes.Ldloc, context);
            il.Emit(OpCodes.Ldloc_0);
            il.Emit(OpCodes.Call, contextType.GetMethod("set_ParameterArgs"));

            il.Emit(OpCodes.Ldloc, context);
            il.Emit(OpCodes.Ldloc, methodInfo);
            il.Emit(OpCodes.Call, contextType.GetMethod("set_MethodInfo"));
            #endregion

            AspectAttribute[] attrs = GetAspectAttributes(method);
            int attrLen = attrs.Length;
            LocalBuilder[] lbs = new LocalBuilder[attrLen];
            MethodInfo[] endInvokeMethods = new MethodInfo[attrLen];
            MethodInfo[] invokingExceptionMethods = new MethodInfo[attrLen];

            #region 初始化标记的切面对象,并调用切面对象的BeforeInvoke方法
            for (int i = 0; i < attrLen; i++)
            {
                var tmpAttrType = attrs[i].GetType();
                var tmpAttr = il.DeclareLocal(tmpAttrType);
                ConstructorInfo tmpAttrCtor = tmpAttrType.GetConstructor(Type.EmptyTypes);

                il.Emit(OpCodes.Newobj, tmpAttrCtor);
                il.Emit(OpCodes.Stloc, tmpAttr);

                var beforeInvokeMethod = tmpAttrType.GetMethod("BeforeInvoke");
                endInvokeMethods[i] = tmpAttrType.GetMethod("AfterInvoke");
                invokingExceptionMethods[i] = tmpAttrType.GetMethod("InvokingException");

                il.Emit(OpCodes.Ldloc, tmpAttr);
                il.Emit(OpCodes.Ldloc, context);
                il.Emit(OpCodes.Callvirt, beforeInvokeMethod);
                il.Emit(OpCodes.Nop);

                lbs[i] = tmpAttr;
            }
            #endregion

            il.BeginExceptionBlock();

            #region 调用实现方法
            if (!method.IsAbstract)
            {
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Ldfld, _realProxyField);
                for (int i = 0; i < paramTypes.Length; i++)
                    il.Emit(OpCodes.Ldarg, i + 1);  //arg_0为当前实例,故不添加到栈。

                il.Emit(OpCodes.Call, method);

                if (!isReturnVoid)
                {
                    il.Emit(OpCodes.Stloc, resultLocal);

                    //
                    il.Emit(OpCodes.Ldloc, context);
                    il.Emit(OpCodes.Ldloc, resultLocal);
                    if (method.ReturnType.IsValueType)
                        il.Emit(OpCodes.Box, method.ReturnType);
                    il.Emit(OpCodes.Call, contextType.GetMethod("set_ReturnObj"));
                }
            }
            #endregion

            //catch
            il.BeginCatchBlock(typeof(Exception));
            var exception = il.DeclareLocal(typeof(Exception));
            il.Emit(OpCodes.Stloc, exception);

            #region 初始化ExceptionContext
            var exceptionContentType = typeof(ExceptionContext);
            var exceptionContent = il.DeclareLocal(exceptionContentType);
            var exceptionContentCtor = exceptionContentType.GetConstructor(Type.EmptyTypes);
            il.Emit(OpCodes.Newobj, exceptionContentCtor);
            il.Emit(OpCodes.Stloc, exceptionContent);
            #endregion

            #region 给ExceptionContext的参数值属性ParameterArgs,MethodInfo,ExceptionInfo,赋值
            il.Emit(OpCodes.Ldloc, exceptionContent);
            il.Emit(OpCodes.Ldloc_0);
            il.Emit(OpCodes.Call, exceptionContentType.GetMethod("set_ParameterArgs"));

            il.Emit(OpCodes.Ldloc, exceptionContent);
            il.Emit(OpCodes.Ldloc, methodInfo);
            il.Emit(OpCodes.Call, exceptionContentType.GetMethod("set_MethodInfo"));

            il.Emit(OpCodes.Ldloc, exceptionContent);
            il.Emit(OpCodes.Ldloc, exception);
            il.Emit(OpCodes.Call, exceptionContentType.GetMethod("set_ExceptionInfo"));
            #endregion

            #region 调用切面对象的InvokingException方法
            for (int i = 0; i < attrLen; i++)
            {
                il.Emit(OpCodes.Ldloc, lbs[i]);
                il.Emit(OpCodes.Ldloc, exceptionContent);
                il.Emit(OpCodes.Callvirt, invokingExceptionMethods[i]);
                il.Emit(OpCodes.Nop);
            }
            #endregion

            il.EndExceptionBlock();

            #region 调用切面对象的AfterInvoke方法
            for (int i = 0; i < attrLen; i++)
            {
                il.Emit(OpCodes.Ldloc, lbs[i]);
                il.Emit(OpCodes.Ldloc, context);
                il.Emit(OpCodes.Callvirt, endInvokeMethods[i]);
                il.Emit(OpCodes.Nop);
            }
            #endregion

            if (!isReturnVoid)
            {
                il.Emit(OpCodes.Ldloc, resultLocal);
            }

            il.Emit(OpCodes.Ret);

该种方式,与继承类模式十分类似,唯一的区别是其保存了被Aop的实例到一个全局变量,通过该全局变量进行相应的业务方法调用。

五、通过MarshalByRefObject和RealProxy

public sealed class ProxyMarshalByRefObject<TClass> : RealProxy where TClass : class
    {
        private readonly MarshalByRefObject _target;

        public ProxyMarshalByRefObject()
            : base(typeof(TClass))
        {
            _target = (MarshalByRefObject)Activator.CreateInstance(typeof(TClass));

#if DEBUG
            // Get 'ObjRef', for transmission serialization between application domains.
            ObjRef myObjRef = RemotingServices.Marshal(_target);
            // Get the 'URI' property of 'ObjRef' and store it.
            Console.WriteLine("URI :{0}", myObjRef.URI);
#endif
        }

        public TClass CreateProxyType()
        {
            return (TClass)GetTransparentProxy();
        }

        #region Invoke
        public override IMessage Invoke(IMessage msg)
        {
            var call = (IMethodCallMessage)msg;
            var attributes = GetAspectAttributes(call.MethodBase);
            var context = new AspectContext
            {
                MethodInfo = call.MethodBase,
                ParameterArgs = call.Args
            };

            PreProcess(call, attributes, context);

            var ctor = call as IConstructionCallMessage;
            if (ctor != null)
            {
                var defaultProxy = RemotingServices.GetRealProxy(this._target);
                defaultProxy.InitializeServerObject(ctor);
                var tp = (MarshalByRefObject)this.GetTransparentProxy();
                return EnterpriseServicesHelper.CreateConstructionReturnMessage(ctor, tp);
            }

            IMethodReturnMessage resultMsg = default(IMethodReturnMessage);
            var methodInfo = (this._target as TClass).GetType().GetMethod(call.MethodName);
            var newArray = call.Args.ToArray();  //拷贝一份参数本地副本,用于从实际方法中接收out ,ref参数的值
            try
            {
                var resultValue = methodInfo.Invoke(_target, newArray);
                context.ReturnObj = resultValue;
                resultMsg = new ReturnMessage(context.ReturnObj, newArray, newArray.Length, call.LogicalCallContext, call);
            }
            catch (Exception ex)
            {
                var exceptionContext = new ExceptionContext
                {
                    MethodInfo = context.MethodInfo,
                    ParameterArgs = context.ParameterArgs,
                    ReturnObj = context.ReturnObj,
                    ExceptionInfo = ex
                };

                ProcessException(attributes, exceptionContext);

                var resultValue = methodInfo.ReturnType.IsValueType ? Activator.CreateInstance(methodInfo.ReturnType) : null;
                resultMsg = new ReturnMessage(resultValue, newArray, newArray.Length, call.LogicalCallContext, call);
            }

            PostProcess(call, resultMsg, attributes, context);

            return resultMsg;
        }
        #endregion
}

该种方式,通过一个继承了RealProxy的类,来包裹一个继承了MarshalByRefObject的类,进行拦截该被Aop的实例的方法的调用。

该方式有个难点是异常的捕获,如果使用RemotingServices.ExecuteMessage(MarshalByRefObject target, IMethodCallMessage reqMsg),那么异常捕获不到,会直接抛出到外层。那么需要使用其他的方式来进行,并且要与方法调用的上下文对应。实现思路:

1.反射出指定的Method

2.进行Try catch

3.通过重新实例化ReturnMessage返回给是调用实例。

六、性能测试

CPU:i7-3770K CPU 3.50Hz

1000次调用

aopcsharp

1万次调用

aopcsharp

10万次调用

aopcsharp

100万次调用

aopcsharp

可以看到,在调用10万次之前,一直是继承模式遥遥领先,而在10万次之后,实现接口模式效率渐渐开始忧于继承模式。

七、尾声

   代码虽简单,但实现的过程和思路曲折,绝对干货,帮您的节省写代码的数量和时间。节省了您的时间,那么您也花几秒钟点一下右下角的推荐吧。:)

  最后附上源码,点此下载源码

 



原创文章,转载请注明本文链接: https://zacharyfan.com/archives/27.html

关于作者:张帆(Zachary,个人微信号:Zachary-ZF)。坚持用心打磨每一篇高质量原创。欢迎扫描二维码~

微信公众号

定期发表原创内容:架构设计丨分布式系统丨产品丨运营丨一些思考。

如果你是初级程序员,想提升但不知道如何下手。又或者做程序员多年,陷入了一些瓶颈想拓宽一下视野。欢迎关注我的公众号「跨界架构师」,回复「技术」,送你一份我长期收集和整理的思维导图。

如果你是运营,面对不断变化的市场束手无策。又或者想了解主流的运营策略,以丰富自己的“仓库”。欢迎关注我的公众号「跨界架构师」,回复「运营」,送你一份我长期收集和整理的思维导图。

Leave a Reply

发表评论

电子邮件地址不会被公开。 必填项已用*标注

ZacharyFan.com © 2019 | WordPress Theme: BlogGem by TwoPoints.