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 | 匹配目標類的方法名稱(默認是%) |
匹配算法
舉例:
匹配結果 | 匹配模板 | 要匹配的字符串 |
---|---|---|
匹配結果: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.執行被匹配的方法時會執行對應的攔截方法