Lind.DDD.Aspects通過Plugins實現方法的動態攔截~Lind裡的AOP

張佔嶺發表於2016-12-06

回到目錄

.Net MVC之所以發展的如些之好,一個很重要原因就是它公開了一組AOP的過濾器,即使用這些過濾器可以方便的攔截controller裡的action,並注入我們自己的程式碼邏輯,向全域性的異常記錄,使用者授權,Url授權,操作行為記錄等,這一大批Lind的基本元件都是實現MVC和API的過濾實現的,使用這些過濾讓我們不用去像HttpModule和HttpHandler那樣,還要在Config裡配置注入點,讓程式設計師在開發方式上感覺很舒服,維護成功很低!

本文主要內容點

  1. Lind.DDD裡的方法攔截器
  2. 動態注入需要Lind.DDD.Plugins的支援
  3. 零配置的方法攔截
  4. 一個日誌攔截器
  5. 正在構建一個快取攔截器

目錄結構

 

Lind.DDD裡的方法攔截器

Lind.DDD.Aspects這個攔截器起源自ABP框架,但不知道為什麼,ABP對這個攔截器並沒有完全實現,所以今天大叔又實現了一下,解決了相關BUG, 對方法攔截上,在動態代理工廠裡對方法攔截上下文新增了一些必要的引數,因為大叔認為,你只提供一個“方法名稱”引數,太過簡單了,哈哈。

    /// <summary>
    /// 方法相關資訊
    /// </summary>
    public class MethodMetadata
    {
        /// <summary>
        /// 上下文
        /// </summary>
        private MethodInfo _context;
        /// <summary>
        /// 方法名
        /// </summary>
        private string _methodName;

        public MethodMetadata(string methodName, MethodInfo context = null)
        {
            _methodName = methodName;
            _context = context;
        }
        /// <summary>
        /// 方法名稱
        /// </summary>
        public virtual string MethodName
        {
            get { return _methodName; }
            set { _methodName = value; }
        }
        /// <summary>
        /// 方法上下文
        /// </summary>
        public virtual string Context
        {
            get { return _context; }
            set { _context = value; }
        }
    }

一個簡單的日誌攔截器的實現,它在方法執行前去攔截

   /// <summary>
    /// 方法執行前攔截,並記錄日誌
    /// </summary>
    public class LoggerAspectAttribute : BeforeAspectAttribute
    {
        public override void Action(InvokeContext context)
        {
            Console.WriteLine("logger start!" + context.Method.MethodName);
            Lind.DDD.Logger.LoggerFactory.Instance.Logger_Info(context.Method.MethodName + "這個方法開始執行");
        }
    }

而在程式中,這個特性Attribute如何被動態代理攔截呢,事件上,如果你直接寫程式碼也是可以的,就是使用Aspect提供的ProxyFactory工廠來進行生產,但大叔認為,這樣的程式碼耦合度太高,而且對於現有的程式碼還需要進行修改,最重要一點,這種程式碼總感覺有種壞味道!

     static void Main(string[] args)
        {
            ProxyFactory.CreateProxy<ITest>(typeof(LoggerAspectTest)).Do();
            Console.Read();
        }

所以就有了下面大叔的封裝,用到了Lind.DDD.Plugins這個外掛模式,將所有的攔截器都先進行註冊,然後在生產物件時為它動態新增對應的ProxyFactory物件,請大家接著向下看,動態注入需要Lind.DDD.Plugins的支援這部分講解。

動態注入需要Lind.DDD.Plugins的支援

上面的攔截器只是簡單的實現,簡單的呼叫,而不具有一般性,即你需要自己維護需要“攔截的程式碼”,而大叔在進行使用中感覺很不爽,於是想起了Plugins,讓外掛為我們實現這種注入,就像MVC的Filter一樣,在框架本身去實現方法攔截的功能!大叔認為這樣才是最好的!

1 所有攔截器都繼承IAspectProxy表示介面,而它自己則是繼承IPlugins的

 

   /// <summary>
    /// 支援AOP攔截的介面,它被認為是一種外掛動態注入到系統中
    /// </summary>
    public interface IAspectProxy : Lind.DDD.Plugins.IPlugins { }

 

