0. 前言

之前写了几篇文章介绍了一些AOP的知识,

但是还没有亮出来AOP的姿势,

也许姿势漂亮一点,

大家会对AOP有点兴趣

内容大致会分为如下几篇:(毕竟人懒,一下子写完太累了,没有动力)

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

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

AOP的姿势之 如何把 HttpClient 变为声明式

至于AOP框架在这儿示例依然会使用我自己基于emit实现的动态代理AOP框架: https://github.com/fs7744/Norns.Urd

毕竟是自己写的,魔改/加功能都很方便,

万一万一大家如果有疑问,(虽然大概不会有),我也好回答, (当然如果大家认可,在github给个star,就实在是太让人开心了)

1. 非常重要的注意事项

本篇主要目的是介绍如何利用AOP简化使用Cache的代码的方式

但是在真实业务场景如果要混用 MemoryCache 和 DistributedCache,

最好贴合场景好好思考一下,为何要这样用?

每多加一个cache就是增加一层复杂度,

如果一层cache不能解决问题?

那么两层就能吗?三层就能吗?特别是缓存穿透等等怎么办呢?

一层不能解决问题的原因是什么呢?

希望大家三思而行,哈哈

2. 如何混用呢?

2.1 统一模型,统一接口

MemoryCache 和 DistributedCache 的接口定义虽然相似度和思想很接近,

但是呢,还是存在不一样,

大家可以看下面的接口定义

    public interface IMemoryCache : IDisposable
{
//
// 摘要:
// Create or overwrite an entry in the cache.
//
// 参数:
// key:
// An object identifying the entry.
//
// 返回结果:
// The newly created Microsoft.Extensions.Caching.Memory.ICacheEntry instance.
ICacheEntry CreateEntry(object key);
//
// 摘要:
// Removes the object associated with the given key.
//
// 参数:
// key:
// An object identifying the entry.
void Remove(object key);
//
// 摘要:
// Gets the item associated with this key if present.
//
// 参数:
// key:
// An object identifying the requested entry.
//
// value:
// The located value or null.
//
// 返回结果:
// true if the key was found.
bool TryGetValue(object key, out object value);
}
    public interface IDistributedCache
{
//
// 摘要:
// Gets a value with the given key.
//
// 参数:
// key:
// A string identifying the requested value.
//
// 返回结果:
// The located value or null.
byte[] Get(string key);
//
// 摘要:
// Gets a value with the given key.
//
// 参数:
// key:
// A string identifying the requested value.
//
// token:
// Optional. The System.Threading.CancellationToken used to propagate notifications
// that the operation should be canceled.
//
// 返回结果:
// The System.Threading.Tasks.Task that represents the asynchronous operation, containing
// the located value or null.
Task<byte[]> GetAsync(string key, CancellationToken token = default);
//
// 摘要:
// Refreshes a value in the cache based on its key, resetting its sliding expiration
// timeout (if any).
//
// 参数:
// key:
// A string identifying the requested value.
void Refresh(string key);
//
// 摘要:
// Refreshes a value in the cache based on its key, resetting its sliding expiration
// timeout (if any).
//
// 参数:
// key:
// A string identifying the requested value.
//
// token:
// Optional. The System.Threading.CancellationToken used to propagate notifications
// that the operation should be canceled.
//
// 返回结果:
// The System.Threading.Tasks.Task that represents the asynchronous operation.
Task RefreshAsync(string key, CancellationToken token = default);
//
// 摘要:
// Removes the value with the given key.
//
// 参数:
// key:
// A string identifying the requested value.
void Remove(string key);
//
// 摘要:
// Removes the value with the given key.
//
// 参数:
// key:
// A string identifying the requested value.
//
// token:
// Optional. The System.Threading.CancellationToken used to propagate notifications
// that the operation should be canceled.
//
// 返回结果:
// The System.Threading.Tasks.Task that represents the asynchronous operation.
Task RemoveAsync(string key, CancellationToken token = default);
//
// 摘要:
// Sets a value with the given key.
//
// 参数:
// key:
// A string identifying the requested value.
//
// value:
// The value to set in the cache.
//
// options:
// The cache options for the value.
void Set(string key, byte[] value, DistributedCacheEntryOptions options);
//
// 摘要:
// Sets the value with the given key.
//
// 参数:
// key:
// A string identifying the requested value.
//
// value:
// The value to set in the cache.
//
// options:
// The cache options for the value.
//
// token:
// Optional. The System.Threading.CancellationToken used to propagate notifications
// that the operation should be canceled.
//
// 返回结果:
// The System.Threading.Tasks.Task that represents the asynchronous operation.
Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default);
}

那么我们为了让多个不同实现的缓存接口能被同一段缓存操作代码所使用,

就需要定义统一的接口并适配各种不同的缓存接口

(当然,我们不这样做也能凑出代码达到相同效果,但是呢,别人看到这样的实现,难免会吐槽我们的代码,如果不小心听见,面子有点挂不住呀)

这里呢,我们就这样简单定义一个这样的接口

    public interface ICacheAdapter
{
// Cache 实现的名字,以此能指定使用哪种缓存实现
string Name { get; } // 尝试获取缓存
bool TryGetValue<T>(string key, out T result); // 存入缓存数据,这里为了简单,我们就只支持ttl过期策略
void Set<T>(string key, T result, TimeSpan ttl);
}

2.2 适配MemoryCache

    [NonAspect]
public class MemoryCacheAdapter : ICacheAdapter
{
private readonly IMemoryCache cache; public MemoryCacheAdapter(IMemoryCache cache)
{
this.cache = cache;
} // 取个固定名字
public string Name => "memory"; public void Set<T>(string key, T result, TimeSpan ttl)
{
cache.Set(key, result, ttl);
} public bool TryGetValue<T>(string key, out T result)
{
return cache.TryGetValue(key, out result);
}
}

2.3 适配DistributedCache

    [NonAspect]
public class DistributedCacheAdapter : ICacheAdapter
{
private readonly IDistributedCache cache;
private readonly string name; public DistributedCacheAdapter(IDistributedCache cache, string name)
{
this.cache = cache;
this.name = name;
} /// 这里我们就不固定名字了,大家想用 redis 就可以自己名字取redis
public string Name => name; public void Set<T>(string key, T result, TimeSpan ttl)
{
cache.Set(key,
JsonSerializer.SerializeToUtf8Bytes(result), // 为了简单,我们就不在扩展更多不同序列化器了,这里就用System.Text.Json
new DistributedCacheEntryOptions() { AbsoluteExpirationRelativeToNow = ttl }); // 同样,为了简单,也只支持ttl缓存策略
} public bool TryGetValue<T>(string key, out T result)
{
var data = cache.Get(key);
if (data == null)
{
result = default;
return false;
}
else
{
result = JsonSerializer.Deserialize<T>(data);
return true;
}
}
}

2.4 定义CacheAttribute

这里我们依然使用 attribute 这种对大家使用最简单的方式

但是呢,由于有多个缓存实现使用,

我们直接使用 InterceptorAttribute 很难控制不同缓存实现的使用,

所以我们这里拆分 缓存使用的定义 与 真正缓存的调用逻辑

CacheAttribute 只是缓存使用的定义

    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class CacheAttribute : Attribute
{
// 由于多个缓存实现,我们需要有使用顺序指定
public int Order { get; set; }
public string CacheKey { get; set; }
public string Ttl { get; set; }
public string CacheName { get; set; }
}

