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; }
}
這一組註解是暴露給外部使用,來蒐集哪些類的哪些方法需要增強
接下來需要去針對性去實現每一種增強器要做的事情
定義一個增強器介面IAdvice
internal interface IAdvice
{
/// <summary>
/// 攔截器方法
/// </summary>
/// <param name="aspectContext">執行上下文</param>
/// <param name="next">下一個增強器</param>
/// <returns></returns>
Task OnInvocation(AspectContext aspectContext, AspectDelegate next);
}
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. 組裝增強器們成為一個呼叫鏈
每一個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管道(一個委託)
更多關於這種設計模式更多資訊請參考我另外一篇文章: 中介軟體(middlewware)模式
按照我們的需求構建的完整執行示意圖如下:
單個攔截器或者切面
多個攔截器或者切面
生成代理類代理目標方法去執行上面構造的攔截器鏈
這一步就簡單了,如果檢測到目標有打攔截器註解,則會給這個類動態建立一個proxy類,
生成代理類用的是castle.core的dynamic元件
預設的是Class+virtual的方式對目標方法進行攔截
注意:考慮到效能,在專案啟動的時候把構建好進行快取,然後再攔截器裡面使用
好了,攔截器和切面介紹到此,更多教程請參考專案wiki(教程很詳細哦,別忘記給個star)
https://github.com/yuzd/Autofac.Annotation/wiki
關注公眾號一起學習