2 在PluginManager的Resolve方法中,新增動態的ProxyFactory實現,讓實現了IAspectProxy的型別,自動進行攔截器的實現

      /// <summary>
        /// 從外掛容器裡返回物件
        /// </summary>
        /// <param name="serviceName">物件全名</param>
        /// <param name="serviceType">介面型別</param>
        /// <returns></returns>
        public static object Resolve(string serviceName, Type serviceType)
        {
            var obj = _container.ResolveNamed(serviceName, serviceType);
            if (typeof(Lind.DDD.Aspects.IAspectProxy).IsAssignableFrom(serviceType))
            {
                obj = ProxyFactory.CreateProxy(serviceType, obj.GetType());
            }
            return obj;
        }

OK,有了上面的程式碼,我們的方法攔截就成了一種外掛了,在使用的時間之前的外掛的使用方法相同,當然底層還是使用autofac來實現的Ioc容器。

 var old = Lind.DDD.Plugins.PluginManager.Resolve<IAopHelloTest2>("Lind.DDD.UnitTest.AopHello");
 old.Hello("zz", 1);

一個日誌攔截器

日誌記錄是一些業務複雜方法必備的,如一些訂單方法,使用者提現方法都會新增相關的日誌,而如果希望動態新增日誌,而不在程式碼段中去新增,則可以設計一個日誌攔截器,當然你可以在方法執行前去控制,也可以在方法執行後去控制!

  /// <summary>
    /// 方法執行前攔截,並記錄日誌
    /// </summary>
    public class LoggerAspectAttribute : BeforeAspectAttribute
    {
        public override void Action(InvokeContext context)
        {
            Console.WriteLine("logger start!" + context.Method.MethodName);
            Lind.DDD.Logger.LoggerFactory.Instance.Logger_Info(context.Method.MethodName + "這個方法開始執行");
        }
    }

    /// <summary>
    /// 方法執行完成後攔截,並記錄日誌
    /// </summary>
    public class LoggerEndAspectAttribute : AfterAspectAttribute
    {
        public override void Action(InvokeContext context)
        {
            Console.WriteLine("logger start!" + context.Method.MethodName);
            Lind.DDD.Logger.LoggerFactory.Instance.Logger_Info(context.Method.MethodName + "這個方法開始執行");
        }
    }

目錄方法需要新增這種日誌的行為,只要在方法上新增對應的特性即可,(方法不需要為虛方法)而不需要修改方法程式碼體,如下面的程式碼

   /// <summary>
    /// AOP呼叫方式
    /// </summary>
    public class LoggerAspectTest : ITest
    {
        [LoggerAspectAttribute]
        public void Do()
        {
            //我做事情
            Console.WriteLine("我做事情");
        }
    }

正在構建一個快取攔截器

目前,大叔正在構建一個快取的攔截器,主要是實現對方法返回值的快取,而不需要將這種快取判斷的邏輯寫在每個方法體內,大叔認為,這種面向切面的AOP的設計,才是大勢所趨,敬請大家期待!

   /// <summary>
    /// 快取攔截器
    /// </summary>
    public class CachingAspectAttribute : BeforeAspectAttribute
    {
        CachingMethod cachingMethod;
        public CachingAspectAttribute(CachingMethod cachingMethod)
        {
            this.cachingMethod = cachingMethod;
        }

        public override void Action(InvokeContext context)
        {
            var method = context.Method;
            string prefix = "Lind";
            var baseInterfaces = context.GetType().GetInterfaces();
            if (baseInterfaces != null && baseInterfaces.Any())
            {
                foreach (var item in baseInterfaces)
                {
                    prefix += item.ToString() + "_";
                }
            }

            //鍵名,在put和get時使用
            var key = prefix + method.MethodName;
            Console.WriteLine(key);
            switch (cachingMethod)
            {
                case CachingMethod.Remove:
                    //……
                    break;
                case CachingMethod.Get:
                    //……
                    break;
                case CachingMethod.Put:
                    //……
                    break;
                default:
                    throw new InvalidOperationException("無效的快取方式。");

            }
        }
    }

我們對支援的追求將不會停止,希望廣大青年都可以踏一心來,去認真的研究一個技術,事實上,對一個技術研究透了,大叔認為就足夠了!

此致

敬禮

回到目錄

 

相關文章