AOP的姿势之 简化 MemoryCache 使用方式

    作者:甜甜更新于: 2020-12-30 22:19:43

    Web开发

    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();
    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();
    var r = await cache.GetOrCreateAsync(cacheKey, async e =>
    {
    await next(context); // 所以真正实现的方法逻辑都在 next 中,所以调用它就好了
    e.AbsoluteExpirationRelativeToNow = absoluteExpirationRelativeToNow;
    return context.ReturnValue; // 结果都在ReturnValue , 这里为了简单,就不写 void / Task / ValueTask 等等 各种返回值的兼容代码了
    });
    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()
    .ConfigureAop()
    .BuildServiceProvider()
    .GetRequiredService();
    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!

课课家教育

未登录