.NET Core 3.x 基于AspectCore实现AOP,实现事务、缓存拦截器
最近想给我的框架加一种功能,就是比如给一个方法加一个事务的特性Attribute,那这个方法就会启用事务处理。给一个方法加一个缓存特性,那这个方法就会进行缓存。这个也是网上说的面向切面编程AOP。
AOP的概念也很好理解,跟中间件差不多,说白了,就是我可以任意地在方法的前面或后面添加代码,这很适合用于缓存、日志等处理。
在net core2.2时,我当时就尝试过用autofac实现aop,但这次我不想用autofac,我用了一个更轻量级的框架,AspectCore。
先安装NuGet包,包名:AspectCore.Extensions.DependencyInjection,然后在Program.cs类中增加一行代码,这是.NET Core 3.x的不同之处,这句添加的代码,意思就是用AspectCore的IOC容器替换内置的。因为AOP需要依靠IOC实现,所以必须得替换掉内置的IOC。
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
//用AspectCore替换默认的IOC容器
.UseServiceProviderFactory(new DynamicProxyServiceProviderFactory());
}
然后在Startup.cs类中的ConfigureServices中添加代码。(其实这个加不加都可以,如果需要配置就加,例如全局的拦截器、只拦截哪些匹配的服务,因为我只用特性进行拦截,所以我就什么也没配置)
services.ConfigureDynamicProxy(o=> {
//添加AOP的配置
});
这样AOP就配置好了,是不是很简单。
当然使用方面也需要注意一下,可以在接口、接口的方法、类,类的virtual方法上进行拦截。还有如果你想拦截控制器的action的话,那需要在ConfigureService里AddControllerAsServices
services.AddControllers()
.AddControllersAsServices() //把控制器当成服务
事务拦截器,如果是特性拦截,就继承AbstractInterceptorAttribute,如果要写一个全局拦截器,就AbstractInterceptor,然后在ConfigureDynamicProxy中进行配置
如果拦截器是放在其他项目的,那要记得添加AspectCore.Core包,不要只添加AspectCore.Abstractions,我一开始就只添加了AspectCore.Abstractions,一直没发现IsAsync、UnwrapAsyncReturnValue等一些扩展方法。
public class TransactionInterceptorAttribute : AbstractInterceptorAttribute
{
public async override Task Invoke(AspectContext context, AspectDelegate next)
{
var dbContext = context.ServiceProvider.GetService<AppDbContext>();
//先判断是否已经启用了事务
if (dbContext.Database.CurrentTransaction == null)
{
await dbContext.Database.BeginTransactionAsync();
try
{
await next(context);
dbContext.Database.CommitTransaction();
}
catch (Exception ex)
{
dbContext.Database.RollbackTransaction();
throw ex;
}
}
else
{
await next(context);
}
}
}

