0. 前言
之前寫了幾篇文章介紹了一些AOP的知識,
但是還沒有亮出來AOP的姿勢,
也許姿勢漂亮一點,
大家會對AOP有點興趣
內容大致會分為如下幾篇:(畢竟人懶,一下子寫完太累了,沒有動力)
- AOP的姿勢之 簡化 MemoryCache 使用方式
- AOP的姿勢之 簡化混用 MemoryCache 和 DistributedCache 使用方式
- AOP的姿勢之 如何把 HttpClient 變為宣告式
至於AOP框架在這兒示例依然會使用我自己基於emit實現的動態代理AOP框架: https://github.com/fs7744/Norns.Urd
畢竟是自己寫的,魔改/加功能都很方便,
萬一萬一大家如果有疑問,(雖然大概不會有),我也好回答, (當然如果大家認可,在github給個star,就實在是太讓人開心了)
1. 正文
1.1 回顧 MemoryCache如何使用
var cache = ServiceProvider.GetRequiredService<IMemoryCache>();
var r = await cache.GetOrCreateAsync(cacheKey, async e =>
{
var rr = await do();
e.AbsoluteExpirationRelativeToNow = absoluteExpirationRelativeToNow;
return rr;
});
MemoryCache
本身已經被封裝到如此簡單就可以使用了
但是呢,每次我們使用的時候依然要這樣重複寫類似的程式碼
當然我們都是擁有超強的 ctrl+c 和 ctrl+v 能力,
這點點重複程式碼都是些毛毛雨啦,
上w行程式碼一把梭都是小場面了,
不過呢,這樣的程式碼寫的和在校的學生一樣,
怎麼能體現我們混跡江湖,加班數十載的逼格呢?
我們要讓這些在校學生/實習生看不懂我們的程式碼,
讓他們看不到GetOrCreateAsync
,
讓他們除錯的時候 do()
裡面的斷點跑不到
這樣我們才能展示出掃地僧的實力:來,小朋友,我來教你新姿勢
1.2 逼格啟航
1.2.1 逼格核心 - 攔截器
在Norns.Urd中,Interceptor 攔截器是使用者可以在方法插入自己的邏輯的核心。
標準結構為IInterceptor
public interface IInterceptor
{
// 使用者可以通過Order自定義攔截器順序,排序方式為ASC,全域性攔截器和顯示攔截器都會列入排序中
int Order { get; }
// 同步攔截方法
void Invoke(AspectContext context, AspectDelegate next);
// 非同步攔截方法
Task InvokeAsync(AspectContext context, AsyncAspectDelegate next);
// 可以設定攔截器如何選擇過濾是否攔截方法,除了這裡還有NonAspectAttribute 和全域性的NonPredicates可以影響過濾
bool CanAspect(MethodInfo method);
}
這裡我們為了大家理解簡單,就使用最簡單的方式來做 : 使用 AbstractInterceptorAttribute
一個非常簡單的例子就如下了:
public class CacheAttribute : AbstractInterceptorAttribute
{
private readonly TimeSpan absoluteExpirationRelativeToNow;
private readonly string cacheKey;
// 為了簡單,快取策略我們就先只支援TTL 存活固定時間
public CacheAttribute(string cacheKey, string absoluteExpirationRelativeToNow)
{
this.cacheKey = cacheKey;
this.absoluteExpirationRelativeToNow = TimeSpan.Parse(absoluteExpirationRelativeToNow);
}
public override async Task InvokeAsync(AspectContext context, AsyncAspectDelegate next)
{
// 整個程式碼基本和我們直接使用 MemoryCache 一樣
var cache = context.ServiceProvider.GetRequiredService<IMemoryCache>();
var r = await cache.GetOrCreateAsync(cacheKey, async e =>
{
await next(context); // 所以真正實現的方法邏輯都在 next 中,所以呼叫它就好了
e.AbsoluteExpirationRelativeToNow = absoluteExpirationRelativeToNow;
return context.ReturnValue; // 結果都在ReturnValue , 這裡為了簡單,就不寫 void / Task<T> / ValueTask<T> 等等 各種返回值的相容程式碼了
});
context.ReturnValue = r; // 設定 ReturnValue, 由於快取有效期內, next不會被呼叫, 所以ReturnValue不會有值,我們需要將快取結果設定到 ReturnValue
}
}
1.2.2 測試一下
public interface ITestCacheClient
{
string Say(string v);
}
public class TestCacheClient : ITestCacheClient
{
public string Say(string v) => v;
}
static class Program
{
static void Main(string[] args)
{
var client = new ServiceCollection()
.AddMemoryCache()
.AddSingleton<ITestCacheClient, TestCacheClient>()
.ConfigureAop()
.BuildServiceProvider()
.GetRequiredService<ITestCacheClient>();
Console.WriteLine(client.Say("Hello World!"));
Console.WriteLine(client.Say("Hello Two!"));
Thread.Sleep(3000);
Console.WriteLine(client.Say("Hello Two!"));
}
}
Console 結果
Hello World!
Hello Two!
Hello Two!
加上快取設定:
public class TestCacheClient : ITestCacheClient
{
[Cache(nameof(Say), "00:00:03")]
public string Say(string v) => v;
}
再次測試的 Console 結果
Hello World!
Hello World!
Hello Two!
例子程式碼都在 https://github.com/fs7744/AopDemoList/tree/master/MakeMemoryChacheSimple
處理情況更全面的例子在 https://github.com/fs7744/Norns.Urd/tree/main/src
祝大家都能愉快被叫 大神 nb。