C#中利用Attribute实现AOP

C#的AOP实现主要是参考了这篇博客,并对实现过程中遇到的问题进行分析和修改)。

AOP实现流程

类拦截

AOPContextAttribute.cs

定义一个AOP上下文特性,用于标注需要支持AOP方法的类,通过 ContextAttribute, IContributeObjectSink 来获取类的上下文环境,这是通过 Attribute 拦截参数和获取返回值的前提。

    /// <summary>
    /// 定义一个AOP上下文特性,用于标注需要支持AOP方法的类,
    /// 通过 ContextAttribute, IContributeObjectSink 来获取类的上下文环境,
    /// 这是通过 Attribute 拦截参数和获取返回值的前提。
    /// </summary>
    [AttributeUsage(AttributeTargets.Class)]
    public sealed class AOPContextAttribute : ContextAttribute, IContributeObjectSink
    {
        public IMessageSink GetObjectSink(MarshalByRefObject obj, IMessageSink nextSink)
        {
            return new AOPHandler(nextSink);
        }

        public AOPContextAttribute() : base("AOPContext")
        {
        }
    }

AOPContext.cs

实现一个继承自 ContextBoundObject 的类,并标注 [AOPContext] 特性,两者配合,使得这个类下的方法可以被成功拦截。需要支持AOP的类,继承这个类即可。

[AOPContext]
    public class AOPContext : ContextBoundObject 
    {
    }

方法处理

AOPBeforeAttribute.cs

用于标注需要拦截参数的方法,和指出对应的处理函数。

// <summary>
    /// 用于标注需要拦截参数的方法,和指出对应的处理函数。
    /// @author: seawish.zheng
    /// </summary>
    public class AOPBeforeAttribute : Attribute
    {
        public string FullClassName;
        public string StaticMethodName;

        public AOPBeforeAttribute(string fullClassName, string methodName)
        {
            FullClassName = fullClassName;
            StaticMethodName = methodName;
        }
    }

AOPAfterAttribute

/// <summary>
    /// 用于标注需要获取返回值的方法,和指出对应的处理函数。
    /// @author: seawish.zheng
    /// </summary>
    public class AOPAfterAttribute : Attribute
    {
        public string FullClassName;
        public string StaticMethodName;

        public AOPAfterAttribute(string fullClassName, string methodName)
        {
            FullClassName = fullClassName;
            StaticMethodName = methodName;
        }
    }

ReflectionUtil

/// <summary>
/// 获取方法特性
/// </summary>
class ReflectionUtil
    {
        public static T GetAttribute<T>(MethodInfo method) where T : Attribute
        {
            var attrs = method.GetCustomAttributes(typeof(T), false);
            if (attrs.Length != 0)
            {
                var attribute = attrs[0] as T;
                if (attribute != null)
                {
                    return attribute;
                }
            }
            return null;
        }
    }

handler类

