• .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的话,那需要在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还是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()这样。

  • 相关阅读:
    海岛争霸
    hihocoder 1089 最短路径·二:Floyd算法
    hihocoder1081 :最短路径·一
    [蓝桥杯][历届试题]蚂蚁感冒
    牛客网Wannafly挑战赛27 A: 灰魔法师
    hdu1875:畅通工程再续
    hdu1863:畅通工程
    hdu1879:继续畅通工程
    hdu1233:还是畅通工程
    洛谷 P1546 最短网络 Agri-Net
  • 原文地址:https://www.cnblogs.com/zhaoshujie/p/13666169.html
Copyright © 2020-2023  润新知