Skip to content

4.Aspect拦截器

俞正东 edited this page Nov 13, 2022 · 20 revisions

前提条件

自己new一个对象不能实现拦截器功能,必须得从DI容器拿到的对象才能具备拦截器功能 可以参考 我写的文章介绍

拦截器原理简单介绍

用了Castle.Core组件 把你想要实现拦截器的目标类生成一个代理类。 然后织入拦截器,有2种方式

  1. 类拦截器: class + 方法为virtual的方式
    • 这种方式需要 从容器中是根据一个classType来获取到目标实例

image

  1. 接口型拦截器: interface + 方法重写的方式
    • 这种方式需要 从容器中是根据一个interfaceType来获取到目标实例

image

ps: 图片参考了蒋老师的

拦截器

如果你的class里面存在任一方法上面有打上下方列的拦截器标签,那么就会被框架认为这个class需要被代理包装,还可以根据InterceptorType属性值设定你是哪种方式的拦截器

InterceptorType属性 解释
Class 使用class的虚方法模式 【默认方式】
Interface 使用接口模式

目的是打个标签就能够拦截目标方法

使得我们自定义的方法能够

  • 在指定的目标方法执行之前先执行(比如参数校验)
  • 或者在指定的目标方法执行之后执行(比如说检验返回值,或其他收尾工作)
  • 或者环绕目标的方法,比如日志or事务:TransactionScope或者记录方法执行的时间或者日志
拦截器标签 拦截器类型 使用说明
AspectArround(抽象标签类) 环绕拦截 重写OnInvocation方法
AspectBefore(抽象标签类) 前置拦截器 重写Before方法
AspectAfter(抽象标签类) 后置拦截器(不管目标方法成功失败都会执行) 重写After方法
AspectAfterReturn(抽象标签类) 后置拦截器(只有目标方法成功才会执行) 重写AfterReturn方法
AspectAfterThrows(抽象标签类) 错误拦截器(只有目标方法失败才会执行) 重写AfterThrows方法

每个拦截器方法都有一个

拦截器的方法参数 AspectContext 属性说明

名称 说明
ComponentContext DI容器,可以从中取得你已注册的实例
Arguments 目标方法的参数
TargetMethod 目标方法的MethodInfo
ReturnValue 目标方法的返回
Method 目标方法的代理方法MethodInfo

前置拦截器 (Before)

  1. 首先要自己写一个类继承 前置拦截器AspectBefore(抽象标签类)
  2. 实现该抽象类的Before方法
    public class TestHelloBefore:AspectBefore
    {
        public override Task Before(AspectContext aspectContext)
        {
            Console.WriteLine("TestHelloBefore");
            return Task.CompletedTask;
        }
    }
    
    [Component]//Component注解里面有一个属性叫做InterceptorType默认为Class+virtual来实现代理包装
    public class TestHello
    {

        [TestHelloBefore]//打上拦截器
        public virtual void Say()
        {
            Console.WriteLine("Say");
        }
    }

前置拦截器方法的执行顺序为:先执行 TestHelloBefor的Before方法再执行你的Say方法

后置拦截器 (After) 不管目标方法成功还是抛异常都会执行

  1. 首先要自己写一个类继承后置拦截器AspectAfter(抽象标签类)
  2. 实现该抽象类的After方法
    public class TestHelloAfter:AspectAfter
    {
        //这个 returnValue 如果目标方法正常返回的话 那就是目标方法的返回值
        // 如果目标方法抛异常的话 那就是异常本身
        public override Task After(AspectContext aspectContext,object returnValue)
        {
            Console.WriteLine("TestHelloAfter");
            return Task.CompletedTask;
        }
    }
    
    [Component]//Component注解里面有一个属性叫做InterceptorType默认为Class+virtual来实现代理包装
    public class TestHello
    {

        [TestHelloAfter]//打上拦截器
        public virtual void Say()
        {
            Console.WriteLine("Say");
        }
    }

执行顺序为:先执行你的SayAfter方法再执行 TestHelloAfter的After方法

这里要特别注意的是 After 拦截器 是不管你的目标方法(SayAfter是成功还是抛异常) 都被会执行到的

