65.9K
CodeProject 正在变化。 阅读更多。
Home

让 MemoryCache 更易于使用的 AOP 示例

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2020年12月27日

CPOL

2分钟阅读

viewsIcon

5243

使用 AOP 示例使 MemoryCache 更易于使用

0. 前言

我之前写过几篇文章介绍了一些 AOP 知识,但 AOP 的姿势还没有展现出来。
也许更漂亮的姿势,大家会对 AOP 更感兴趣。

内容大致会分成以下几篇文章(毕竟我是一个懒人,写完一次太累了,也没有动力)

  1. AOP 姿势简化 MemoryCache 的使用
  2. AOP 姿势简化 MemoryCacheDistributedCache 的混合使用
  3. AOP 姿势如何将 HttpClient 变成声明式

至于 AOP 框架,这里的示例仍然会使用我自己的基于 emit 的动态代理 AOP 框架:https://github.com/fs7744/Norns.Urd
毕竟是我自己写的,所以修改/添加功能非常方便。

万一你有什么问题(虽然我可能没有),我也可以回答。 (当然,如果大家同意,在 github 上给个 star 会真的很有趣)。

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+cCtrl+v 能力。 这点重复代码是小菜一碟,上面的 w 行代码都是小场景。

但是,这种代码的编写方式就像在学校里的学生一样。 如何体现我们加班了几十年? 我们想让这些学生/实习生看不懂我们的代码,让他们看不到 `GetOrCreateAsync`。
让他们找不到在 `do()` 中如何运行断点。 只有这样才能展现出扫地工的实力:来,孩子,让我教你一项新技能。

2. 编写 Cache AOP 拦截器

Norns.Urd 中,Interceptor 是核心,用户可以在方法中插入自己的逻辑。
标准结构是 `IInterceptor`。

public interface IInterceptor
{
    // Users can customize the interceptor Order with Order, sorted by ASC, 
    // in which both the global interceptor and the display interceptor are included
    int Order { get; }

    // Synchronous interception method
    void Invoke(AspectContext context, AspectDelegate next);

    // Asynchronous interception method
    Task InvokeAsync(AspectContext context, AsyncAspectDelegate next);

    // You can set how the interceptor chooses whether to filter or 
    // not to intercept a method, in addition to the NonAspectAttribute 
    // and global NonPredicates that can influence filtering
    bool CanAspect(MethodInfo method);
}

在这里,我们将使用最简单的方法让大家简单理解:使用 `AbstractInterceptorAttribute`。 一个非常简单的例子如下

public class CacheAttribute : AbstractInterceptorAttribute
    {
        private readonly TimeSpan absoluteExpirationRelativeToNow;
        private readonly string cacheKey;

        // For the sake of simplicity, we will only support TTL 
        // for a fixed time for the caching strategy.
        public CacheAttribute(string cacheKey, string absoluteExpirationRelativeToNow)
        {
            this.cacheKey = cacheKey;
            this.absoluteExpirationRelativeToNow = 
                                   TimeSpan.Parse(absoluteExpirationRelativeToNow);
        }

        public override async Task InvokeAsync
                        (AspectContext context, AsyncAspectDelegate next)
        {
            // The whole code is basically the same as we directly use MemoryCache
            var cache = context.ServiceProvider.GetRequiredService<IMemoryCache>();
            var r = await cache.GetOrCreateAsync(cacheKey, async e =>
            {
                await next(context);         // So the real method logic is in next, 
                                             // so just call it
                e.AbsoluteExpirationRelativeToNow = absoluteExpirationRelativeToNow;
                return context.ReturnValue;  // The results are all in ReturnValue. 
                                             // For simplicity, I won’t write 
                                             // void / Task<T> / ValueTask<T> and so on. 
                                             // Compatible codes for various return values.
            });
            context.ReturnValue = r;         // Set ReturnValue, because next will not be 
                                             // called within the validity period of the 
                                             // cache, so ReturnValue will not have a value, 
                                             // we need to set the cached result 
                                             // to ReturnValue
        }
    }

3. 进行测试

只需使用像这样的简单测试代码

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!"));
        }
    }

控制台结果

Hello World!
Hello Two!
Hello Two!

添加缓存设置

    public class TestCacheClient : ITestCacheClient
    {
        [Cache(nameof(Say), "00:00:03")]
        public string Say(string v) => v;
    }

控制台结果

Hello World!
Hello World!
Hello Two!

示例代码都在 https://github.com/fs7744/AopDemoList/tree/master/MakeMemoryChacheSimple
一个更全面的处理情况的示例在 https://github.com/fs7744/Norns.Urd/tree/main/src

历史

  • 2020年12月27日:初始版本
© . All rights reserved.