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


    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。

  • 相关阅读:
    从泛型类中继承
    DataGridView中的单元格提示错误信息
    C#中的转换
    C#的运算符重载
    解决android模块化升级方法
    个人总结如何在项目管理的实际软件开发工作的几个关键点和控制
    bash no such file or directory in ubuntu 1404
    java 遍历树节点 同时保留所有的从根到叶节点的路径
    ZendFramework2学习笔记 json和ajax
    POJ 2531-Network Saboteur(DFS)
  • 原文地址:https://www.cnblogs.com/fs7744/p/14197151.html
Copyright © 2020-2023  润新知