成功返回拦截器 (AfterReturn)只有目标方法成功的时候才会执行

  1. 首先要自己写一个类继承拦截器AspectReturn(抽象标签类)
  2. 实现该抽象类的After方法
    public class TestHelloAfterReturn:AspectAfterReturn
    {
        //result 是目标方法的返回 (如果目标方法是void 则为null)
        public override Task AfterReturn(AspectContext aspectContext, object result)
        {
            Console.WriteLine("TestHelloAfterReturn");
            return Task.CompletedTask;
        }
    }
    
    [Component]//Component注解里面有一个属性叫做InterceptorType默认为Class+virtual来实现代理包装
    public class TestHello
    {

        [TestHelloAfterReturn]//打上拦截器
        public virtual void Say()
        {
            Console.WriteLine("Say");
        }
    }

执行顺序为:先执行你的Say方法再执行 TestHelloAfterReturn的AfterReturn方法

如果你的Say方法抛出异常那么就不会执行TestHelloAfterReturn的AfterReturn方法

异常拦截器 (AfterThrows)

  1. 首先要自己写一个类继承拦截器AspectReturn(抽象标签类)
  2. 实现该抽象类的After方法
   public class TestHelloAfterThrows:AspectAfterThrows
    {
       
        public override Task AfterThrows(AspectContext aspectContext, Exception exception)
        {
            Console.WriteLine(exception.Message);
            return Task.CompletedTask;
        }
    }
    
    [Component]//Component注解里面有一个属性叫做InterceptorType默认为Class+virtual来实现代理包装
    public class TestHello
    {

        [TestHelloAfterThrows]//打上拦截器
        public virtual void Say()
        {
            Console.WriteLine("Say");
            throw new ArgumentException("exception");
        }
    }
    

执行顺序为:先执行你的Say方法再执行 TestHelloAfterThrows的AfterThrows方法

如果你的Say方法不抛出异常那么就不会执行 TestHelloAfterThrows的AfterThrows方法

环绕拦截器(Around)

注意:OnInvocation方法除了AspectContext参数以外 还有一个 AspectDelegate _next 参数, 需要在你的Around拦截器方法显示调用 _next(aspectContext) 方法,否则目标方法不会被调用

  1. 首先要自己写一个类继承拦截器AspectArround(抽象标签类)
  2. 实现该抽象类的OnInvocation方法
    public class TestHelloAround:AspectArround
    {
        public override async Task OnInvocation(AspectContext aspectContext, AspectDelegate _next)
        {
            Console.WriteLine("around start");
            await _next(aspectContext);
            Console.WriteLine("around end");
        }
    }
    
    [Component]//Component注解里面有一个属性叫做InterceptorType默认为Class+virtual来实现代理包装
    public class TestHello
    {

        [TestHelloAround]//打上拦截器
        public virtual void Say()
        {
            Console.WriteLine("Say");
        }
    }

方法的执行顺序为:

  1. 先执行TestHelloAround的OnInvocation方法
  2. 然后TestHelloAround的OnInvocation方法里面执行的 await _next(aspectContext); 就会执行被拦截方法TestHello的Say方法;

如果Around Befor After AfterReturn AfterThrows 一起用

正常case

    [Component]//Component注解里面有一个属性叫做InterceptorType默认为Class+virtual来实现代理包装
    public class TestHello
    {

        [TestHelloAround,TestHelloBefore,TestHelloAfter,TestHelloAfterReturn,TestHelloAfterThrows]//打上拦截器
        public virtual void Say()
        {
            Console.WriteLine("Say");
        }
    }

代码的执行顺序为:

  1. 先执行TestHelloAround,打印 “around start” 然后执行到里面的_next(aspectContext)会触发下面
  2. 执行TestHelloBefore 打印 “TestHelloBefore”
  3. 执行目标方法 打印 "Say"
  4. 打印 “around end” TestHelloAround运行结束
  5. 执行TestHelloAfter 打印 “TestHelloAfter”
  6. 因为是目标方法成功执行 TestHelloAfterReturn 打印 “TestHelloAfterReturn”

