.NET Core 3.x 基于AspectCore实现AOP,实现事务、缓存拦截器

news/2024/9/28 16:17:17

最近想给我的框架加一种功能,就是比如给一个方法加一个事务的特性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的话,那需要在ConfigureServiceAddControllerAsServices

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还是ValueTaskif (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);}}
}
PLAINTEXT 复制 全屏

总结:要实现AOP,需要依靠IOC容器,因为它是我们类的管家,那能被拦截的类必须是IOC注入的,自己new出来的是不受拦截的。如果我想在A方法前面添加点代码,那我告诉IOC,把代码给它,那IOC在注入A方法所在类时,会继承它生成一个派生类,然后重写A方法,所以拦截方法必须得为virtual,然后A方法里写上我要添加的代码,再base.A()这样。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hjln.cn/news/47293.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

内存调优实战

实战篇1、内存调优1.1 内存溢出和内存泄漏内存泄漏(memory leak):在Java中如果不再使用一个对象,但是该对象依然在GC ROOT的引用链上,这个对象就不会被垃圾回收器回收,这种情况就称之为内存泄漏。内存泄漏绝大多数情况都是由堆内存泄漏引起的,所以后续没有特别说明则讨论…

团队开发日记4

日期:2024年5月11日标题:校园兼职招聘系统开发日记 - 收藏功能和邮箱功能实现 项目概述:今天我们完成了用户收藏功能和邮箱功能的设计和实现,增强了系统的个性化和通知能力。 当天的工作内容:李健龙负责了用户收藏兼职和帖子的页面设计和数据存储逻辑。 郑盾实现了系统的邮…

团队开发日记2

日期:2024年5月5日标题:校园兼职招聘系统开发日记 - 兼职发布与搜索功能实现 项目概述:今天我们重点完成了兼职发布和搜索功能的初步实现,用户可以发布兼职信息并进行基本的搜索。 当天的工作内容:李健龙完成了兼职信息录入页面的前端设计和部分后端逻辑的编写。 郑盾实现…

[模式识别复习笔记] 第4章 SVM

1. SVM 简介 1.1 SVM 支持向量机 给定如图所示的线性可分训练集,能够将两类样本正确分开的直线很多。 感知机算法可以找到一条直线,且找到的直线不唯一。 然而感知机无法确定哪一条直线最优,但是 \(\text{SVM}\) 可以。 \(\text{SVM}\) 可以找到能够将训练样本正确分类的直线…

react 18 基础教程

1.React开发环境搭建执行 npx create-react-app 项目名称 命令来创建项目 2.实现列表渲染在react中可以通过在{}中写入js表达式来执行js代码,所以可以通过如下手段来执行来实现列表的渲染。 function App() {let list = [{id:1,name:"Vue"},{id:2,name:"React&…

成为MySQL DBA后,再看ORACLE数据库(十一、闪回技术)

前文说到ORACLE通过undo实现数据的多版本模型,同样的道理ORACLE还通过undo实现了闪回查询的特性,本文将总结ORACLE的几种闪回技术。闪回技术是Oracle数据库独有的特性,支持各级恢复,包括行、事务、表、表空间和数据库范围。采用闪回技术,可以针对行级和事务级发生过变化的…

KD散件组装的几种形式

介绍 KD指散件组装,英文全称为:knockdown。 KD有三种形式: CKD 、 SKD 、 DKD 。 CKD (Complete Knockdown)为全散件组装, SKD(Semi-Knockdown) 则是半散件组装,一部分总成是现成的。 DKD(Direct Knockdown) 直接组装或成品组装,如手机组装生产中,单机头整体进口,安装配…

面经梳理-计算机网络

梳理计算机网络相关面经前言 整理计算机网络的相关面试题,计算机网络在我看来挺复杂的,想要完全精通应该是不可能的,毕竟后端开发的知识点那么多,不过掌握面试的常考知识点是由必要的。建议系统学习计算机网络课本再进行知识点的整理记忆。 题目 OSI七层协议有了解么?Ip协…