Autofac實現攔截器和切面程式設計

俞正東 發表於 2022-01-22

Autofac.Annotation框架是我用.netcore寫的一個註解式DI框架,基於Autofac參考 Spring註解方式所有容器的註冊和裝配,切面,攔截器等都是依賴標籤來完成。

開源地址:https://github.com/yuzd/Autofac.Annotation

本期講的是最新重構的功能,這個功能也是賦予了這個框架的無限可能,也是我覺得設計的比較好的地方, 今天來說說我是怎麼設計的

切面和攔截器介紹

攔截器是什麼?

可以幫助我們方便在執行目標方法的

  • 前(Before)
  • 後(After)
  • 返回值時(AfterReturn)
  • 拋錯誤時(AfterThrowing)
  • 環繞(Around)

簡單示例:

    //自己實現一個攔截器
    public class TestHelloBefore:AspectBefore
    {
        public override Task Before(AspectContext aspectContext)
        {
            Console.WriteLine("TestHelloBefore");
            return Task.CompletedTask;
        }
    }
    
    [Component]
    public class TestHello
    {

        [TestHelloBefore]//打上攔截器
        public virtual void Say()
        {
            Console.WriteLine("Say");
        }
    }

先執行 TestHelloBefor的Before方法再執行你的Say方法

更多使用示例請檢視 Aspect攔截器

切面是什麼?

