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<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。