Autofac的切面编程实现
面向切面编程:Autofac.Annotation扩展组件是我开源的一款利用打标签完成autofac容器的注入组件。
https://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.执行被匹配的方法时会执行对应的拦截方法
利用切面编程来自动加事物的后台系统例子
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎各位转载,转载文章之后须在文章页面明显位置给出作者和原文连接,谢谢。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构