-
Notifications
You must be signed in to change notification settings - Fork 52
5 Pointcut切面编程
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个对象。
Pointcut标签类有如下属性:
属性名 | 说明 |
---|---|
Name | 名称Pointcut切面的名称(默认为空,和拦截方法进行匹配,参考下面说明) |
RetType | 匹配目标类的方法的返回类型(默认是%) |
NameSpace | 匹配目标类的namespace(默认是%) |
ClassName | 匹配目标类的类名称(和下面的AttributeType参数二选一必填) |
AttributeType | 匹配特定的标签(和上面的ClassName参数二选一必填) |
AttributeFlag | 当指定了AttributeType时可以用此值来扩展(具体看下面) |
MethodName | 匹配目标类的方法名称(默认是%) |
- NONE(默认值,代表打了AttributeType才匹配)
- AssignableFrom 代表打了AttributeType或者AttributeType的父类(排除Abstract类型)的都会被匹配
- AssignableTo 代表打了AttributeType或者AttributeType的子类的都会被匹配
可以看文末举例
// *Controller 代表匹配 只要是Controller结尾的类都能匹配
// Get* 代表上面匹配成功的类下 所以是Get打头的方法都能匹配
[Pointcut(Class = "*Controller",Method = "Get*")]
public class LoggerPointCut
{
}
// *Controller 代表匹配 只要是Controller结尾的类都能匹配
// Get* 代表上面匹配成功的类下 所以是Get打头的方法都能匹配
[Pointcut(ClassName = "*Controller",MethodName = "Get*")]
public class LoggerPointCut
{
}
// 打了TestPointAttributes1注解的Component都会被识别
[Pointcut(AttributeType = typeof(TestPointAttributes1))]
// 因为设置了AssignableFrom 代表打了TestPointAttributes1和它的父类(排除Abstract类型)都会被识别
//[Pointcut(AttributeType = typeof(TestPointAttributes1),AttributeFlag = AssignableFlag.AssignableFrom)]
public class LoggerPointCut
{
}
配合Pointcut切面标签,可以在打了这个标签的class下定义拦截方法, 在方法上得打上特定的标签,有如下几种:
切入点 | 说明 |
---|---|
Before标签 | 在匹配成功的类的方法执行前执行 |
After标签 | 在匹配成功的类的方法执行后执行(不管目标方法成功还是失败) |
AfterReturn标签 | 在匹配成功的类的方法执行后执行(只是目标方法成功) |
AfterThrows标签 | 在匹配成功的类的方法执行后执行(只是目标方法抛异常时) |
Around标签 | 环绕目标方法,承接了匹配成功的类的方法的执行权 |
以上3种标签有一个可选的参数:Name (默认为空,可以和Pointcut的Name进行mapping)
- 因为一个class上可以打多个Pointcut切面,一个Pointcut切面可以根据name去匹配对应拦截方法
- Around切入点 必须要指定 AspectContext类型 和 AspectDelegate类型的2个参数,且返回类型要是Task 否则会报错
- 除了Around切入点以外其他的切入点的返回值只能是Task或者Void 否则会报错
- 除了Around切入点以外其他的切入点可以指定 AspectContext类型 参数注入进来
- After切入点 可以指定Returing参数,可以把目标方法的返回注入进来,如果目标方法抛异常则是异常本身
- AfterReturn切入点 可以指定Returing参数,可以把目标方法的返回注入进来
- AfterThrows切入点 可以指定 Throwing参数,可以把目标方法抛出的异常注入进来
- 只要你参数类型是你注册到DI容器,运行时会自动从DI容器把类型注入进来
- 如果是指定Attribute扫描的,支持方法注入这个Attribute进来《参考这个》
- 支持方法注入当前是哪个PointCut《参考这个》
- 可以使用Autowired,Value标签来修饰参数
/// <summary>
/// 第一组切面
/// </summary>
[Pointcut(NameSpace = "Autofac.Annotation.Test.test6",Class = "Pointcut*",OrderIndex = 1)]
public class PointcutTest1
{
[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");
}
}
/// <summary>
/// 第二组切面
/// </summary>
[Pointcut(NameSpace = "Autofac.Annotation.Test.test6",Class = "Pointcut*",OrderIndex = 0)]
public class PointcutTest2
{
[Around]
public async Task Around(AspectContext context,AspectDelegate next)
{
Console.WriteLine("PointcutTest2.Around-start");
await next(context);
Console.WriteLine("PointcutTest2.Around-end");
}
[Before]
public void Before()
{
Console.WriteLine("PointcutTest2.Before");
}
[After]
public void After()
{
Console.WriteLine("PointcutTest2.After");
}
[AfterReturn(Returing = "value")]
public void AfterReturn(object value)
{
Console.WriteLine("PointcutTest2.AfterReturn");
}
[AfterThrows(Throwing = "ex")]
public void Throwing(Exception ex)
{
Console.WriteLine("PointcutTest2.Throwing");
}
}
[Component]
public class Pointcut1Controller
{
//正常case
public virtual void TestSuccess()
{
Console.WriteLine("Pointcut1Controller.TestSuccess");
}
//异常case
public virtual void TestThrow()
{
Console.WriteLine("Pointcut1Controller.TestThrow");
throw new ArgumentException("ddd");
}
}
[Component]
public class Pointcut2Controller
{
//正常case
public virtual void TestSuccess()
{
Console.WriteLine("Pointcut1Controller.TestSuccess");
}
//异常case
public virtual void TestThrow()
{
Console.WriteLine("Pointcut1Controller.TestThrow");
throw new ArgumentException("ddd");
}
}
- Pointcut1Controller.TestSuccess 和 TestThrow 2个方法 会被匹配
- Pointcut2Controller.TestThrow 和 TestThrow 2个方法 会被匹配
单个切面顺序如下图
多个切面执行的顺序如下图
关于顺序是和上一节说的[Aspect]是一致的
[Pointcut(NameSpace = "Autofac.Annotation.Test.issue31", AttributeType = typeof(ExceptionAttIgnore1Attribute))]
public class TestIgnoreAopClass1
{
[Before]
public void befor()
{
}
}
[Pointcut(NameSpace = "Autofac.Annotation.Test.issue31", AttributeType = typeof(ExceptionAttIgnore2Attribute))]
public class TestIgnoreAopClass2
{
[Before]
public void befor()
{
}
}
public interface InterIgnoreAop2
{
[ExceptionAttIgnore1Attribute]
[ExceptionAttIgnore2Attribute]
void sayIgnore();
}
[Component]
public class IgnoreAop2 : InterIgnoreAop2
{
// 如果不打上[IgnoreAop]注解的话,那么切面TestIgnoreAopClass1和切面TestIgnoreAopClass2都会走
[IgnoreAop] //打上的话,就都不会走了
// [IgnoreAop(Target = new[] { typeof(TestIgnoreAopClass1)})] // 这样就只会走切面TestIgnoreAopClass2
public virtual void sayIgnore()
{
}
}
想必大家都知道在winform/wpf/asp.net等有异步上下文的场景中,同步代码调用异步代码且Wait异步结果会导致死锁问题(ASP.NET Core没有此问题)。
那么该问题在AOP中会是怎样呢?
举例:在wpf中同步的click事件中调用另外一个方法,这个方法是会被AOP的
//wpf的click事件
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
//参考下发代码 TestController的controllerTest方法是同步方法,会被AOP
var abd = container.Resolve<TestController>().controllerTest();
}
[Component]
public class TestController
{
[AfterAopTestAttribute]
public virtual string controllerTest()
{
Console.WriteLine(111);
return "abc";
}
}
public class AfterAopTestAttribute : AspectAfter
{
public override async Task After(AspectContext aspectContext, object result)
{
// 注意这里,当同步方法被aop ,当调用同步方法走到这里会被卡死
await aspectContext.ComponentContext.Resolve<Serial>().Send();
}
}
[Component]
public class Serial
{
public virtual async Task Send()
{
await Task.Delay(100);
Console.WriteLine("aaa");
}
}
当执行到After拦截器时候,
如果不加.ConfigureAwait(false) 会造成死锁,所以在本框架的aop执行流程里面,会自动将aop执行逻辑包裹.ConfigureAwait(false)
遇到阻塞死锁的时候会直接不等待,所以那上面代码的效果就是 await Task.Delay(100); 不会死锁,会异步
出去,不等待它了
另外本框架的aop执行逻辑会自动检测死锁,比如改成如下:
public class AfterAopTestAttribute : AspectAfter
{
public override async Task After(AspectContext aspectContext, object result)
{
// 会直接触发死锁检测,报异常出来
aspectContext.ComponentContext.Resolve<Serial>().Send().ConfigureAwait(false).GetAwaiter().GetResult();
}
}
本框架的aop执行自带死锁检测,调试的时候可以看到错误内容,可以定位到是哪个方法造成了死锁 检测到死锁会异常终止程序 至少不会卡死!
先走Pointcut的逻辑,然后再走Aspect的逻辑
tip: 打了Pointcut的class,框架会注册为单例,不要里面定义共享属性,会有并发问题(如下图)
匹配结果 | 匹配模板 | 要匹配的字符串 |
---|---|---|
匹配结果:true | "%" | "" |
匹配结果:true | "%" | " " |
匹配结果:true | "%" | "asdfa asdf asdf" |
匹配结果:true | "%" | "%" |
匹配结果:false | "_" | "" |
匹配结果:true | "_" | " " |
匹配结果:true | "_" | "4" |
匹配结果:true | "_" | "C" |
匹配结果:false | "_" | "CX" |
匹配结果:false | "[ABCD]" | "" |
匹配结果:true | "[ABCD]" | "A" |
匹配结果:false | "[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" // 不是B或者C或者D打头 |
匹配结果:true | "[^BCD]" | "A" // 不是B或者C或者D打头 |
匹配结果:false | "[^(abc)]" | "abc" //不是abc打头 |
匹配结果:true | "[^(abc)]" | "abd" |
匹配结果:false | "[^(abc)(def)]" | "def" /不是abc或者def打头 |
匹配结果:false | "[^(abc)(def)]" | "abc" |
匹配结果:true | "[^(abc)(def)]" | "edf" |
匹配结果:true | "[(abc)(def)]" | "abc" // 是abc或者def打头 |
匹配结果:false | "[(abc)]" | "def" |
匹配结果: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" |