• Asp.net Core 3.1基于AspectCore实现AOP,实现事务、缓存拦截器


    最近想给我的框架加一种功能,就是比如给一个方法加一个事务的特性Attribute,那这个方法就会启用事务处理。给一个方法加一个缓存特性,那这个方法就会进行缓存。

    这个也是网上说的面向切面编程AOP。

    AOP的概念也很好理解,跟中间件差不多,说白了,就是我可以任意地在方法的前面或后面添加代码,这很适合用于缓存、日志等处理。

    在net core2.2时,我当时就尝试过用autofac实现aop,但这次我不想用autofac,我用了一个更轻量级的框架,AspectCore。

    用起来非常非常的简单,但一开始还是走了一点弯路,主要是网上都是net core3以下的教程,3以下的使用方法跟之前有一些不同。

    先安装NuGet包,包名:AspectCore.Extensions.DependencyInjection

    然后在Program.cs类中增加一行代码,这是net core 3的不同之处,这句添加的代码,意思就是用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的实现原理我也想象了一下:

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

  • 相关阅读:
    调试脚本的技巧与实际应用
    mysqlconnector将EXCEL表数据导入数据库
    第四十三节,文件、文件夹、压缩包、处理模块shutil
    第四十二节,configparser特定格式的ini配置文件模块
    第四十一节,xml处理模块
    第四十节,requests模拟浏览器请求模块初识
    第三十九节,python内置全局变量
    第三十八节,字符串格式化
    第三十七节,hashlib加密模块
    第三十六节,os系统级别操作模块
  • 原文地址:https://www.cnblogs.com/caijt/p/13380455.html
Copyright © 2020-2023  润新知