.NET Core中實現AOP編程


AOP全稱Aspect Oriented Progarmming(面向切面編程),其實AOP對ASP.NET程序員來說一點都不神秘,你也許早就通過Filter來完成一些通用的功能,例如你使用Authorization Filter來攔截所有的用戶請求,驗證Http Header中是否有合法的token。或者使用Exception Filter來處理某種特定的異常。
你之所以可以攔截所有的用戶請求,能夠在期望的時機來執行某些通用的行為,是因為ASP.NET Core在框架級別預留了一些鈎子,他允許你在特定的時機注入一些行為。對ASP.NET Core應用程序來說,這個時機就是HTTP請求在執行MVC Action的中間件時。

顯然這個時機並不能滿足你的所有求,比如你在Repository層有一個讀取數據庫的方法:

public void GetUser()
{
    //Get user from db
}

你試圖得到該方法執行的時間,首先想到的方式就是在整個方法外面包一層用來計算時間的代碼:

public void GetUserWithTime()
{
    var stopwatch = Stopwatch.StartNew();
    try
    {
        //Get user from db
    }
    finally
    {
        stopwatch.Stop();
        Trace.WriteLine("Total" + stopwatch.ElapsedMilliseconds + "ms");
    }
}

如果僅僅是為了得到這一個方法的執行時間,這種方式可以滿足你的需求。問題在於你有可能還想得到DeleteUser或者UpdateUser等方法的執行時間。修改每一個方法並添加計算時間的代碼存在着明顯的code smell。
一個比較優雅的做法是給需要計算時間的方法標記一個Attribute:

[Time]
public void GetUser()
{
    //Get user from db
}

你把計算時間這個功能當做一個切面(Aspect)注入到了現有的邏輯中,這是一個AOP的典型應用。

在C#中使用AOP

C#中可以用來做AOP的開源類庫有若干個,比較流行的:

這些類庫之所以能夠實現AOP是因為他們有動態修改IL代碼的能力,這種能力又被稱為IL weaving。
還有的類庫把AOP和Dependency Injection結合在了一起,通過服務上注冊一個攔截器(Interceptor)的方式做達到AOP的目的,例如:

本文將使用一個C#開源項目aspect-injector來描述AOP的幾種常見的場景。
aspect-injector是一個非常輕量級的AOP類庫,麻雀雖小,但是已經能夠應對大部分AOP的應用場景:

  • 支持.NET Core
  • 支持對異步方法注入切面
  • 能夠把切面注入到方法、屬性和事件上
  • 支持Attribute的方式注入切面

注入計算執行時間的邏輯

在已有的方法上注入一段邏輯可以分為三種情況:

  1. 在方法執行前注入一段邏輯,例如注入統一的認證邏輯
  2. 在方法執行后注入一段邏輯,例如將結果寫入日志
  3. 方法前后同時注入邏輯,例如計算時間,又或者給整個方法內容包裹一個事務
    已知一個計算個數的方法如下:
public class SampleService
{
    public int GetCount()
    {
        Thread.Sleep(3000);
        return 10;
    }
}

為了將計算時間的邏輯包裹在現有的方法上,我們需要在被注入邏輯的方法上標記InjectAttribute

public class SampleService
{
    [Inject(typeof(TimeAspect))]
    public int GetCount()
    {
        Thread.Sleep(3000);
        return 10;
    }
}

TimeAspect就是我們將要注入的一個切面:

  [Aspect(Aspect.Scope.Global)]
  public class TimeAspect
    {
        [Advice(Advice.Type.Around, Advice.Target.Method)]
        public object HandleMethod(
        [Advice.Argument(Advice.Argument.Source.Name)] string name,
        [Advice.Argument(Advice.Argument.Source.Arguments)] object[] arguments,
        [Advice.Argument(Advice.Argument.Source.Target)] 
        Func<object[], object> method)
        {
            Console.WriteLine($"Executing method {name}");
            var sw = Stopwatch.StartNew();
            var result = method(arguments); //調用被注入切面的方法
            sw.Stop();
            Console.WriteLine($"method {name} in {sw.ElapsedMilliseconds} ms");
            return result;
        }
    }

大部分代碼是非常清晰的,我們只描述幾個重要的概念:
標記了AdviceAttribute的方法就是即將要注入到目標方法的切面邏輯,也就是說HandleMethod描述了如何計算時間。
Advice.Type.Around描述了同時在目標方法的前后都注入邏輯
方法參數Func<object[], object> method其實就代表目標方法

注入認證邏輯

試想你有如果干個服務,每個服務在執行前都要做安全認證,顯然安全認證的邏輯是可重用的,那我們就可以把認證的邏輯提取成一個切面(Aspect)。

[Inject(typeof(AuthorizationAspect))]
public class SampleService
{
    public void MethodA(Guid userId)
    {
        // Do something
    }

    public void MethodB(Guid userId)
    {
        // Do something
    }
}

AuthorizationAspect就是安全認證的邏輯:

[Aspect(Aspect.Scope.Global)]
public class AuthorizationAspect
{
    [Advice(Advice.Type.Before, Advice.Target.Method)]
    public void CheckAccess(
    [Advice.Argument(Advice.Argument.Source.Method)] MethodInfo method,
    [Advice.Argument(Advice.Argument.Source.Arguments)] object[] arguments)
    {
        if (arguments.Length == 0 || !(arguments[0] is Guid))
        {
            throw new ArgumentException($"{nameof(AuthorizationAspect)} expects 
            every target method to have Guid as the first parameter");
        }

        var userId = (Guid)arguments[0];
        if (!_securityService.HasPermission(userId, authorizationAttr.Permission))
        {
            throw new Exception($"User {userId} doesn't have 
            permission to execute method {method.Name}");
        }
    }
}

Advice.Type.Before描述了該邏輯會在被修改的方法前執行
通過object[] arguments得到了被修改方法的所有參數

AOP是面向對象編程中一種用來抽取公用邏輯,簡化業務代碼的方式,靈活使用AOP可以讓你的業務邏輯代碼不會過度臃腫,也是除了繼承之外另一種可復用代碼的方式。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM