AOP的姿勢之 簡化 MemoryCache 使用方式

victor.x.qu發表於2020-12-27

0. 前言

之前寫了幾篇文章介紹了一些AOP的知識,
但是還沒有亮出來AOP的姿勢,
也許姿勢漂亮一點,
大家會對AOP有點興趣
內容大致會分為如下幾篇:(畢竟人懶,一下子寫完太累了,沒有動力)

  1. AOP的姿勢之 簡化 MemoryCache 使用方式
  2. AOP的姿勢之 簡化混用 MemoryCache 和 DistributedCache 使用方式
  3. 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。

相關文章