Autofac的切面編程實現

 

面向切面編程:Autofac.Annotation擴展組件是我開源的一款利用打標籤完成autofac容器的注入組件。

//github.com/yuzd/Autofac.Annotation

我們之前介紹了利用Aspect標籤來完成攔截器功能

Aspect是一對一的方式,我想要某個class開啟攔截器功能我需要針對每個class去配置。 詳情請點擊

比如說 我有2個 controller 每個controller都有2個action方法,


    [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;
        }
    }

如果我需要這2個controller的action方法都在執行方法前打log 在方法執行後打log 按照上一節Aspect的話 我需要每個controller都要配置。如果我有100個controller的畫我就需要配置100次,這樣我覺得太麻煩了。所以我參考了Spring的Pointcut切面編程的方式實現了一套類似的,下面看如何用Pointcut的方式方便的配置一種切面去適用於N個對象。

定義一個切面:創建一個class 上面打上Pointcut的標籤 如下:

Pointcut標籤類有如下屬性:

屬性名 說明
Name 名稱Pointcut切面的名稱(默認為空,和攔截方法進行匹配,參考下面說明)
RetType 匹配目標類的方法的返回類型(默認是%)
NameSpace 匹配目標類的namespace(默認是%)
ClassName 匹配目標類的類名稱(必填)
MethodName 匹配目標類的方法名稱(默認是%)

匹配算法

image 舉例:

匹配結果 匹配模板 要匹配的字符串
匹配結果:true “%” “”
匹配結果:true “%” ” “
匹配結果:true “%” “asdfa asdf asdf”
匹配結果:true “%” “%”
匹配結果:false “_” “”
匹配結果:true “_” ” “
匹配結果:true “_” “4”
匹配結果:true “_” “C”
匹配結果:false “_” “CX”
匹配結果:false “[ABCD]” “”
匹配結果:true “[ABCD]” “A”
匹配結果:true “[ABCD]” “b”
匹配結果:false “[ABCD]” “X”
匹配結果:false “[ABCD]” “AB”
匹配結果:true “[B-D]” “C”
匹配結果:true “[B-D]” “D”
匹配結果:false “[B-D]” “A”
匹配結果:false “[^B-D]” “C”
匹配結果:false “[^B-D]” “D”
匹配結果:true “[^B-D]” “A”
匹配結果:true “%TEST[ABCD]XXX” “lolTESTBXXX”
匹配結果:false “%TEST[ABCD]XXX” “lolTESTZXXX”
匹配結果:false “%TEST[^ABCD]XXX” “lolTESTBXXX”
匹配結果:true “%TEST[^ABCD]XXX” “lolTESTZXXX”
匹配結果:true “%TEST[B-D]XXX” “lolTESTBXXX”
匹配結果:true “%TEST[^B-D]XXX” “lolTESTZXXX”
匹配結果:true “%Stuff.txt” “Stuff.txt”
匹配結果:true “%Stuff.txt” “MagicStuff.txt”
匹配結果:false “%Stuff.txt” “MagicStuff.txt.img”
匹配結果:false “%Stuff.txt” “Stuff.txt.img”
匹配結果:false “%Stuff.txt” “MagicStuff001.txt.img”
匹配結果:true “Stuff.txt%” “Stuff.txt”
匹配結果:false “Stuff.txt%” “MagicStuff.txt”
匹配結果:false “Stuff.txt%” “MagicStuff.txt.img”
匹配結果:true “Stuff.txt%” “Stuff.txt.img”
匹配結果:false “Stuff.txt%” “MagicStuff001.txt.img”
匹配結果:true “%Stuff.txt%” “Stuff.txt”
匹配結果:true “%Stuff.txt%” “MagicStuff.txt”
匹配結果:true “%Stuff.txt%” “MagicStuff.txt.img”
匹配結果:true “%Stuff.txt%” “Stuff.txt.img”
匹配結果:false “%Stuff.txt%” “MagicStuff001.txt.img”
匹配結果:true “%Stuff%.txt” “Stuff.txt”
匹配結果:true “%Stuff%.txt” “MagicStuff.txt”
匹配結果:false “%Stuff%.txt” “MagicStuff.txt.img”
匹配結果:false “%Stuff%.txt” “Stuff.txt.img”
匹配結果:false “%Stuff%.txt” “MagicStuff001.txt.img”
匹配結果:true “%Stuff%.txt” “MagicStuff001.txt”
匹配結果:true “Stuff%.txt%” “Stuff.txt”
匹配結果:false “Stuff%.txt%” “MagicStuff.txt”
匹配結果:false “Stuff%.txt%” “MagicStuff.txt.img”
匹配結果:true “Stuff%.txt%” “Stuff.txt.img”
匹配結果:false “Stuff%.txt%” “MagicStuff001.txt.img”
匹配結果:false “Stuff%.txt%” “MagicStuff001.txt”
匹配結果:true “%Stuff%.txt%” “Stuff.txt”
匹配結果:true “%Stuff%.txt%” “MagicStuff.txt”
匹配結果:true “%Stuff%.txt%” “MagicStuff.txt.img”
匹配結果:true “%Stuff%.txt%” “Stuff.txt.img”
匹配結果:true “%Stuff%.txt%” “MagicStuff001.txt.img”
匹配結果:true “%Stuff%.txt%” “MagicStuff001.txt”
匹配結果:true “?Stuff?.txt?” “1Stuff3.txt4”
匹配結果:false “?Stuff?.txt?” “1Stuff.txt4”
匹配結果:false “?Stuff?.txt?” “1Stuff3.txt”
匹配結果:false “?Stuff?.txt?” “Stuff3.txt4”

    // *Controller 代表匹配 只要是Controller結尾的類都能匹配
    // Get* 代表上面匹配成功的類下 所以是Get打頭的方法都能匹配
    [Pointcut(ClassName = "*Controller",MethodName = "Get*")]
    public class LoggerPointCut
    {
        
    }