2.5 实现CacheInterceptor

    public class CacheInterceptor : AbstractInterceptor
{
public override bool CanAspect(MethodReflector method)
{
return method.IsDefined<CacheAttribute>(); // 限制只对有缓存定义的方法起效
} public override async Task InvokeAsync(AspectContext context, AsyncAspectDelegate next)
{
var caches = context.ServiceProvider.GetRequiredService<IEnumerable<ICacheAdapter>>()
.ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase); var cas = context.Method.GetReflector()
.GetCustomAttributes<CacheAttribute>()
.OrderBy(i => i.Order)
.ToArray(); // 为了简单,我们就使用最简单的反射形式调用
var m = typeof(CacheInterceptor).GetMethod(nameof(CacheInterceptor.GetOrCreateAsync))
.MakeGenericMethod(context.Method.ReturnType.GetGenericArguments()[0]);
await (Task)m.Invoke(this, new object[] { caches, cas, context, next, 0 });
} public async Task<T> GetOrCreateAsync<T>(Dictionary<string, ICacheAdapter> adapters, CacheAttribute[] options, AspectContext context, AsyncAspectDelegate next, int index)
{
if (index >= options.Length)
{
Console.WriteLine($"No found Cache at {DateTime.Now}.");
// 所有cache 都找完了,没有找到有效cache,所以需要拿真正的结果
await next(context);
// 为了简单,我们就只支持 Task<T> 的结果
return ((Task<T>)context.ReturnValue).Result;
} var op = options[index];
T result;
var cacheName = op.CacheName;
if (adapters.TryGetValue(cacheName, out var adapter))
{
if (!adapter.TryGetValue<T>(op.CacheKey, out result))
{
// 当前缓存找不到结果,移到下一个缓存获取结果
result = await GetOrCreateAsync<T>(adapters, options, context, next, ++index);
adapter.Set(op.CacheKey, result, TimeSpan.Parse(op.Ttl)); // 更新当前缓存实现的存储
context.ReturnValue = Task.FromResult(result); // 为了简单,我们就在这儿更新返回结果,其实不该在这儿的,为什么,大家可以猜一猜为什么?
}
else
{
Console.WriteLine($"Get Cache From {cacheName} at {DateTime.Now}.");
context.ReturnValue = Task.FromResult(result); // 为了简单,我们就在这儿更新返回结果,其实不该在这儿的,为什么,大家可以猜一猜为什么?
}
}
else
{
throw new ArgumentException($"No such cache: {cacheName}.");
} return result;
}
}

2.6 测试

    public class DoCacheTest
{
[Cache(CacheKey = nameof(Do), CacheName = "memory", Order = 0, Ttl = "00:00:01")] // 1秒过期
[Cache(CacheKey = nameof(Do), CacheName = "distribute", Order = 1, Ttl = "00:00:05")] // 5秒过期
public virtual Task<string> Do() => Task.FromResult(DateTime.Now.ToString());
} class Program
{
static async Task Main(string[] args)
{
var sut = new ServiceCollection()
.AddTransient<DoCacheTest>()
.ConfigureAop(i => i.GlobalInterceptors.Add(new CacheInterceptor())) // 设置Cache拦截器
.AddMemoryCache()
.AddDistributedMemoryCache() // 为了测试,我们就不使用redis之类的东西了,用个内存实现模拟就好
.AddSingleton<ICacheAdapter, MemoryCacheAdapter>() // 添加缓存适配器
.AddSingleton<ICacheAdapter>(i => new DistributedCacheAdapter(i.GetRequiredService<IDistributedCache>(), "distribute"))
.BuildServiceProvider()
.GetRequiredService<DoCacheTest>(); for (int i = 0; i < 20; i++)
{
Console.WriteLine($"Get: {await sut.Do()}");
await Task.Delay(500); // 每隔半秒,观察缓存变化
}
}
}

结果:

No found Cache at 2021/1/3 11:56:10.
Get: 2021/1/3 11:56:10 Get Cache From memory at 2021/1/3 11:56:10.
Get: 2021/1/3 11:56:10
Get Cache From distribute at 2021/1/3 11:56:11.
Get: 2021/1/3 11:56:10
Get Cache From memory at 2021/1/3 11:56:11.
Get: 2021/1/3 11:56:10
Get Cache From distribute at 2021/1/3 11:56:12.
Get: 2021/1/3 11:56:10
Get Cache From memory at 2021/1/3 11:56:12.
Get: 2021/1/3 11:56:10
Get Cache From distribute at 2021/1/3 11:56:13.
Get: 2021/1/3 11:56:10
Get Cache From memory at 2021/1/3 11:56:13.
Get: 2021/1/3 11:56:10
Get Cache From distribute at 2021/1/3 11:56:14.
Get: 2021/1/3 11:56:10
Get Cache From memory at 2021/1/3 11:56:14.
Get: 2021/1/3 11:56:10 No found Cache at 2021/1/3 11:56:15.
Get: 2021/1/3 11:56:15 Get Cache From memory at 2021/1/3 11:56:15.
Get: 2021/1/3 11:56:15
Get Cache From distribute at 2021/1/3 11:56:16.
Get: 2021/1/3 11:56:15
Get Cache From memory at 2021/1/3 11:56:16.
Get: 2021/1/3 11:56:15
Get Cache From distribute at 2021/1/3 11:56:17.
Get: 2021/1/3 11:56:15
Get Cache From memory at 2021/1/3 11:56:17.
Get: 2021/1/3 11:56:15
Get Cache From distribute at 2021/1/3 11:56:18.
Get: 2021/1/3 11:56:15
Get Cache From memory at 2021/1/3 11:56:18.
Get: 2021/1/3 11:56:15
Get Cache From distribute at 2021/1/3 11:56:19.
Get: 2021/1/3 11:56:15
Get Cache From memory at 2021/1/3 11:56:19.
Get: 2021/1/3 11:56:15

就是这样,大家就可以很简单的混用 各种缓存了,

但是呢,多个缓存有没有用?缓存穿透等等问题需要大家最好想好才使用哦

完整的demo 放在 https://github.com/fs7744/AopDemoList/tree/master/MultipleCache/MultipleCache

AOP的姿势之 简化混用 MemoryCache 和 DistributedCache 的方式的更多相关文章

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

    0. 前言 之前写了几篇文章介绍了一些AOP的知识, 但是还没有亮出来AOP的姿势, 也许姿势漂亮一点, 大家会对AOP有点兴趣 内容大致会分为如下几篇:(毕竟人懒,一下子写完太累了,没有动力) AO ...

  2. AOP的具体实践-简化结果返回的处理

    原因: 以前学习Spring的时候着重学习过AOP概念,但是一直也没有用上,唯一碰到过的就是Spring内置的事务管理.现在碰到过一些结果后面的操作适合用到,所以这里就拿出来用一下,并且复习一下落下的 ...

  3. spring AOP 代理机制、执行过程、四种实现方式及示例详解

    1.加载过程 spring首先检测配置文件中的代理配置,然后去加载bean; 如果配置文件中没有配置代理,自然代理不会生效,如果配置了代理,但是代理还没有生效,那么有可能是加载顺序的问题,即在检测到代 ...

  4. 8 -- 深入使用Spring -- 4...6 AOP代理:基于注解的XML配置文件的管理方式

    8.4.6 基于XML配置文件的管理方式 Spring 2.x 提供一个新的aop:命名空间来定义切面.切入点和增强处理. XML配置方式优点: ⊙ 如果应用没有使用JDK 1.5 以上版本,那么应用 ...

  5. :Spring-06 -AOP [面向切面编程] -配置异常通知的两种方式--AspectJ 方式 -Schema-based 方式

    三.配置异常通知的步骤(AspectJ 方式) 1.只有当切点报异常才能触发异常通知 2.在spring 中有AspectJ 方式提供了异常通知的办法 3.实现步骤: 3.1新建类,在类写任意名称的方 ...

  6. Spring之AOP原理、代码、使用详解(XML配置方式)

    Spring 的两大核心,一是IOC,另一个是AOP,本博客从原理.AOP代码以及AOP使用三个方向来讲AOP.先给出一张AOP相关的结构图,可以放大查看. 一.Spring AOP 接口设计 1.P ...

  7. 浅谈MemoryCache的原生插值方式

    .NET运行时内置了常用的缓存模块: MemoryCache 标准的MemoryCache暴露了如下几个属性和方法: public int Count { get; } public void Com ...

  8. [转]彻底征服 Spring AOP 之 理论篇

    基本知识 其实, 接触了这么久的 AOP, 我感觉, AOP 给人难以理解的一个关键点是它的概念比较多, 而且坑爹的是, 这些概念经过了中文翻译后, 变得面目全非, 相同的一个术语, 在不同的翻译下, ...

  9. java框架篇---spring AOP 实现原理

    什么是AOP AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善.OOP引入 ...