由于是目标方法成功返回 没有异常,所以不会走进TestHelloAfterThrows

异常case

    [Component]//Component注解里面有一个属性叫做InterceptorType默认为Class+virtual来实现代理包装
    public class TestHello
    {

        [TestHelloAround,TestHelloBefore,TestHelloAfter,TestHelloAfterReturn,TestHelloAfterThrows]
        public virtual void Say()
        {
            Console.WriteLine("Say");
            throw new ArgumentException("exception");
        }
    }

代码的执行顺序为:

  1. 先执行TestHelloAround,打印 “around start” 然后执行到里面的_next(aspectContext)会触发下面
  2. 执行TestHelloBefore 打印 “TestHelloBefore”
  3. 执行目标方法 打印 "Say"
  4. 打印 “around end” TestHelloAround运行结束
  5. 执行TestHelloAfter 打印 “TestHelloAfter”
  6. 因为是目标方法异常 执行 TestHelloAfterThrows 打印异常信息

如上述执行顺序和spring是一致的

image

多组的情况

 public class TestHelloBefore1:AspectBefore
    {
        public override Task Before(AspectContext aspectContext)
        {
            Console.WriteLine("TestHelloBefore1");
            return Task.CompletedTask;
        }
    }
    
    public class TestHelloAfter1:AspectAfter
    {
        //这个 returnValue 如果目标方法正常返回的话 那就是目标方法的返回值
        // 如果目标方法抛异常的话 那就是异常本身
        public override Task After(AspectContext aspectContext,object returnValue)
        {
            Console.WriteLine("TestHelloAfter1");
            return Task.CompletedTask;
        }
    }
    
    public class TestHelloAfterReturn1:AspectAfterReturn
    {
        //result 是目标方法的返回 (如果目标方法是void 则为null)
        public override Task AfterReturn(AspectContext aspectContext, object result)
        {
            Console.WriteLine("TestHelloAfterReturn1");
            return Task.CompletedTask;
        }
    }
    
    public class TestHelloAround1:AspectArround
    {
        public override async Task OnInvocation(AspectContext aspectContext, AspectDelegate _next)
        {
            Console.WriteLine("TestHelloAround1 start");
            await _next(aspectContext);
            Console.WriteLine("TestHelloAround1 end");
        }
    }
    
    public class TestHelloAfterThrows1:AspectAfterThrows
    {
       
        public override Task AfterThrows(AspectContext aspectContext, Exception exception)
        {
            Console.WriteLine("TestHelloAfterThrows1");
            return Task.CompletedTask;
        }
    }
    
    

    //////////////////////////////////////////////
    public class TestHelloBefore2:AspectBefore
    {
        public override Task Before(AspectContext aspectContext)
        {
            Console.WriteLine("TestHelloBefore2");
            return Task.CompletedTask;
        }
    }
    
    public class TestHelloAfter2:AspectAfter
    {
        //这个 returnValue 如果目标方法正常返回的话 那就是目标方法的返回值
        // 如果目标方法抛异常的话 那就是异常本身
        public override Task After(AspectContext aspectContext,object returnValue)
        {
            Console.WriteLine("TestHelloAfter2");
            return Task.CompletedTask;
        }
    }
    
    public class TestHelloAfterReturn2:AspectAfterReturn
    {
        //result 是目标方法的返回 (如果目标方法是void 则为null)
        public override Task AfterReturn(AspectContext aspectContext, object result)
        {
            Console.WriteLine("TestHelloAfterReturn2");
            return Task.CompletedTask;
        }
    }
    
    public class TestHelloAround2:AspectArround
    {
        public override async Task OnInvocation(AspectContext aspectContext, AspectDelegate _next)
        {
            Console.WriteLine("TestHelloAround2 start");
            await _next(aspectContext);
            Console.WriteLine("TestHelloAround2 end");
        }
    }
    
    public class TestHelloAfterThrows2:AspectAfterThrows
    {
       
        public override Task AfterThrows(AspectContext aspectContext, Exception exception)
        {
            Console.WriteLine("TestHelloAfterThrows2");
            return Task.CompletedTask;
        }
    }
    
    [Component]
    public class TestHello
    {

        [
            TestHelloAround1(GroupName = "Aspect1",OrderIndex = 10),
            TestHelloBefore1(GroupName = "Aspect1",OrderIndex = 10),
            TestHelloAfter1(GroupName = "Aspect1",OrderIndex = 10),
            TestHelloAfterReturn1(GroupName = "Aspect1",OrderIndex = 10),
            TestHelloAfterThrows1(GroupName = "Aspect1",OrderIndex = 10)
        ]
        [
            TestHelloAround2(GroupName = "Aspect2",OrderIndex = 1),
            TestHelloBefore2(GroupName = "Aspect2",OrderIndex = 1),
            TestHelloAfter2(GroupName = "Aspect2",OrderIndex = 1),
            TestHelloAfterReturn2(GroupName = "Aspect2",OrderIndex = 1),
            TestHelloAfterThrows2(GroupName = "Aspect2",OrderIndex = 1)
        ]
        public virtual void SayGroup()
        {
            Console.WriteLine("SayGroup");
        }
    }