定義好了一個Pointcut切面後 需要定義這個切面的攔截方法

配合Pointcut切面標籤,可以在打了這個標籤的class下定義攔截方法, 在方法上得打上特定的標籤,有如下幾種:

切面執行方法上打標籤種類 說明
Before標籤 在匹配成功的類的方法執行前執行
After標籤 在匹配成功的類的方法執行後執行
Around標籤 承接了 匹配成功的類的方法的執行權(如果一個切面配置了Around又配置了Before或者After,那麼會只執行Around)

以上3種標籤有一個可選的參數:Name (默認為空,可以和Pointcut的Name進行mapping)

  • 因為一個class上可以打多個Pointcut切面,一個Pointcut切面可以根據name去匹配對應攔截方法
    // *Controller 代表匹配 只要是Controller結尾的類都能匹配
    // Get* 代表上面匹配成功的類下 所以是Get打頭的方法都能匹配
    [Pointcut(ClassName = "*Controller",MethodName = "Get*")]
    public class LoggerPointCut
    {
        /// <summary>
        /// 打上Before標籤 代表滿足匹配的方法 在執行之前會執行下面的Before()方法
        /// </summary>
        [Before]
        public void Befor()
        {
            Console.WriteLine("before");
        }

        /// <summary>
        /// 打上After標籤 代表滿足匹配的方法 在執行之前會執行下面的After()方法
        /// </summary>
        [After]
        public void After()
        {
            Console.WriteLine("after");
        }
    }

如果是用Around環繞的話


    // *Controller 代表匹配 只要是Controller結尾的類都能匹配
    // Get* 代表上面匹配成功的類下 所以是Get打頭的方法都能匹配
    [Pointcut(ClassName = "*Controller",MethodName = "Get*")]
    public class LoggerPointCut
    {
        
        /// <summary>
        /// 打上Around標籤 承接了 匹配成功的類的方法的執行權
        /// </summary>
        /// <param name="context"></param>
        [Around]
        public void Around(AspectContext context)
        {
            //執行原目標方法前
            Console.WriteLine(context.InvocationContext.MethodInvocationTarget.Name + "-->Start");
            //執行原目標方法
            context.InvocationProceedInfo.Invoke();
            //執行原目標方法後
            Console.WriteLine(context.InvocationContext.MethodInvocationTarget.Name + "-->End");
        }
    }

執行方法的參數說明:

執行方法的參數可以是DI容器的類型,會在執行時自動注入進來,可以使用Autowired,Value等標籤來修飾參數。

還有一個特殊的執行參數可以注入,那就是AspectContext,這個類型裏面你可以獲取到被攔截的方法的信息,以及執行原方法的委託。

注意:這個執行後 有2種

  • 正常執行成功
  • 有異常,若方法參數注入了AspectContext 那麼可以通過Exception屬性值獲得異常內容

按照上面的配置

  • ProductController.GetProduct 會被匹配
  • UserController.GetUser 會被匹配

在執行上面這2個方法的時候會

  • 先執行 LoggerPointCut.Before方法
  • 再執行 LoggerPointCut.After方法

利用Autofac的這個開源擴展組件很方便的實現切面編程,總結切面編程三步驟

  • 1.定義一個切面,編寫要匹配的目標類的方法(採用sql的like方式匹配),所以一個切面可以攔截n個目標
  • 2.定義對應的攔截方法
  • 3.執行被匹配的方法時會執行對應的攔截方法