Autofac实现拦截器和切面编程
Autofac.Annotation框架是我用.netcore写的一个注解式DI框架,基于Autofac参考 Spring注解方式所有容器的注册和装配,切面,拦截器等都是依赖标签来完成。
开源地址:https://github.com/yuzd/Autofac.Annotation
本期讲的是最新重构的功能,这个功能也是赋予了这个框架的无限可能,也是我觉得设计的比较好的地方, 今天来说说我是怎么设计的
切面和拦截器介绍
拦截器是什么?
可以帮助我们方便在执行目标方法的
前(Before) 后(After) 返回值时(AfterReturn) 抛错误时(AfterThrowing) 环绕(Around)
简单示例:
//自己实现一个拦截器
public class TestHelloBefore:AspectBefore
{
public override Task Before(AspectContext aspectContext)
{
Console.WriteLine("TestHelloBefore");
return Task.CompletedTask;
}
}
[Component]
public class TestHello
{
[TestHelloBefore]//打上拦截器
public virtual void Say()
{
Console.WriteLine("Say");
}
}
先执行 TestHelloBefor的Before方法再执行你的Say方法
更多使用示例请查看 Aspect拦截器
切面是什么?
定义一个切面(根据筛选器去实现满足条件的多个类的多个方法的“拦截器”
简单示例:
[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;
}
}
// *Controller 代表匹配 只要是Controller结尾的类都能匹配
// Get* 代表上面匹配成功的类下 所以是Get打头的方法都能匹配
[Pointcut(Class = "*Controller",Method = "Get*")]
public class LoggerPointCut
{
[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");
}
}
更多示例请查看 Pointcut切面编程
如何实现的
分为3步
1.搜集拦截算子(比如Before/After等这个我们叫算子) 2.构造拦截器链(按照上面图的方式把算子链接起来) 3.生成代理类代理目标方法去执行上面构造的拦截器链
1.搜集拦截算子
因为拦截器的使用是约定了要继承 AspectInvokeAttribute
/// <summary>
/// AOP拦截器 默认包含继承关系
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class AspectInvokeAttribute : Attribute
{
/// <summary>
/// 排序 值越低,优先级越高
/// </summary>
public int OrderIndex { get; set; }
/// <summary>
/// 分组名称
/// </summary>
public string GroupName { get; set; }
}
这一组注解是暴露给外部使用,来搜集哪些类的哪些方法需要增强
接下来需要去针对性去实现每一种增强器要做的事情
定义一个增强器接口IAdvice
internal interface IAdvice
{
/// <summary>
/// 拦截器方法
/// </summary>
/// <param name="aspectContext">执行上下文</param>
/// <param name="next">下一个增强器</param>
/// <returns></returns>
Task OnInvocation(AspectContext aspectContext, AspectDelegate next);
}
Before增强器
/// <summary>
/// 前置增强器
/// </summary>
internal class AspectBeforeInterceptor : IAdvice
{
private readonly AspectBefore _beforeAttribute;
public AspectBeforeInterceptor(AspectBefore beforeAttribute)
{
_beforeAttribute = beforeAttribute;
}
public async Task OnInvocation(AspectContext aspectContext, AspectDelegate next)
{
//先执行Before逻辑
await this._beforeAttribute.Before(aspectContext);
//在走下一个增强器
await next.Invoke(aspectContext);
}
}
After增强器
/// <summary>
/// 后置增强器
/// </summary>
internal class AspectAfterInterceptor : IAdvice
{
private readonly AspectAfter _afterAttribute;
private readonly bool _isAfterAround;
public AspectAfterInterceptor(AspectAfter afterAttribute, bool isAfterAround = false)
{
_afterAttribute = afterAttribute;
_isAfterAround = isAfterAround;
}
public async Task OnInvocation(AspectContext aspectContext, AspectDelegate next)
{
try
{
if (!_isAfterAround) await next.Invoke(aspectContext);
}
finally
{
//不管成功还是失败都会执行的
await this._afterAttribute.After(aspectContext, aspectContext.Exception ?? aspectContext.ReturnValue);
}
}
}
环绕增强器
/// <summary>
/// 环绕返回拦截处理器
/// </summary>
internal class AspectAroundInterceptor : IAdvice
{
private readonly AspectArround _aroundAttribute;
private readonly AspectAfterInterceptor _aspectAfter;
private readonly AspectAfterThrowsInterceptor _aspectThrows;
public AspectAroundInterceptor(AspectArround aroundAttribute, AspectAfter aspectAfter, AspectAfterThrows chainAspectAfterThrows)
{
_aroundAttribute = aroundAttribute;
if (aspectAfter != null)
{
_aspectAfter = new AspectAfterInterceptor(aspectAfter, true);
}
if (chainAspectAfterThrows != null)
{
_aspectThrows = new AspectAfterThrowsInterceptor(chainAspectAfterThrows, true);
}
}
public async Task OnInvocation(AspectContext aspectContext, AspectDelegate next)
{
Exception exception = null;
try
{
if (_aroundAttribute != null)
{
await _aroundAttribute.OnInvocation(aspectContext, next);
return;
}
}
catch (Exception ex)
{
exception = ex;
}
finally
{
if (exception == null && _aspectAfter != null) await _aspectAfter.OnInvocation(aspectContext, next);
}
try
{
if (exception != null && _aspectAfter != null)
{
await _aspectAfter.OnInvocation(aspectContext, next);
}
if (exception != null && _aspectThrows != null)
{
await _aspectThrows.OnInvocation(aspectContext, next);
}
}
finally
{
if (exception != null) throw exception;
}
}
}
返回值增强器
/// <summary>
/// 后置返值增强器
/// </summary>
internal class AspectAfterReturnInterceptor : IAdvice
{
private readonly AspectAfterReturn _afterAttribute;
public AspectAfterReturnInterceptor(AspectAfterReturn afterAttribute)
{
_afterAttribute = afterAttribute;
}
public async Task OnInvocation(AspectContext aspectContext, AspectDelegate next)
{
await next.Invoke(aspectContext);
//执行异常了不执行after 去执行Throw
if (aspectContext.Exception != null)
{
return;
}
if (_afterAttribute != null)
{
await this._afterAttribute.AfterReturn(aspectContext, aspectContext.ReturnValue);
}
}
}
异常返回增强器
/// <summary>
/// 异常返回增强器
/// </summary>
internal class AspectAfterThrowsInterceptor : IAdvice
{
private readonly AspectAfterThrows _aspectThrowing;
private readonly bool _isFromAround;
public AspectAfterThrowsInterceptor(AspectAfterThrows throwAttribute, bool isFromAround = false)
{
_aspectThrowing = throwAttribute;
_isFromAround = isFromAround;
}
public async Task OnInvocation(AspectContext aspectContext, AspectDelegate next)
{
try
{
if (!_isFromAround) await next.Invoke(aspectContext);
}
finally
{
//只有目标方法出现异常才会走 增强的方法出异常不要走
if (aspectContext.Exception != null)
{
Exception ex = aspectContext.Exception;
if (aspectContext.Exception is TargetInvocationException targetInvocationException)
{
ex = targetInvocationException.InnerException;
}
if (ex == null)
{
ex = aspectContext.Exception;
}
var currentExType = ex.GetType();
if (_aspectThrowing.ExceptionType == null || _aspectThrowing.ExceptionType == currentExType)
{
await _aspectThrowing.AfterThrows(aspectContext, aspectContext.Exception);
}
}
}
}
}
2. 组装增强器们成为一个调用链
每一个node的有三个信息,如下
/// <summary>
/// 拦截node组装
/// </summary>
internal class AspectMiddlewareComponentNode
{
/// <summary>
/// 下一个
/// </summary>
public AspectDelegate Next;
/// <summary>
/// 执行器
/// </summary>
public AspectDelegate Process;
/// <summary>
/// 组件
/// </summary>
public Func<AspectDelegate, AspectDelegate> Component;
}
采用LinkedList来构建我们的拉链式调用, 我们把上面的每个增强器作为一个个middeware,添加进来。
internal class AspectMiddlewareBuilder
{
private readonly LinkedList<AspectMiddlewareComponentNode> Components = new LinkedList<AspectMiddlewareComponentNode>();
/// <summary>
/// 新增拦截器链
/// </summary>
/// <param name="component"></param>
public void Use(Func<AspectDelegate, AspectDelegate> component)
{
var node = new AspectMiddlewareComponentNode
{
Component = component
};
Components.AddLast(node);
}
/// <summary>
/// 构建拦截器链
/// </summary>
/// <returns></returns>
public AspectDelegate Build()
{
var node = Components.Last;
while (node != null)
{
node.Value.Next = GetNextFunc(node);
node.Value.Process = node.Value.Component(node.Value.Next);
node = node.Previous;
}
return Components.First.Value.Process;
}
/// <summary>
/// 获取下一个
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
private AspectDelegate GetNextFunc(LinkedListNode<AspectMiddlewareComponentNode> node)
{
return node.Next == null ? ctx => Task.CompletedTask : node.Next.Value.Process;
}
}
然后build方法会构建成一个一层嵌套一层的pipeline管道(一个委托)
更多关于这种设计模式更多信息请参考我另外一篇文章: 中间件(middlewware)模式
按照我们的需求构建的完整执行示意图如下:
单个拦截器或者切面
多个拦截器或者切面
生成代理类代理目标方法去执行上面构造的拦截器链
这一步就简单了,如果检测到目标有打拦截器注解,则会给这个类动态创建一个proxy类,
生成代理类用的是castle.core的dynamic组件
默认的是Class+virtual的方式对目标方法进行拦截
注意:考虑到性能,在项目启动的时候把构建好进行缓存,然后再拦截器里面使用
好了,拦截器和切面介绍到此,更多教程请参考项目wiki(教程很详细哦,别忘记给个star)
https://github.com/yuzd/Autofac.Annotation/wiki
关注公众号一起学习

Autofac实现拦截器和切面编程的更多相关文章
- SpringBoot09 自定义servlet、注册自定义的servlet、过滤器、监听器、拦截器、切面、webmvcconfigureradapter过时问题
1 servlet简介 servlet是一种用于开发动态web资源的技术 参考博客:servlet基础知识 httpservlet详解 2 在springboot应用中添加servlet sp ...
- SpringBoot自定义servlet、注册自定义的servlet、过滤器、监听器、拦截器、切面、webmvcconfigureradapter过时问题
[转]https://www.cnblogs.com/NeverCtrl-C/p/8191920.html 1 servlet简介 servlet是一种用于开发动态web资源的技术 参考博客:serv ...
- Autofac的切面编程实现
*:first-child { margin-top: 0 !important; } .markdown-body>*:last-child { margin-bottom: 0 !impor ...
- C#使用Autofac实现控制反转IoC和面向切面编程AOP
Autofac是一个.net下非常优秀,性能非常好的IOC容器(.net下效率最高的容器),加上AOP简直是如虎添翼.Autofac的AOP是通过Castle(也是一个容器)项目的核心部分实现的,名为 ...
- .net core autofac asyncinterceptor 异步拦截器开发
autofac使用拦截器实现AOP,是基于Castle.Core的.然而Castle.Core并未提供原生异步支持.所以需要使用帮助类实现,这在autofac官方文档的已知问题中有详细说明: http ...
- .net core autofac asyncinterceptor 异步拦截器帮助包
autofac使用拦截器实现AOP,是基于Castle.Core的.然而Castle.Core并未提供原生异步支持.所以需要使用帮助类实现,这在autofac官方文档的已知问题中有详细说明: http ...
- struts2-第二章-拦截器
一,回顾 (1)默认action,404问题;<default-action-ref name="action 名称"/> (2)模块化,package,struts. ...
- 过滤器、拦截器和AOP的分析与对比
目录 一.过滤器(Filter) 1.1 简介 1.2 应用场景 1.3 源码分析 二.拦截器(Interceptor) 2.1 简介 2.2 应用场景 2.2 源码分析 三.面向切面编程(AOP) ...
- 面试题:struts 拦截器和过滤器
拦截器和过滤器的区别 过滤器是servlet规范中的一部分,任何java web工程都可以使用. 拦截器是struts2框架自己的,只有使用了struts2框架的工程才能用. 过滤器在url-patt ...
随机推荐
- 优化vue+springboot项目页面响应时间:waiting(TTFB) 及content Download
优化vue+springboot项目页面响应时间:waiting(TTFB) 及content Download TTFB全称Time To First Byte,是指网络请求被发起到从服务器接收到地 ...
- NLTK 3.2.5 documentation Installing NLTK
Installing NLTK NLTK requires Python versions 2.7, 3.4, or 3.5 Mac/Unix Install NLTK: run sudo pip i ...
- vc++ 调用winapi调节屏幕亮度(增加win7代码demo)
1.关于 代码是通过测试的,测试环境: win7 + MFC 为什么要发在这里? 区别于上一篇随笔. MD排版更顺眼 demo 会放到 这里 更正了上一篇随笔中的代码错误 2.头文件 #include ...
- c++11多线程常用代码总结
关于 好记性不如烂笔头 理解虽然到位,但是时间长了就容易忘. 本文仅总结自己经常忘记的知识点,非 详细解释多线程某些原理.概念. 抱着复习的态度总结此文. 本文参考: cppreference 欢迎指 ...
- leetcode1260二维网络迁移
题目 n*m的矩阵,一个整数k,移动矩阵k次.每次移动的操作为: 向右移动(最后一列移动到第一列) 之后,第一列向下移动. 1<=N.M<=50 0<=k<=100 题解 思考 ...
- ZYB loves Xor I(hud5269)
ZYB loves Xor I Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)T ...
- Codeforces Round #358 (Div. 2) C. Alyona and the Tree
C. Alyona and the Tree time limit per test 1 second memory limit per test 256 megabytes input standa ...
- MQ消费失败,自动重试思路
在遇到与第三方系统做对接时,MQ无疑是非常好的解决方案(解耦.异步).但是如果引入MQ组件,随之要考虑的问题就变多了,如何保证MQ消息能够正常被业务消费.所以引入MQ消费失败情况下,自动重试功能是非常 ...
- [数学]高数部分-Part IV 一元函数积分学
Part IV 一元函数积分学 回到总目录 Part IV 一元函数积分学 不定积分定义 定积分定义 不定积分与定积分的几何意义 牛顿-莱布尼兹公式 / N-L 公式 基本积分公式 点火公式(华里士公 ...
- [opencv]opencv主要组件介绍
[calib3d]--其实就是就是Calibration(校准)加3D这两个词的组合缩写.这个模块主要是相机校准和三维重建相关的内容.基本的多视角几何算法,单个立体摄像头标定,物体姿态估计,立体相似性 ...