定義一個切面(根據篩選器去實現滿足條件的多個類的多個方法的“攔截器”

簡單示例:

    [Component]
    public class ProductController
    {
        public virtual string GetProduct(string productId)
        {
            return "GetProduct:" + productId;
        }
        
        public virtual string UpdateProduct(string productId)
        {
            return "UpdateProduct:" + productId;
        }
    }
    
    [Component]
    public class UserController
    {
        public virtual string GetUser(string userId)
        {
            return "GetUser:" + userId;
        }
        
        public virtual string DeleteUser(string userId)
        {
            return "DeleteUser:" + userId;
        }
    }
    
    // *Controller 代表匹配 只要是Controller結尾的類都能匹配
    // Get* 代表上面匹配成功的類下 所以是Get打頭的方法都能匹配
    [Pointcut(Class = "*Controller",Method = "Get*")]
    public class LoggerPointCut
    {
        [Around]
        public async Task Around(AspectContext context,AspectDelegate next)
        {
            Console.WriteLine("PointcutTest1.Around-start");
            await next(context);
            Console.WriteLine("PointcutTest1.Around-end");
        }

        [Before]
        public void Before()
        {
            Console.WriteLine("PointcutTest1.Before");
            
        }
        
        [After]
        public void After()
        {
            Console.WriteLine("PointcutTest1.After");
            
        }
        
        [AfterReturn(Returing = "value1")]
        public void AfterReturn(object value1)
        {
            Console.WriteLine("PointcutTest1.AfterReturn");
        }
        
        [AfterThrows(Throwing = "ex1")]
        public void Throwing(Exception ex1)
        {
            Console.WriteLine("PointcutTest1.Throwing");
        }       
    }

更多示例請檢視 Pointcut切面程式設計

如何實現的

分為3步

  • 1.蒐集攔截運算元(比如Before/After等這個我們叫運算元)
  • 2.構造攔截器鏈(按照上面圖的方式把運算元連結起來)
  • 3.生成代理類代理目標方法去執行上面構造的攔截器鏈

1.蒐集攔截運算元

因為攔截器的使用是約定了要繼承 AspectInvokeAttribute

    /// <summary>
    ///     AOP攔截器 預設包含繼承關係
    /// </summary>
    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
    public class AspectInvokeAttribute : Attribute
    {
        /// <summary>
        ///     排序 值越低,優先順序越高
        /// </summary>
        public int OrderIndex { get; set; }

        /// <summary>
        ///     分組名稱
        /// </summary>
        public string GroupName { get; set; }
    }

image
image

這一組註解是暴露給外部使用,來蒐集哪些類的哪些方法需要增強

接下來需要去針對性去實現每一種增強器要做的事情

定義一個增強器介面IAdvice

    internal interface IAdvice
    {
        /// <summary>
        ///  攔截器方法
        /// </summary>
        /// <param name="aspectContext">執行上下文</param>
        /// <param name="next">下一個增強器</param>
        /// <returns></returns>
        Task OnInvocation(AspectContext aspectContext, AspectDelegate next);
    }
image
image

Before增強器

    /// <summary>
    /// 前置增強器
    /// </summary>
    internal class AspectBeforeInterceptor : IAdvice
    {
        private readonly AspectBefore _beforeAttribute;

        public AspectBeforeInterceptor(AspectBefore beforeAttribute)
        {
            _beforeAttribute = beforeAttribute;
        }

        public async Task OnInvocation(AspectContext aspectContext, AspectDelegate next)
        {
            //先執行Before邏輯
            await this._beforeAttribute.Before(aspectContext);
            //在走下一個增強器
            await next.Invoke(aspectContext);
        }
    }

After增強器

    /// <summary>
    /// 後置增強器
    /// </summary>
    internal class AspectAfterInterceptor : IAdvice
    {
        private readonly AspectAfter _afterAttribute;
        private readonly bool _isAfterAround;

        public AspectAfterInterceptor(AspectAfter afterAttribute, bool isAfterAround = false)
        {
            _afterAttribute = afterAttribute;
            _isAfterAround = isAfterAround;
        }

        public async Task OnInvocation(AspectContext aspectContext, AspectDelegate next)
        {
            try
            {
                if (!_isAfterAround) await next.Invoke(aspectContext);
            }
            finally
            {
                //不管成功還是失敗都會執行的 
                 await this._afterAttribute.After(aspectContext, aspectContext.Exception ?? aspectContext.ReturnValue);
            }
        }
    }

環繞增強器


    /// <summary>
    /// 環繞返回攔截處理器
    /// </summary>
    internal class AspectAroundInterceptor : IAdvice
    {
        private readonly AspectArround _aroundAttribute;
        private readonly AspectAfterInterceptor _aspectAfter;
        private readonly AspectAfterThrowsInterceptor _aspectThrows;

        public AspectAroundInterceptor(AspectArround aroundAttribute, AspectAfter aspectAfter, AspectAfterThrows chainAspectAfterThrows)
        {
            _aroundAttribute = aroundAttribute;
            if (aspectAfter != null)
            {
                _aspectAfter = new AspectAfterInterceptor(aspectAfter, true);
            }

            if (chainAspectAfterThrows != null)
            {
                _aspectThrows = new AspectAfterThrowsInterceptor(chainAspectAfterThrows, true);
            }
        }

        public async Task OnInvocation(AspectContext aspectContext, AspectDelegate next)
        {
            Exception exception = null;
            try
            {
                if (_aroundAttribute != null)
                {
                    await _aroundAttribute.OnInvocation(aspectContext, next);
                    return;
                }
            }
            catch (Exception ex)
            {
                exception = ex;
            }
            finally
            {
                if (exception == null && _aspectAfter != null) await _aspectAfter.OnInvocation(aspectContext, next);
            }

            try
            {
                if (exception != null && _aspectAfter != null)
                {
                    await _aspectAfter.OnInvocation(aspectContext, next);
                }

                if (exception != null && _aspectThrows != null)
                {
                    await _aspectThrows.OnInvocation(aspectContext, next);
                }
            }
            finally
            {
                if (exception != null) throw exception;
            }
        }
    }

返回值增強器

    /// <summary>
    /// 後置返值增強器
    /// </summary>
    internal class AspectAfterReturnInterceptor : IAdvice
    {
        private readonly AspectAfterReturn _afterAttribute;

        public AspectAfterReturnInterceptor(AspectAfterReturn afterAttribute)
        {
            _afterAttribute = afterAttribute;
        }

        public async Task OnInvocation(AspectContext aspectContext, AspectDelegate next)
        {
            await next.Invoke(aspectContext);


            //執行異常了不執行after 去執行Throw
            if (aspectContext.Exception != null)
            {
                return;
            }


            if (_afterAttribute != null)
            {
                await this._afterAttribute.AfterReturn(aspectContext, aspectContext.ReturnValue);
            }
        }
    }

異常返回增強器

    /// <summary>
    /// 異常返回增強器
    /// </summary>
    internal class AspectAfterThrowsInterceptor : IAdvice
    {
        private readonly AspectAfterThrows _aspectThrowing;
        private readonly bool _isFromAround;
        public AspectAfterThrowsInterceptor(AspectAfterThrows throwAttribute, bool isFromAround = false)
        {
            _aspectThrowing = throwAttribute;
            _isFromAround = isFromAround;
        }

        public async Task OnInvocation(AspectContext aspectContext, AspectDelegate next)
        {
            try
            {
                if (!_isFromAround) await next.Invoke(aspectContext);
            }
            finally
            {
                //只有目標方法出現異常才會走 增強的方法出異常不要走
                if (aspectContext.Exception != null)
                {
                    Exception ex = aspectContext.Exception;
                    if (aspectContext.Exception is TargetInvocationException targetInvocationException)
                    {
                        ex = targetInvocationException.InnerException;
                    }

                    if (ex == null)
                    {
                        ex = aspectContext.Exception;
                    }

                    var currentExType = ex.GetType();

                    if (_aspectThrowing.ExceptionType == null || _aspectThrowing.ExceptionType == currentExType)
                    {
                        await _aspectThrowing.AfterThrows(aspectContext, aspectContext.Exception);
                    }
                }
            }
        }
    }

2. 組裝增強器們成為一個呼叫鏈

image
image

每一個node的有三個資訊,如下

    /// <summary>
    /// 攔截node組裝
    /// </summary>
    internal class AspectMiddlewareComponentNode
    {
        /// <summary>
        /// 下一個
        /// </summary>
        public AspectDelegate Next;

        /// <summary>
        /// 執行器
        /// </summary>
        public AspectDelegate Process;

        /// <summary>
        /// 元件
        /// </summary>
        public Func<AspectDelegate, AspectDelegate> Component;
    }

採用LinkedList來構建我們的拉鍊式呼叫, 我們把上面的每個增強器作為一個個middeware,新增進來。

    internal class AspectMiddlewareBuilder
    {
        private readonly LinkedList<AspectMiddlewareComponentNode> Components = new LinkedList<AspectMiddlewareComponentNode>();

        /// <summary>
        /// 新增攔截器鏈
        /// </summary>
        /// <param name="component"></param>
        public void Use(Func<AspectDelegate, AspectDelegate> component)
        {
            var node = new AspectMiddlewareComponentNode
            {
                Component = component
            };

            Components.AddLast(node);
        }

        /// <summary>
        /// 構建攔截器鏈
        /// </summary>
        /// <returns></returns>
        public AspectDelegate Build()
        {
            var node = Components.Last;
            while (node != null)
            {
                node.Value.Next = GetNextFunc(node);
                node.Value.Process = node.Value.Component(node.Value.Next);
                node = node.Previous;
            }

            return Components.First.Value.Process;
        }

        /// <summary>
        /// 獲取下一個
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        private AspectDelegate GetNextFunc(LinkedListNode<AspectMiddlewareComponentNode> node)
        {
            return node.Next == null ? ctx => Task.CompletedTask : node.Next.Value.Process;
        }
    }

然後build方法會構建成一個一層巢狀一層的pipeline管道(一個委託)

image
image

更多關於這種設計模式更多資訊請參考我另外一篇文章: 中介軟體(middlewware)模式

按照我們的需求構建的完整執行示意圖如下:

單個攔截器或者切面
image
image
多個攔截器或者切面
image
image

生成代理類代理目標方法去執行上面構造的攔截器鏈

這一步就簡單了,如果檢測到目標有打攔截器註解,則會給這個類動態建立一個proxy類,

生成代理類用的是castle.core的dynamic元件

預設的是Class+virtual的方式對目標方法進行攔截

image
image

注意:考慮到效能,在專案啟動的時候把構建好進行快取,然後再攔截器裡面使用

 

好了,攔截器和切面介紹到此,更多教程請參考專案wiki(教程很詳細哦,別忘記給個star)

https://github.com/yuzd/Autofac.Annotation/wiki

 

關注公眾號一起學習

Autofac實現攔截器和切面程式設計