/// <summary>
    /// 这个类用于实现 具体的参数拦截和返回值获取操作。
    /// todo: ?????目前拦截的是@AopBeforeAttribute标注的方法的上一个方法,奇怪。
    /// </summary>
    public class AOPHandler : IMessageSink
    {
        /// <summary>
        /// 下一个接收器
        /// </summary>
        private readonly IMessageSink _nextSink;

        public AOPHandler(IMessageSink nextSink)
        {
            _nextSink = nextSink;
        }

        public IMessageSink NextSink
        {
            get { return _nextSink; }
        }

        /// <summary>
        /// 同步处理方法  
        /// </summary>
        /// <param name="msg"></param>
        /// <returns></returns>
        public IMessage SyncProcessMessage(IMessage msg)
        {
            IMessage message = null;
            var callMessage = msg as IMethodCallMessage;
            if (callMessage != null)
            {
                // Before
                var before = ReflectionUtil.GetAttribute<AOPBeforeAttribute>(callMessage.MethodBase as MethodInfo);
                if (before != null)
                {
                    PreProceed(msg, before);
                }
                // Invoke
                message = _nextSink.SyncProcessMessage(msg);
                // After
                var after = ReflectionUtil.GetAttribute<AOPAfterAttribute>(callMessage.MethodBase as MethodInfo);
                if (after != null)
                {
                    PostProceed(message, after);
                }
            }
            else
            {
                message = _nextSink.SyncProcessMessage(msg);
            }
            return message;
        }

        /// <summary>
        /// 异步处理方法
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="replySink"></param>
        /// <returns></returns>
        public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)
        {
            return null;
        }

        /// <summary>
        /// 方法执行前
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="before"></param>
        public void PreProceed(IMessage msg, AOPBeforeAttribute before)
        {
            var message = msg as IMethodMessage;
            var type = Assembly.GetCallingAssembly().GetType(before.FullClassName);
            var param = message.Args;
            type.InvokeMember(before.StaticMethodName, BindingFlags.InvokeMethod, null, null, param);
            Console.WriteLine("test");
        }

        /// <summary>
        /// 方法执行后
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="after"></param>
        public void PostProceed(IMessage msg, AOPAfterAttribute after)
        {
            var message = msg as IMethodReturnMessage;
            var type = Assembly.GetCallingAssembly().GetType(after.FullClassName);
            var param = message.ReturnValue;
            type.InvokeMember(after.StaticMethodName, BindingFlags.InvokeMethod, null, null, new[] { param });
            Console.WriteLine("test");
        }

    }

aop示例

AopSample

TestMethod1为将被拦截处理的方法,before方法和after方法可抽取到其他类中。

/// <summary>
/// 业务实现类,主要是配置Before和After方法的实现。
/// </summary>
namespace Dji.MES.WebAPI
{
    public class Model1
    {
        public int a
        {
            get; set;
        }
        
        public int b
        {
            get;
            set;
        }
    }
    public class AopSample : AOPContext
    {
        /// <summary>
        /// AOPBefore("Dji.MES.Base.AopSample", "Before")
        /// </summary>
        /// <returns></returns>
        [AOPBefore("Dji.MES.WebAPI.AopSample", "Before")]
        [AOPAfter("Dji.MES.WebAPI.AopSample", "After")]
        public int TestMethod1(Model1 model1)
        {
            Console.WriteLine("Process Test 1 :" + model1.a + "\t" + model1.b);
            return model1.a + model1.b;
        }

        
        /// <summary>
        /// 方法执行前,对参数进行预处理
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        public static void Before(Model1 model1)
        {   
            model1.a = 200;
            model1.b = 400;
        }

        /// <summary>
        /// 方法执行后,对返回值进行处理
        /// </summary>
        /// <param name="result"></param>
        public static void After(int result)
        {
            Console.WriteLine("End :" + result);
        }
    }
}

问题解决

aop拦截方法

被拦截的类需要继承AOPContext,并且该类中调用的第一个方法会被拦截,如果存在嵌套方法,不会拦截到第二个方法。

Attribute的type获取失败

解决

  1. 该问题为被拦截的类找不到问题,需要保证AOPhandle类能够访问到该类,如果属于不同的dll中,则需要引用。
  2. AOPBefore和AOPAfter的参数错误,需使用全限定名,并且是包含before和after静态方法的类。
    改为全限定名。
  /// <summary>
        /// AOPBefore("Dji.MES.Base.AopSample", "Before")
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        [AOPBefore("Dji.MES.WebAPI.AopSample", "Before")]
        [AOPAfter("Dji.MES.WebAPI.AopSample", "After")]
        public int TestMethod1(int a, int b)
        {
            Console.WriteLine("Process Test 1 :" + a + "\t" + b);
            return a + b;
        }

值参数未改变

目前的实现无法改值参,可以考虑将数值类型封装到model中,传model对象。

参考文献

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容