這篇文章主要介紹了Asp.net Core 3.1基于AspectCore實(shí)現(xiàn)AOP實(shí)現(xiàn)事務(wù)、緩存*功能,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
最近想給我的框架加一種功能,就是比如給一個(gè)方法加一個(gè)事務(wù)的特性Attribute,那這個(gè)方法就會(huì)啟用事務(wù)處理。給一個(gè)方法加一個(gè)緩存特性,那這個(gè)方法就會(huì)進(jìn)行緩存。
這個(gè)也是網(wǎng)上說的面向切面編程AOP。
AOP的概念也很好理解,跟中間件差不多,說白了,就是我可以任意地在方法的前面或后面添加代碼,這很適合用于緩存、日志等處理。
在net core2.2時(shí),我當(dāng)時(shí)就嘗試過用autofac實(shí)現(xiàn)aop,但這次我不想用autofac,我用了一個(gè)更輕量級的框架,AspectCore。
用起來非常非常的簡單,但一開始還是走了一點(diǎn)彎路,主要是網(wǎng)上都是net core3以下的教程,3以下的使用方法跟之前有一些不同。
先安裝NuGet包,包名:AspectCore.Extensions.DependencyInjection
然后在Program.cs類中增加一行代碼,這是net core 3的不同之處,這句添加的代碼,意思就是用AspectCore的IOC容器替換內(nèi)置的。因?yàn)锳OP需要依靠IOC實(shí)現(xiàn),所以必須得替換掉內(nèi)置的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替換默認(rèn)的IOC容器
.UseServiceProviderFactory(new DynamicProxyServiceProviderFactory());
}
然后在Startup.cs類中的ConfigureServices中添加代碼。(其實(shí)這個(gè)加不加都可以,如果需要配置就加,例如全局的*、只攔截哪些匹配的服務(wù),因?yàn)槲抑挥锰匦赃M(jìn)行攔截,所以我就什么也沒配置)
services.ConfigureDynamicProxy(o=> {
//添加AOP的配置
});
這樣AOP就配置好了,是不是很簡單。
當(dāng)然使用方面也需要注意一下,可以在接口、接口的方法、類,類的virtual方法上進(jìn)行攔截。還有如果你想攔截控制器的action的話,那需要在ConfigureService里AddControllerAsServices
services.AddControllers()
//把控制器當(dāng)成服務(wù)
.AddControllersAsServices()
下面我列出我的事務(wù)*代碼,如果是特性攔截,就繼承AbstractInterceptorAttribute,如果要寫一個(gè)全局*,就AbstractInterceptor,然后在ConfigureDynamicProxy中進(jìn)行配置,這個(gè)我就不介紹了
如果你的*是放在其他項(xiàng)目的,那要記得添加AspectCore.Core包,不要只添加AspectCore.Abstractions,我一開始就只添加了AspectCore.Abstractions,一直沒發(fā)現(xiàn)IsAsync、UnwrapAsyncReturnValue等一些擴(kuò)展方法。
public class TransactionInterceptorAttribute : AbstractInterceptorAttribute
{
public async override Task Invoke(AspectContext context, AspectDelegate next)
{
var dbContext = context.ServiceProvider.GetService<AppDbContext>();
//先判斷是否已經(jīng)啟用了事務(wù)
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);
}
}
}
然后我就可以這么優(yōu)雅地使用事務(wù)了
我再列出我的緩存*,(感謝網(wǎng)友的提醒,我做了一下修改,針對異步方法返回值的處理),對了,下面的ICacheHelper是我定義的一個(gè)緩存助手接口,用的是redis,我會(huì)在后面寫一篇博客
public class CacheInterceptorAttribute : AbstractInterceptorAttribute
{
/// <summary>
/// 緩存秒數(shù)
/// </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;
//}
//先判斷方法是否有返回值,無就不進(jìn)行緩存判斷
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();
}
//獲取方法參數(shù)名
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))
{
//反射獲取緩存值,相當(dāng)于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<>類型的返回值,相當(dāng)于Task.FromResult(value)
context.ReturnValue = typeof(Task).GetMethod(nameof(Task.FromResult)).MakeGenericMethod(returnType).Invoke(null, new[] { value });
}
else if (methodReturnType == typeof(ValueTask<>).MakeGenericType(returnType))
{
//反射構(gòu)建ValueTask<>類型的返回值,相當(dāng)于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();
//反射獲取異步結(jié)果的值,相當(dāng)于(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));
}
}
}
我還弄了一個(gè)緩存刪除*,作用就是帶有這個(gè)特性的方法執(zhí)行后,會(huì)刪除相關(guān)緩存值
為什么有這個(gè)設(shè)計(jì)呢,比如說我給一個(gè)方法 GetUserList 加了緩存,那我數(shù)據(jù)改變了怎么辦,我想在User數(shù)據(jù)改變時(shí),把這個(gè)緩存刪除掉,那我就可以在SaveUser方法上加上我這個(gè)緩存刪除*,那這個(gè)方法執(zhí)行后,就會(huì)把相關(guān)的緩存刪除掉了
public class CacheDeleteInterceptorAttribute : AbstractInterceptorAttribute
{
private readonly Type[] _types;
private readonly string[] _methods;
/// <summary>
/// 需傳入相同數(shù)量的Types跟Methods,同樣位置的Type跟Method會(huì)組合成一個(gè)緩存key,進(jìn)行刪除
/// </summary>
/// <param name="Types">傳入要?jiǎng)h除緩存的類</param>
/// <param name="Methods">傳入要?jiǎng)h除緩存的方法名稱,必須與Types數(shù)組對應(yīng)</param>
public CacheDeleteInterceptorAttribute(Type[] Types, string[] Methods)
{
if (Types.Length != Methods.Length)
{
throw new ApiFailException(ApiFailCode.OPERATION_FAIL, "Types必須跟Methods數(shù)量一致");
}
_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的實(shí)現(xiàn)原理我也想象了一下:
要實(shí)現(xiàn)AOP,需要依靠IOC容器,因?yàn)樗俏覀冾惖墓芗?,那能被攔截的類必須是IOC注入的,自己new出來的是不受攔截的。如果我想在A方法前面添加點(diǎn)代碼,那我告訴IOC,把代碼給它,那IOC在注入A方法所在類時(shí),會(huì)繼承它生成一個(gè)派生類,然后重寫A方法,所以攔截方法必須得為virtual,然后A方法里寫上我要添加的代碼,再base.A()這樣。
到此這篇關(guān)于Asp.net Core 3.1基于AspectCore實(shí)現(xiàn)AOP實(shí)現(xiàn)事務(wù)、緩存*功能的文章就介紹到這了,更多相關(guān)Asp.net Core 3.1實(shí)現(xiàn)事務(wù)、緩存*內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
來源:腳本之家
鏈接:https://www.jb51.net/article/201403.htm
申請創(chuàng)業(yè)報(bào)道,分享創(chuàng)業(yè)好點(diǎn)子。點(diǎn)擊此處,共同探討創(chuàng)業(yè)新機(jī)遇!