缓存拦截器,下面的ICacheHelper是定义的一个缓存助手接口,用的是redis
public class CacheInterceptorAttribute : AbstractInterceptorAttribute
{
/// <summary>
/// 缓存秒数
/// </summary>
public int ExpireSeconds { get; set; }
public async override Task Invoke(AspectContext context, AspectDelegate next)
{
//判断是否是异步方法
bool isAsync = context.IsAsync();
//if (context.ImplementationMethod.GetCustomAttribute(typeof(AsyncStateMachineAttribute)) != null)
//{
// isAsync = true;
//}
//先判断方法是否有返回值,无就不进行缓存判断
var methodReturnType = context.GetReturnParameter().Type;
if (methodReturnType == typeof(void) || methodReturnType == typeof(Task) || methodReturnType == typeof(ValueTask))
{
await next(context);
return;
}
var returnType = methodReturnType;
if (isAsync)
{
//取得异步返回的类型
returnType = returnType.GenericTypeArguments.FirstOrDefault();
}
//获取方法参数名
string param = CommonHelper.ObjectToJsonString(context.Parameters);
//获取方法名称,也就是缓存key值
string key = "Methods:" + context.ImplementationMethod.DeclaringType.FullName + "." + context.ImplementationMethod.Name;
var cache = context.ServiceProvider.GetService<ICacheHelper>();
//如果缓存有值,那就直接返回缓存值
if (cache.HashExists(key, param))
{
//反射获取缓存值,相当于cache.HashGet<>(key,param)
var value = typeof(ICacheHelper).GetMethod(nameof(ICacheHelper.HashGet)).MakeGenericMethod(returnType).Invoke(cache, new[] { key, param });
if (isAsync)
{
//判断是Task还是ValueTask
if (methodReturnType == typeof(Task<>).MakeGenericType(returnType))
{
//反射获取Task<>类型的返回值,相当于Task.FromResult(value)
context.ReturnValue = typeof(Task).GetMethod(nameof(Task.FromResult)).MakeGenericMethod(returnType).Invoke(null, new[] { value });
}
else if (methodReturnType == typeof(ValueTask<>).MakeGenericType(returnType))
{
//反射构建ValueTask<>类型的返回值,相当于new ValueTask(value)
context.ReturnValue = Activator.CreateInstance(typeof(ValueTask<>).MakeGenericType(returnType), value);
}
}
else
{
context.ReturnValue = value;
}
return;
}
await next(context);
object returnValue;
if (isAsync)
{
returnValue = await context.UnwrapAsyncReturnValue();
//反射获取异步结果的值,相当于(context.ReturnValue as Task<>).Result
//returnValue = typeof(Task<>).MakeGenericType(returnType).GetProperty(nameof(Task<object>.Result)).GetValue(context.ReturnValue);
}
else
{
returnValue = context.ReturnValue;
}
cache.HashSet(key, param, returnValue);
if (ExpireSeconds > 0)
{
cache.SetExpire(key, TimeSpan.FromSeconds(ExpireSeconds));
}
}
}
删除拦截器,作用就是带有这个特性的方法执行后,会删除相关缓存值,比如说我给一个方法 GetUserList 加了缓存,那我数据改变了怎么办,我想在User数据改变时,把这个缓存删除掉,那我就可以在SaveUser方法上加上我这个缓存删除拦截器,那这个方法执行后,就会把相关的缓存删除掉了
public class CacheDeleteInterceptorAttribute : AbstractInterceptorAttribute
{
private readonly Type[] _types;
private readonly string[] _methods;
/// <summary>
/// 需传入相同数量的Types跟Methods,同样位置的Type跟Method会组合成一个缓存key,进行删除
/// </summary>
/// <param name="Types">传入要删除缓存的类</param>
/// <param name="Methods">传入要删除缓存的方法名称,必须与Types数组对应</param>
public CacheDeleteInterceptorAttribute(Type[] Types, string[] Methods)
{
if (Types.Length != Methods.Length)
{
throw new ApiFailException(ApiFailCode.OPERATION_FAIL, "Types必须跟Methods数量一致");
}
_types = Types;
_methods = Methods;
}
public async override Task Invoke(AspectContext context, AspectDelegate next)
{
var cache = context.ServiceProvider.GetService<ICacheHelper>();
await next(context);
for (int i = 0; i < _types.Length; i++)
{
var type = _types[i];
var method = _methods[i];
string key = "Methods:" + type.FullName + "." + method;
cache.Delete(key);
}
}
}
总结:要实现AOP,需要依靠IOC容器,因为它是我们类的管家,那能被拦截的类必须是IOC注入的,自己new出来的是不受拦截的。如果我想在A方法前面添加点代码,那我告诉IOC,把代码给它,那IOC在注入A方法所在类时,会继承它生成一个派生类,然后重写A方法,所以拦截方法必须得为virtual,然后A方法里写上我要添加的代码,再base.A()这样。
.NET Core 3.x 基于AspectCore实现AOP,实现事务、缓存拦截器的更多相关文章
- Asp.net Core 3.1基于AspectCore实现AOP,实现事务、缓存拦截器
最近想给我的框架加一种功能,就是比如给一个方法加一个事务的特性Attribute,那这个方法就会启用事务处理.给一个方法加一个缓存特性,那这个方法就会进行缓存. 这个也是网上说的面向切面编程AOP. ...
- 我心中的核心组件(可插拔的AOP)~第二回 缓存拦截器
回到目录 AOP面向切面的编程,也称面向方面的编程,我更青睐于前面的叫法,将一个大系统切成多个独立的部分,而这个独立的部分又可以方便的插拔在其它领域的系统之中,这种编程的方式我们叫它面向切面,而这些独 ...
- Spring AOP 源码分析 - 拦截器链的执行过程
1.简介 本篇文章是 AOP 源码分析系列文章的最后一篇文章,在前面的两篇文章中,我分别介绍了 Spring AOP 是如何为目标 bean 筛选合适的通知器,以及如何创建代理对象的过程.现在我们的得 ...
- Spring AOP深入理解之拦截器调用
Spring AOP深入理解之拦截器调用 Spring AOP代理对象生成回想 上一篇博客中:深入理解Spring AOP之二代理对象生成介绍了Spring代理对象是怎样生成的,当中重点介绍了JDK动 ...
- .net core 3.1 过滤器(Filter) 与中间件与AOP面向切面 与拦截器及其应用
Filter(过滤器) 总共有五种,Authorization Filter,Resource Filter,Exception Filter,Action Filter,Result Filter ...
- 【基于初学者的SSH】struts2 的拦截器、令牌的简单应用及理解
一:拦截器与过滤器类似,但是它们的区别也很大: 01):过滤器理论上可以过滤任意内容,比如HTML,servlet,jsp,图片路径 02):拦截器只可以拦截action. 二:拦截器的原理 act ...
- SSM整合AOP,日志框架和拦截器
前言 日志是所有系统必不可少的部分,而AOP在MVC通常用于监控方法调用,可以生成一个traceid,记录从用户调用到底层数据库的数据链路,帮助监控和排查问题. AOP 现在做一个简单的前置切面,用来 ...
- Spring的AOP,Struts2的拦截器(Interceptor),以及springMVC的(interceptor)
参考外链:http://www.ibm.com/developerworks/cn/java/j-lo-springaopfilter/ 1.首先,spring的AOP作用范围很广,可以使用Aspec ...
- .NetCore学习笔记:三、基于AspectCore的AOP事务管理
AOP(面向切面编程),通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术.AOP是OOP的延续,是函数式编程的一种衍生范型.利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑 ...
- 阶段3 2.Spring_10.Spring中事务控制_2 作业-基于注解的AOP实现事务控制及问题分析_上
创建maven的新项目 先复制坐标的依赖. 再把代码复制进来 先改造ioc的部分 复制上面一行代码.到下面 改成context 这里也是复制的上面两行代码.到下面改成context关键字 配置扫描的包 ...
随机推荐
- android studio 安装与配置
android studio 下载地址:http://www.android-studio.org/ 找一个存储空间,我在D盘上,建好如下目录 : 找到刚才在载的文件 android-stu ...
- CSS——渐变色
<!DOCTYPE html> <html> <head> <style> div { width: 210px; height: 50px; floa ...
- 高精度离线免费 的C#文字识别PaddleOCR库
随便打开一个Microsoft Visual Studio,新建一个WinForms项目,从下面列表中随便选择一个NET框架.目标平台要设置成X64,该OCR仅支持64位. net35;net40;n ...
- CH57x/CH58x/CH59x获取从机广播信息
有时需要通过主机设备(MCU非手机)获取从设备的广播信息例如广播包,MAC地址,扫描应答包等 以下的程序片段及功能实现是在WCH的CH59X的observer例程上实现的: 1.获取广播包 所有的函数 ...
- NOIP模拟91(多校24)
T1 破门而入 解题思路 签到题(然而我数组开小直接变成暴力分...) 发现其实就是第一类斯特林数,然后 \(n^2\) 推就好了. 感觉可以用 NTT 优化成 \(nlogn\) ,但是好像并没有什 ...
- MySQL入门到精通(十):SQL优化第一篇(2021最新发布)
SQL优化 1. 对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. 2.应尽量避免在 where 子句中对字段进行 null 值判断,创建表时N ...
- Dva.js 快速上手指南
先说些废话 最近在开发React技术栈的项目产品,对于数据状态的管理使用了Dva.js,作为一个资深的ow玩家,我看到这个名字第一反应就是----这不是ow里的一个女英雄吗?仔细阅读了官方文档之后,发 ...
- ES6 ES6变量的声明
// ES6语法 // 是对js影响最大的一个版本更新 // 在 ES6 语法中 退出了很多新的 语法结构 // 就相当于 js 语言, 换了一个新 ...
- Adobe软件资源 PS PR AE等等
整理了一波Adobe软件,19年20年21年Mac版本的都有,关注Rand_cs即可领取
- 解决:Maven PKIX path building failed: sun.security.provider.certpath
在构建SpringBoot项目时,maven下载依赖会报 PKIX path building failed: sun.security.provider.certpath的错误. 使用https:/ ...