前言:
本工具启发于Spring Cache,故使用C#的Attribute去模仿Spring的@Cahce相关的注解,试图实现一样的效果。
代码地址:https://gitee.com/godenSpirit/cache-attribute,
欢迎提出意见和改善。
结构:
Attribute注解
using System;
using System.Collections.Generic;
using System.Text;
namespace CacheAttribute.Attributes
{
[AttributeUsage(validOn: AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class CacheAbleAttribute : Attribute
{
//缓存名前缀
public string preffix { get; }
//缓存名主体-----(可以解析方法参数的值)
public string Totoal_Name { get; }
//是否生效
public bool work {get;}
//缓存时间
public int Expire;
public CacheAbleAttribute(string preffix, bool work, string totoal_Name, int expire = 1800)
{
this.preffix = preffix;
Totoal_Name = totoal_Name;
this.work = work;
Expire = expire;
}
}
}
Cache接口和实现
using System;
using System.Collections.Generic;
using System.Text;
namespace CacheAttribute.Utils
{
public interface ICacheable
{
/// <summary>
/// 泛型获取
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
T Get<T>(string key);
/// <summary>
/// object获取
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
object Get(string key);
/// <summary>
/// 设置缓存
/// </summary>
/// <param name="key"></param>
/// <param name="obj"></param>
/// <param name="Expire"></param>
/// <returns></returns>
bool Set(string key, object obj, int Expire = 1800);
/// <summary>
/// 缓存是否存在
/// </summary>
/// <param name="Key"></param>
/// <returns></returns>
bool Exist(string Key);
/// <summary>
/// 移除缓存
/// </summary>
/// <param name="Key"></param>
/// <returns></returns>
bool Remove(string Key);
/// <summary>
/// 匹配移除
/// </summary>
/// <param name="Pattern"></param>
/// <returns></returns>
bool RemovePattern(string Pattern);
}
}
Redis的实现类
using SugarRedis;
using System;
using System.Collections.Generic;
using System.Text;
namespace CacheAttribute.Utils
{
public class RedisCahceUtil : ICacheable
{
//这里直接使用了SqlSugar提供的Redis工具
private static SugarRedisClient client;
private static RedisCahceUtil instance;
//使用单例模式
private RedisCahceUtil(String config = null) {
if (!string.IsNullOrEmpty(config))
client = new SugarRedisClient(config);
else
client = new SugarRedisClient();
}
//如果不是本地Redis的测试环境,请从Configuration中传入连接参数
public static RedisCahceUtil GetInstance()
{
if (instance == null)
{
instance = new RedisCahceUtil();
return instance;
}
else
return instance;
}
public bool Exist(string Key)
{
return client.Exists(Key);
}
public T Get<T>(string key)
{
return client.Get<T>(key);
}
public object Get(string key)
{
return client.Get(key);
}
public bool Remove(string Key)
{
try
{
client.Remove(Key);
return true;
}
catch(Exception e)
{
return false;
}
}
public bool RemovePattern(string Pattern)
{
try
{
client.RemoveCacheRegex(Pattern);
return true;
}catch(Exception e)
{
return false;
}
}
public bool Set(string key, object obj, int Expire = 1800)
{
return client.Set(key, obj, Expire);
}
}
}
CacheFilter
using CacheAttribute.Attributes;
using CacheAttribute.Utils;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace CacheAttribute.Filter
{
/// <summary>
/// 异步过滤器,调用缓存方法
/// </summary>
public class AsyncCacheAtttibuteCheckFilter : IAsyncActionFilter
{
//缓存工具
private readonly ICacheable _cache;
//缓存策略
private readonly CacheOperation operation;
//日志工具
private readonly ILogger<AsyncCacheAtttibuteCheckFilter> logger;
public AsyncCacheAtttibuteCheckFilter(ICacheable cache, CacheOperation operation, ILogger<AsyncCacheAtttibuteCheckFilter> logger)
{
_cache = cache;
this.operation = operation;
this.logger = logger;
}
/// <summary>
/// 异步过滤方法,可以用Invoke来使原方法执行
/// </summary>
/// <param name="context"></param>
/// <param name="next"></param>
/// <returns></returns>
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
#region 方法执行前
logger.LogInformation("方法执行前,获取参数");
//强转获取cntroller实例
ControllerActionDescriptor controller = (ControllerActionDescriptor)context.ActionDescriptor;
//获取调用接口的反射对象
MethodInfo action = controller.MethodInfo;
//获取Cacheable注解
CacheAbleAttribute cacheable = (CacheAbleAttribute)action.GetCustomAttribute(typeof(CacheAbleAttribute));
//获取CacheDel注解
CacheDelAttribute cacheDel = (CacheDelAttribute)action.GetCustomAttribute(typeof(CacheDelAttribute));
//获取CacheUpdate注解
CacheUpdateAttribute cacheUpdate = (CacheUpdateAttribute)action.GetCustomAttribute(typeof(CacheUpdateAttribute));
#region CacheAble注解处理
//当有cacheable注解时
if (cacheable != null)
{
//当使用CacheAble时,同时也具有CacheInit时,先处理CacheInit
if (action.GetCustomAttribute(typeof(CacheInitAttribute)) != null)
{
bool isSus = operation.CacheInit_strategy(action, _cache);
}
operation.CacheAble_Strategy_Before(context, _cache);
}
#endregion
//删除正则匹配到的缓存
if (cacheDel != null)
{
if (!operation.CacheDel_strategy(action, _cache))
{
logger.LogInformation(this.GetType().Name + ":" + "没有相应的缓存被删除");
}
else
{
logger.LogInformation(this.GetType().Name + ":" + "删除了相应的缓存");
}
}
//相当于强化版的Cacheable注解
if (cacheUpdate != null && cacheUpdate.work == true)
{
operation.CacheUpdate_strategy_Before(context, _cache);
}
#endregion
//await next();
#region 方法执行后
logger.LogInformation("方法执行后,去获取结果");
Task<ActionExecutedContext> task = next.Invoke();
ControllerActionDescriptor controller2 = (ControllerActionDescriptor)context.ActionDescriptor;
MethodInfo action2 = controller.MethodInfo;
//获取cacheable注解
CacheAbleAttribute cacheable2 = (CacheAbleAttribute)action2.GetCustomAttribute(typeof(CacheAbleAttribute));
//获取cacheUpdate注解
CacheUpdateAttribute cacheUpdate2 = (CacheUpdateAttribute)action2.GetCustomAttribute(typeof(CacheUpdateAttribute));
//当有cacheable注解时
if (cacheable2 != null && cacheable2.work == true)
{
operation.CacheAble_Strategy_After(task.Result,context, _cache);
}
if (cacheUpdate2 != null && cacheUpdate2.work == true)
{
operation.CacheUpdate_strategy_After(task.Result,context, _cache);
}
#endregion
}
}
}
将执行代码通过策略模式抽取到CacheOperation
/// <summary>
/// 缓存选项
/// 主要是提供自定义缓存策略的方法
/// </summary>
public class CacheOperation
{
private readonly ILogger<CacheOperation> logger;
/// <summary>
/// 构造函数
/// </summary>
public CacheOperation(ILogger<CacheOperation> logger)
{
this.logger = logger;
//为了简洁,我将提供的默认策略初始化省略,当然大家也可以在注入时自己设置自定义的策略
........
}
/// <summary>
/// 自定义缓存策略------------方法执行前
/// </summary>
public Action<ActionExecutingContext,ICacheable> CacheAble_Strategy_Before { get; set; }
/// <summary>
/// 自定义缓存策略-------------方法执行后
/// </summary>
public Action<ActionExecutedContext,ActionExecutingContext,ICacheable> CacheAble_Strategy_After { get; set; }
/// <summary>
/// 自定义删除策略
/// </summary>
public Func<ActionExecutingContext, ICacheable, bool> CacheDel_strategy { get; set; }
/// <summary>
/// 自定义更新策略-------------方法执行前
/// </summary>
public Action<ActionExecutingContext, ICacheable> CacheUpdate_strategy_Before { get; set; }
/// <summary>
/// 自定义更新策略-------------方法执行后
/// </summary>
public Action<ActionExecutedContext,ActionExecutingContext, ICacheable> CacheUpdate_strategy_After { get; set; }
/// <summary>
/// 自定义初始化缓存值
/// </summary>
public Func<MethodInfo, ICacheable,bool> CacheInit_strategy { get; set; }
}
}
缓存名解析
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using System.Text;
namespace CacheAttribute.Filter
{
/// <summary>
/// 缓存名称过滤器
/// 主要从来对注解中设置的缓存名Total_Name做解析,使之能够像@CacheAble一样解析方法参数
/// eg:#arg,#arg.pro
/// </summary>
public class CacheNameResolver
{
/// <summary>
/// 解析正确的TotalName
/// </summary>
/// <param name="TotalName"></param>
/// <param name="context"></param>
/// <returns></returns>
public static string ResolveTotalName([Required]string TotalName,ActionExecutingContext context)
{
//目标缓存名
string TargetCacheName = TotalName;
//获取方法的所有参数信息(这个方法可以获取参数的属性&名称信息,无法拿到参数的值
List<ParameterDescriptor> parameters_list = context.ActionDescriptor.Parameters.ToList();
//这个方法可以拿到值,但不能获取参数的属性信息
IDictionary<string, object> argsMap = context.ActionArguments;
//item==切面方法参数
parameters_list.ForEach(item => {
//如果参数为基础类型,则直接替换为参数值
if (TargetCacheName.Contains("#" + item.Name) && IsBasicType(item.ParameterType))
{
TargetCacheName = TargetCacheName.Replace("#" + item.Name, argsMap[item.Name].ToString());
}
//接下来考虑参数为引用类型的情况下,再去获取一次下属属性
PropertyInfo[] propertyInfos = item.ParameterType.GetProperties();
foreach(PropertyInfo pro in propertyInfos)
{
//如果是引用类型则通过.可以获取下属的属性值
if (TargetCacheName.Contains("#" + item.Name + "." + pro.Name)&&IsBasicType(pro.PropertyType))
{
TargetCacheName = TargetCacheName.Replace("#" + item.Name + "." + pro.Name, pro.GetValue(argsMap[item.Name]).ToString());
}
}
});
return TargetCacheName;
}
//工具方法-------判断一个类型是否是基础数据类型
public static bool IsBasicType(Type type)
{
if (type.Equals(typeof(int)) ||
type.Equals(typeof(double)) ||
type.Equals(typeof(float)) ||
type.Equals(typeof(bool)) ||
type.Equals(typeof(string)) ||
type.Equals(typeof(byte)) ||
type.Equals(typeof(char)) ||
type.Equals(typeof(long)) ||
type.Equals(typeof(DateTime)) ||
type.Equals(typeof(decimal))
)
{
return true;
}
return false;
}
}
}
使用方法
使用方法非常简单
//注册缓存实现实例
builder.Services.AddSingleton(typeof(ICacheable), RedisCahceUtil.GetInstance());
//注册缓存策略类-----(可以先创建,然后再自定义相关策略再注册到容器)
builder.Services.AddSingleton(typeof(CacheOperation));
//添加该Filer即可
builder.Services.AddMvc(option => {
option.Filters.Add(typeof(AsyncCacheAtttibuteCheckFilter));
});
测试
在Gitee地址中自带一个WebAPI的测试,可以直接在其基础上扩写