上一節是全部緩存,很浪費內存,所有很多時候我們只是緩存幾個方法,下面是自定義緩存代碼:
一、依賴包
二、定義一個簡單的緩存接口
/// <summary> /// 簡單的緩存接口,只有查詢和添加,以后會進行擴展 /// </summary> public interface ICaching { object Get(string cacheKey); void Set(string cacheKey, object cacheValue, TimeSpan absoluteExpirationRelativeToNow); }
三、實現緩存接口
public class MemoryCaching : ICaching { private IMemoryCache _cache; public MemoryCaching(IMemoryCache cache) { _cache = cache; } public object Get(string cacheKey) { return _cache.Get(cacheKey); } public void Set(string cacheKey, object cacheValue, TimeSpan absoluteExpirationRelativeToNow) { _cache.Set(cacheKey, cacheValue, absoluteExpirationRelativeToNow); } }
四、定義緩存屬性
/// <summary> /// 這個Attribute就是使用時候的驗證,把它添加到要緩存數據的方法中,即可完成緩存的操作。 /// 往Server層方法上添加即可使用 [CachingAttribute(AbsoluteExpiration = 10)] //使用緩存AOP 緩存10分鍾 /// </summary> [AttributeUsage(AttributeTargets.Method, Inherited = true)] public class CachingAttribute : Attribute { /// <summary> /// 緩存絕對過期時間(分鍾) /// </summary> public int AbsoluteExpiration { get; set; } = 30; }
五、AOP實現
/// <summary> /// 面向切面的緩存使用 /// </summary> public class BlogCacheAOP : IInterceptor { //通過注入的方式,把緩存操作接口通過構造函數注入 private readonly ICaching _cache; public BlogCacheAOP(ICaching cache) { _cache = cache; } //Intercept方法是攔截的關鍵所在,也是IInterceptor接口中的唯一定義 public void Intercept(IInvocation invocation) { var method = invocation.MethodInvocationTarget ?? invocation.Method; //對當前方法的特性驗證 var qCachingAttribute = this.GetQCachingAttributeInfo(invocation.MethodInvocationTarget ?? invocation.Method); if (qCachingAttribute != null) { ProceedCaching(invocation, qCachingAttribute); } else { invocation.Proceed();//直接執行被攔截方法 } } private CachingAttribute GetQCachingAttributeInfo(MethodInfo method) { return method.GetCustomAttributes(true).FirstOrDefault(x => x.GetType() == typeof(CachingAttribute)) as CachingAttribute; } private void ProceedCaching(IInvocation invocation, CachingAttribute attribute) { //獲取自定義緩存鍵 var cacheKey = CustomCacheKey(invocation); //根據key獲取相應的緩存值 var cacheValue = _cache.Get(cacheKey); if (cacheValue != null) { //將當前獲取到的緩存值,賦值給當前執行方法 invocation.ReturnValue = cacheValue; return; } //去執行當前的方法 invocation.Proceed(); //存入緩存 if (!string.IsNullOrWhiteSpace(cacheKey)) { _cache.Set(cacheKey, invocation.ReturnValue, TimeSpan.FromMinutes(attribute.AbsoluteExpiration)); } } /// <summary> /// 自定義緩存的key /// </summary> /// <param name="invocation"></param> /// <returns></returns> protected string CustomCacheKey(IInvocation invocation) { var typeName = invocation.TargetType.Name; var methodName = invocation.Method.Name; var methodArguments = invocation.Arguments.Select(GetArgumentValue).Take(3).ToList();//獲取參數列表,最多三個 string key = $"{typeName}:{methodName}:"; foreach (var param in methodArguments) { key = $"{key}{param}:"; } return key.TrimEnd(':'); } /// <summary> /// object 轉 string /// </summary> /// <param name="arg"></param> /// <returns></returns> protected static string GetArgumentValue(object arg) { if (arg is DateTime || arg is DateTime?) return ((DateTime)arg).ToString("yyyyMMddHHmmss"); if (arg is string || arg is ValueType || arg is Nullable) return arg.ToString(); if (arg != null) { if (arg is Expression) { var obj = arg as Expression; var result = Resolve(obj); return Common.Helper.MD5Helper.MD5Encrypt16(result); } else if (arg.GetType().IsClass) { return Common.Helper.MD5Helper.MD5Encrypt16(Newtonsoft.Json.JsonConvert.SerializeObject(arg)); } } return string.Empty; } private static string Resolve(Expression expression) { if (expression is LambdaExpression) { LambdaExpression lambda = expression as LambdaExpression; expression = lambda.Body; return Resolve(expression); } if (expression is BinaryExpression) { BinaryExpression binary = expression as BinaryExpression; if (binary.Left is MemberExpression && binary.Right is ConstantExpression)//解析x=>x.Name=="123" x.Age==123這類 return ResolveFunc(binary.Left, binary.Right, binary.NodeType); if (binary.Left is MethodCallExpression && binary.Right is ConstantExpression)//解析x=>x.Name.Contains("xxx")==false這類的 { object value = (binary.Right as ConstantExpression).Value; return ResolveLinqToObject(binary.Left, value, binary.NodeType); } if ((binary.Left is MemberExpression && binary.Right is MemberExpression) || (binary.Left is MemberExpression && binary.Right is UnaryExpression))//解析x=>x.Date==DateTime.Now這種 { LambdaExpression lambda = Expression.Lambda(binary.Right); Delegate fn = lambda.Compile(); ConstantExpression value = Expression.Constant(fn.DynamicInvoke(null), binary.Right.Type); return ResolveFunc(binary.Left, value, binary.NodeType); } } if (expression is UnaryExpression) { UnaryExpression unary = expression as UnaryExpression; if (unary.Operand is MethodCallExpression)//解析!x=>x.Name.Contains("xxx")或!array.Contains(x.Name)這類 return ResolveLinqToObject(unary.Operand, false); if (unary.Operand is MemberExpression && unary.NodeType == ExpressionType.Not)//解析x=>!x.isDeletion這樣的 { ConstantExpression constant = Expression.Constant(false); return ResolveFunc(unary.Operand, constant, ExpressionType.Equal); } } if (expression is MemberExpression && expression.NodeType == ExpressionType.MemberAccess)//解析x=>x.isDeletion這樣的 { MemberExpression member = expression as MemberExpression; ConstantExpression constant = Expression.Constant(true); return ResolveFunc(member, constant, ExpressionType.Equal); } if (expression is MethodCallExpression)//x=>x.Name.Contains("xxx")或array.Contains(x.Name)這類 { MethodCallExpression methodcall = expression as MethodCallExpression; return ResolveLinqToObject(methodcall, true); } var body = expression as BinaryExpression; //已經修改過代碼body應該不會是null值了 if (body == null) return string.Empty; var Operator = GetOperator(body.NodeType); var Left = Resolve(body.Left); var Right = Resolve(body.Right); string Result = string.Format("({0} {1} {2})", Left, Operator, Right); return Result; } private static string GetOperator(ExpressionType expressiontype) { switch (expressiontype) { case ExpressionType.And: return "and"; case ExpressionType.AndAlso: return "and"; case ExpressionType.Or: return "or"; case ExpressionType.OrElse: return "or"; case ExpressionType.Equal: return "="; case ExpressionType.NotEqual: return "<>"; case ExpressionType.LessThan: return "<"; case ExpressionType.LessThanOrEqual: return "<="; case ExpressionType.GreaterThan: return ">"; case ExpressionType.GreaterThanOrEqual: return ">="; default: throw new Exception(string.Format("不支持{0}此種運算符查找!" + expressiontype)); } } private static string ResolveFunc(Expression left, Expression right, ExpressionType expressiontype) { var Name = (left as MemberExpression).Member.Name; var Value = (right as ConstantExpression).Value; var Operator = GetOperator(expressiontype); return Name + Operator + Value ?? "null"; } private static string ResolveLinqToObject(Expression expression, object value, ExpressionType? expressiontype = null) { var MethodCall = expression as MethodCallExpression; var MethodName = MethodCall.Method.Name; switch (MethodName) { case "Contains": if (MethodCall.Object != null) return Like(MethodCall); return In(MethodCall, value); case "Count": return Len(MethodCall, value, expressiontype.Value); case "LongCount": return Len(MethodCall, value, expressiontype.Value); default: throw new Exception(string.Format("不支持{0}方法的查找!", MethodName)); } } private static string In(MethodCallExpression expression, object isTrue) { var Argument1 = (expression.Arguments[0] as MemberExpression).Expression as ConstantExpression; var Argument2 = expression.Arguments[1] as MemberExpression; var Field_Array = Argument1.Value.GetType().GetFields().First(); object[] Array = Field_Array.GetValue(Argument1.Value) as object[]; List<string> SetInPara = new List<string>(); for (int i = 0; i < Array.Length; i++) { string Name_para = "InParameter" + i; string Value = Array[i].ToString(); SetInPara.Add(Value); } string Name = Argument2.Member.Name; string Operator = Convert.ToBoolean(isTrue) ? "in" : " not in"; string CompName = string.Join(",", SetInPara); string Result = string.Format("{0} {1} ({2})", Name, Operator, CompName); return Result; } private static string Like(MethodCallExpression expression) { var Temp = expression.Arguments[0]; LambdaExpression lambda = Expression.Lambda(Temp); Delegate fn = lambda.Compile(); var tempValue = Expression.Constant(fn.DynamicInvoke(null), Temp.Type); string Value = string.Format("%{0}%", tempValue); string Name = (expression.Object as MemberExpression).Member.Name; string Result = string.Format("{0} like {1}", Name, Value); return Result; } private static string Len(MethodCallExpression expression, object value, ExpressionType expressiontype) { object Name = (expression.Arguments[0] as MemberExpression).Member.Name; string Operator = GetOperator(expressiontype); string Result = string.Format("len({0}){1}{2}", Name, Operator, value.ToString()); return Result; } }
六、注入緩存
/// <summary> /// 緩存服務啟動 /// </summary> public static class MemoryCacheSetup { public static void AddMemoryCacheSetup(this IServiceCollection services) { if (services == null) throw new ArgumentNullException(nameof(services)); services.AddScoped<ICaching, MemoryCaching>(); services.AddSingleton<IMemoryCache>(factory => { var cache = new MemoryCache(new MemoryCacheOptions()); return cache; }); } }
public class Startup { // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) {//注入緩存 services.AddMemoryCacheSetup(); } }
下面兩步驟需要根據自己服務層修改:
七、注冊AOP
public class Startup { public void ConfigureContainer(ContainerBuilder builder) { //other ...// AOP var cacheType = new List<Type>(); builder.RegisterType<BlogCacheAOP>(); cacheType.Add(typeof(BlogCacheAOP)); // 獲取 Service.dll 程序集服務,並注冊 var assemblysServices = Assembly.LoadFrom(servicesDllFile); builder.RegisterAssemblyTypes(assemblysServices) .AsImplementedInterfaces() .InstancePerDependency() .EnableInterfaceInterceptors()//引用Autofac.Extras.DynamicProxy; .InterceptedBy(cacheType.ToArray());//允許將攔截器服務的列表分配給注冊。 //other ... } }
八、使用
服務層在需要緩存的方法上添加屬性,就OK了
public class BlogArticleServices : BaseServices<BlogArticle>, IBlogArticleServices { IBlogArticleRepository _dal; public BlogArticleServices(IBlogArticleRepository dal) { this._dal = dal; base.BaseDal = dal; } /// <summary> /// 獲取博客列表 /// </summary> /// <returns></returns> [CachingAttribute(AbsoluteExpiration = 10)] //使用緩存AOP 緩存10分鍾 public async Task<List<BlogArticle>> getBlogs() { var blogList = await _dal.Query(a => a.bID > 0, a => a.bID); return blogList; } }
九、運行代碼
第一次進入沒有緩存,走 set 緩存10分鍾
第二次進入,有緩存得到緩存直接返回
大功告成,