-
Notifications
You must be signed in to change notification settings - Fork 328
缓存拦截(AOP)
我们先来看看下面这段简单的代码。
public Product GetProduct(int id)
{
string cacheKey = $"product:{id}";
var val = _cache.Get<Product>(cacheKey);
if(val != null)
return val;
val = _db.GetProduct(id);
if(val != null)
_cache.Set<Product>(cacheKey, val, expiration);
return val;
}
其实这个方法做的事情很简单:
先从缓存中取出对应id的产品信息,如果能从缓存中取出来,就直接把缓存的数据返回出去,反之,就去数据库中查找对应的产品信息。
如果能在数据库中成功找到这个产品信息,就要把它丢进缓存中。逻辑就这些。
相信大部分人都写过类似的代码,思路也很清晰的表达出来了。
不过,写一两个这样的方法,或许还能接受,多了就会觉得很麻烦,然后就少不了CV大法了,结果就是随处可见的ifelse,这也就是为什么EasyCaching要提供一个这样的功能的主要原因。
举个日志的例子,相信大家就更加清晰了,方法的入参和出参都打印出来,我们肯定不可以一个方法一个方法的去加。
这里引入了切面编程的概念!
EasyCaching把CURD这4个常见的操作拆解成3种策略。
- Able
- Put
- Evict
其中,Able的行为就和上面的例子是一样的,就是先查后写,也就是我们常说的CURD中的C和R。
Able策略适用于大部分查询操作。实时性要求很高的要慎用或禁止使用!
下面来看看Put和Evict这两个策略。
Put策略,对应的就是CURD中的U(更新)操作。
如果对某个数据进行了修改操作,理当也要更新其对应的缓存数据。如果是更新频繁的数据,不建议使用!
Evict策略,对应的就是CURD中的D(删除)操作。
如果对某个数据进行了删除操作,理当也要清理其对应的缓存数据。
这就是三个策略的意义和作用。
缓存拦截有下面三个子模块。
- 缓存Key的生成规则
- 拦截的规则
- 拦截的配置及操作
这里的Key是根据要拦截的方法的相关信息(方法,参数)和自定义前缀等来进行自动生成的。
对于一些基础的数据类型会直接把它转化成字符串后进行拼接。
对于复杂类型,像自定义的类,EasyCaching提供了一个ICachable
接口,可以让用户自己定义生成这个类的缓存Key。
public interface ICachable
{
string CacheKey { get; }
}
Attribute是拦截的重要组成部分,对三种策略,提供了三个Attribute。
- EasyCachingAble
- EasyCachingPut
- EasyCachingEvict
要想拦截这个方法,只需要在其接口上面添加上面三个Attribute或者是继承了它们的一些Attribute。
示例:
public interface IDemoService
{
[EasyCachingAble(Expiration = 10)]
string GetCurrentUtcTime();
}
下面是一些可以用的参数说明
属性 | 描述 | 适用策略 |
---|---|---|
CacheKeyPrefix | 指定生成缓存Key的前缀 | All |
CacheProviderName | 指定要使用那个Provider | All |
IsHighAvailability | 指定是否要“高可用”(操作缓存出错,是否直接抛异常) | All |
Expiration | 缓存绝对过期时间,单位是秒 | Able 和 Put |
IsAll | 是否要删除以CacheKeyPrefix开头的所有缓存 | Evict |
IsBefore | 在执行方法之前删除缓存还是指定方法之后 | Evict |
这里要注意一点,如果要使用Put和Evict策略,尽可能要指定CacheKeyPrefix,不然会导致无法正确更新或删除缓存。这个是取决于生成缓存Key的实现,指定了CacheKeyPrefix会忽略方法信息。
目前EasyCaching只有一个CacheProviderName
的整体配置,主要是用于指定要用那个Provider,会被Attribute的属性覆盖。
EasyCaching目前提供了两种实现,一种基于AspectCore,另一种基于Castle+Autofac.Extras.DynamicProxy
在使用上,要区.NET Core 2.x 还是.NET Core 3.0。主要是因为 ConfigureServices方法就不支持直接返回System.IServiceProvider。
下面来看看要怎么使用。
需要使用0.8.0版本以上的EasyCaching!
首先要在Program上面添加UseServiceProviderFactory
的使用
// for aspcectcore
using AspectCore.Extensions.DependencyInjection;
// for castle
using Autofac.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
// for aspcectcore
.UseServiceProviderFactory(new AspectCoreServiceProviderFactory())
//// for castle
//.UseServiceProviderFactory(new AutofacServiceProviderFactory())
;
}
其次要在Startup里加入ConfigureContainer
方法
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IAspectCoreService, AspectCoreService>();
services.AddEasyCaching(options =>
{
options.UseInMemory("m1");
});
services.AddControllers();
// 1 AspectCore
services.ConfigureAspectCoreInterceptor(options => options.CacheProviderName = "m1");
services.AddTransient<ICastleService, CastleService>();
// 2 Castle
services.ConfigureCastleInterceptor(options => options.CacheProviderName = "m1");
}
#region ConfigureContainer方法只能有一个,并且它也只能带有一个参数!
// for aspectcore
public void ConfigureContainer(IServiceContainer builder)
{
builder.ConfigureAspectCoreInterceptor();
}
//// for castle
//public void ConfigureContainer(ContainerBuilder builder)
//{
// builder.ConfigureCastleInterceptor();
//}
#endregion
public void Configure(IApplicationBuilder app)
{
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
接口方法的定义
public interface IAspectCoreService
{
[EasyCachingAble(Expiration = 10)]
string GetCurrentUtcTime();
[EasyCachingPut(CacheKeyPrefix = "AspectCore")]
string PutSomething(string str);
[EasyCachingEvict(IsBefore = true)]
void DeleteSomething(int id);
// ...
}
控制器上面的调用
[Route("api/[controller]")]
public class ValuesController : Controller
{
private readonly IAspectCoreService _aService;
private readonly ICastleService _cService;
public ValuesController(
IAspectCoreService aService = null
, ICastleService cService = null
)
{
this._aService = aService;
this._cService = cService;
}
[HttpGet]
public string Get()
{
_aService.xxxx
_cService.xxxx
return "ok";
}
}
0.8.0版本以上的EasyCaching不再支持.NET Core 2.x了
在Startup中进行配置
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddScoped<IAspectCoreService, AspectCoreService>();
services.AddEasyCaching(options =>
{
options.UseInMemory("m1");
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddTransient<ICastleService, CastleService>();
// 1 AspectCore
return services.ConfigureAspectCoreInterceptor(options => options.CacheProviderName = "m1");
// // 2 Castle
// return services.ConfigureCastleInterceptor(options => options.CacheProviderName = "m1");
}
public void Configure(IApplicationBuilder app)
{
app.UseMvc();
}
}
接口中的方法定义和在控制器中的使用同.NET Core 3.0一致,这里就不在累赘了!