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>(); var dependAssemblyKey = BuildDependAssemblyKey(); if (!string.IsNullOrEmpty(dependAssemblyKey)) { webBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, dependAssemblyKey); } }) //用AspectCore替換默認的IOC容器 .UseServiceProviderFactory(new DynamicProxyServiceProviderFactory()); }
然後在Startup.cs類中的ConfigureServices中添加程式碼。(其實這個加不加都可以,如果需要配置就加,例如全局的攔截器、只攔截哪些匹配的服務,因為我只用特性進行攔截,所以我就什麼也沒配置)
services.ConfigureDynamicProxy(o=> { //添加AOP的配置 });
這樣AOP就配置好了,是不是很簡單。
當然使用方面也需要注意一下,可以在介面、介面的方法、類,類的virtual方法上進行攔截。還有如果你想攔截控制器的action的話,那需要在ConfigureService里AddControllerAsServices
services.AddControllers() //把控制器當成服務 .AddControllersAsServices()
下面我列出我的事務攔截器程式碼
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); } } }
然後我就可以這麼優雅地使用事務了
我再列出我的快取攔截器
public class CacheInterceptorAttribute : AbstractInterceptorAttribute { /// <summary> /// 快取秒數 /// </summary> public int ExpireSeconds { get; set; } public async override Task Invoke(AspectContext context, AspectDelegate next) { //先判斷方法是否有返回值,無就不進行快取判斷 var returnParams = context.GetReturnParameter(); if (returnParams.Type == typeof(void)) { await next(context); return; } //獲取方法參數名 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)) { var value = typeof(ICacheHelper).GetMethod(nameof(ICacheHelper.HashGet)).MakeGenericMethod(returnParams.Type).Invoke(cache, new[] { key, param }); context.ReturnValue = value; return; } await next(context); cache.HashSet(key, param, context.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()這樣。