如上面的代码在目标方法上打了2组 那么对应的执行顺序是:

  1. 先执行TestHelloAround2 打印 "TestHelloAround2 start" 然后执行到里面的_next(aspectContext)会触发下面
  2. 执行TestHelloBefore2 打印 “TestHelloBefore2” 然后进入到
  3. 执行TestHelloAround1 打印 “TestHelloAround1 start” 然后执行到里面的 _next(aspectContext)会触发下面
  4. 执行TestHelloBefore1 打印 “TestHelloBefore1”
  5. 执行目标方法 SayGroup 打印 "SayGroup"
  6. TestHelloAround1运行结束 打印 “TestHelloAround1 end”
  7. 执行 TestHelloAfter1 打印 “TestHelloAfter1”
  8. 执行 TestHelloAfterReturn1 打印 “TestHelloAfterReturn1”
  9. TestHelloAround2运行结束 打印 “TestHelloAround2 end”
  10. 执行 TestHelloAfter2 打印 “TestHelloAfter2”
  11. 执行 TestHelloAfterReturn2 打印 “TestHelloAfterReturn2”

执行的顺序如下图

image

当拦截标签打在了父类/接口的方法上的场景

  public class TestHelloBefore:AspectBefore
    {
        public override Task Before(AspectContext aspectContext)
        {
            Console.WriteLine("TestHelloBefore");
            return Task.CompletedTask;
        }
    }
    public interface IHello
    {
        [TestHelloBefore]//打上拦截器 也会生效,注意看下面的文字说明
        void SayHello();
    } 
    public abstract class TestParentHello
    {

        [TestHelloBefore]//打上拦截器 也会生效
        public virtual void Say()
        {
            Console.WriteLine("Say");
        }
    }    
    [Component]
    public class TestHello:TestParentHello,IHello
    {
        public void SayHello()
        {
        }
    }

本文介绍的拦截器标签的方式实现切面编程,是基于组件Castle创建代理类来实现的, 默认是采用继承原来的class,然后把virtual的方法进行重写,如果你原来的class有继承一些接口的话,代理类也会继承,并且aop也会生效, 只不过需要显示接口的方式调用才行 如下图:

image

当有多个拦截器时,如何排除指定拦截器

public interface InterIgnoreAop
{
    [ExceptionAop1Attribute]
    [ExceptionAop2Attribute]
    void sayIgnore();
}

[Component]
public class IgnoreAop1 : InterIgnoreAop
{

    // 如果不用[IgnoreAop]注解的话 会走2个aop逻辑:ExceptionAop1Attribute 和 ExceptionAop2Attribute
    [IgnoreAop] // 针对方法关闭AOP,那么2个aop逻辑都不会走了
    // [IgnoreAop(Target = new[] { typeof(ExceptionAop1Attribute) })] // 针对方法关闭某个AOP,就只会走ExceptionAop2Attribute
    public virtual void sayIgnore()
    {
    }
}

进阶玩法:批量拦截, 切面编程

业务场景使用例子

缓存拦截器

https://github.com/yuzd/Autofac.Annotation/issues/12