随机推荐

  1. PyQt(Python+Qt)学习随笔:QListWidget的currentRow属性

    QListWidget的currentRow属性保存当前项的位置,为整型,从0开始计数,在某些选择模式下,当前项可能也是选中项. currentRow属性可以通过方法currentRow().setC ...

  2. 第14.16节 爬虫实战2:赠人玫瑰,手留余香! request+BeautifulSoup实现csdn博文自动点赞

    写在前面:本文仅供参考学习,请勿用作它途,禁止转载! 在<第14.14节 爬虫实战准备:csdn博文点赞过程http请求和响应信息分析>老猿分析了csdn博文点赞处理的http请求和响应报 ...

  3. Shiro remeberMe反序列化漏洞复现(Shiro-550)

    Apache Shiro是一个强大易用的Java安全框架,提供了认证.授权.加密和会话管理等功能.Shiro框架直观.易用,同时也能提供健壮的安全性.在Apache Shiro编号为550的 issu ...

  4. 攻防世界 ctf web进阶区 unserialize

    进入到题目的界面,看到以下源码 构造payload=?code=O:4:"xctf":1:{s:4:"flag";s:3:"111";} 结 ...

  5. Java 8 中的方法引用,轻松减少代码量,提升可读性!

    1. 引言 Java8中最受广大开发中喜欢的变化之一是因为引入了 lambda 表达式,因为这些表达式允许我们放弃匿名类,从而大大减少了样板代码,并提高了可读性. 方法引用是lambda表达式的一种特 ...

  6. kubeadm 的工作原理

    kubeadm 的工作原理 作者:张首富 时间:2020-06-04 w x:y18163201 相信使用二进制部署过 k8s 集群的同学们都知道,二进制部署集群太困难了,有点基础的人部署起来还有成功 ...

  7. 解压版mysql+免破解版Navicat,好用!

    解压版mysql安装流程 获取mysql压缩包 获取地址: 链接:https://pan.baidu.com/s/1HqdFDQn_6ccPM0gOftApIg 提取码:n19t 获取压缩包后可安装压 ...

  8. Docker部署FastDFS(附示例代码)

    1. FastDFS简介   FastDFS是一个开源的分布式文件系统,它对文件进行管理,功能包括:文件存储.文件同步.文件访问(文件上传.文件下载)等,解决了大容量存储和负载均衡的问题.特别适合以文 ...

  9. 轮廓检测论文解读 | Richer Convolutional Features for Edge Detection | CVPR | 2017

    有什么问题可以加作者微信讨论,cyx645016617 上千人的粉丝群已经成立,氛围超好.为大家提供一个遇到问题有可能得到答案的平台. 0 概述 论文名称:"Richer Convoluti ...

  10. js上 三、数据类型

    3.1.什么是数据类型 a. 什么是数据类型? 想从生活中出发: 考验智商的时刻到了: 1(只)+1(只)=1(双) 3(天)+4(天)=1(周) 5(月)+7(月)=1(年) 4(时)+9(时)=1 ...