项目搭建了一个基础的框架,实现缓存的AOP拦截,初次查询从数据库获取,再写入缓存,设置过期时间,再次查询数据时从缓存获取。
话不多说我们来上代码实现:
1.界说缓存的接口和实现类
界说缓存接口ICachingProvider和实现类MemoryCaching:- /// <summary>
- /// 简单的缓存接口,只有查询和添加,以后会进行扩展
- /// </summary>
- public interface ICachingProvider
- {
- object Get(string cacheKey);
- void Set(string cacheKey, object cacheValue, int timeSpan);
- }
- /// <summary>
- /// 实例化缓存接口ICaching
- /// </summary>
- public class MemoryCaching : ICachingProvider
- {
- //引用Microsoft.Extensions.Caching.Memory;这个和.net 还是不一样,没有了Httpruntime了
- private readonly IMemoryCache _cache;
- //还是通过构造函数的方法,获取
- public MemoryCaching(IMemoryCache cache)
- {
- _cache = cache;
- }
- public object Get(string cacheKey)
- {
- return _cache.Get(cacheKey);
- }
- public void Set(string cacheKey, object cacheValue, int timeSpan)
- {
- _cache.Set(cacheKey, cacheValue, TimeSpan.FromSeconds(timeSpan * 60));
- }
- }
复制代码 2.界说缓存的特性CachingAttribute:
- /// <summary>
- /// 这个Attribute就是使用时候的验证,把它添加到要缓存数据的方法中,即可完成缓存的操作。
- /// </summary>
- [AttributeUsage(AttributeTargets.Method, Inherited = true)]
- public class CachingAttribute : System.Attribute
- {
- /// <summary>
- /// 缓存绝对过期时间(分钟)
- /// </summary>
- public int AbsoluteExpiration { get; set; } = 30;
- }
复制代码 3.界说MemoryCacheSetup 启动服务注册
- /// <summary>
- /// Memory缓存 启动服务
- /// </summary>
- public static class MemoryCacheSetup
- {
- public static void AddMemoryCacheSetup(this IServiceCollection services)
- {
- if (services == null) throw new ArgumentNullException(nameof(services));
- services.AddScoped<ICachingProvider, MemoryCaching>();
- services.AddSingleton<IMemoryCache>(factory =>
- {
- var cache = new MemoryCache(new MemoryCacheOptions());
- return cache;
- });
- }
- }
复制代码 4.CacheAopBase缓存aop基础类,面向切换的内存缓存使用
界说缓存aop基类:- public abstract class CacheAopBase : IInterceptor
- {
- /// <summary>
- /// AOP的拦截方法
- /// </summary>
- /// <param name="invocation"></param>
- public abstract void Intercept(IInvocation invocation);
- /// <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)
- return ((DateTime)arg).ToString("yyyyMMddHHmmss");
- if (arg is string || arg is ValueType)
- return arg.ToString();
- if (arg != null)
- {
- if (arg is Expression)
- {
- var obj = arg as Expression;
- var result = Resolve(obj);
- return CommonHelper.Md5For16(result);
- }
- else if (arg.GetType().IsClass)
- {
- return CommonHelper.Md5For16(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;
- }
- }
复制代码 缓存AOP的实现:- /// <summary>
- /// 面向切换的内存缓存使用
- /// </summary>
- public class MemoryCacheAop : CacheAopBase
- {
- //通过注入的方式,把缓存操作接口通过构造函数注入
- private readonly ICachingProvider _cache;
- public MemoryCacheAop(ICachingProvider cache)
- {
- _cache = cache;
- }
- //Intercept方法是拦截的关键所在,也是IInterceptor接口中的唯一定义
- public override void Intercept(IInvocation invocation)
- {
- var method = invocation.MethodInvocationTarget ?? invocation.Method;
- //对当前方法的特性验证
- //如果需要验证
- //var CachingAttribute = method.GetCustomAttributes(true).FirstOrDefault(x => x.GetType() == typeof(CachingAttribute));
- var CachingAttribute = method.GetCustomAttribute<CachingAttribute>(true);
- Console.WriteLine(method.Name);
- Console.WriteLine(method.DeclaringType.FullName);
- if (CachingAttribute is CachingAttribute qCachingAttribute)
- {
- //获取自定义缓存键
- 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, qCachingAttribute.AbsoluteExpiration);
- }
- }
- else
- {
- invocation.Proceed();//直接执行被拦截方法
- }
- }
- }
复制代码 5.界说AutofacModuleRegister 模块注册类实现模块间的解耦,在这个类中注册所有的接口和服务等:在这里实现MemoryCacheAop的拦截开启
- public class AutofacModuleRegister : Autofac.Module
- {
- protected override void Load(ContainerBuilder builder)
- {
- var basePath = AppContext.BaseDirectory;
- #region 带有接口层的服务注入
- var servicesDllFile = Path.Combine(basePath, "Net.Service.dll");
- var repositoryDllFile = Path.Combine(basePath, "Net.Repository.dll");
- if (!(File.Exists(servicesDllFile) && File.Exists(repositoryDllFile)))
- {
- var msg = "Repository.dll和Service.dll 丢失,因为项目解耦了,所以需要先F6编译,再F5运行,请检查 bin 文件夹,并拷贝。";
- throw new Exception(msg);
- }
- // AOP 开关,如果想要打开指定的功能,只需要在 appsettigns.json 对应的配置项设置为 true 就行。
- var cacheType = new List<Type>();
- if (AppSettingsConstVars.RedisUseCache)
- {
- //builder.RegisterType<RedisCacheAop>();
- //cacheType.Add(typeof(RedisCacheAop));
- }
- else
- {
- builder.RegisterType<MemoryCacheAop>();
- cacheType.Add(typeof(MemoryCacheAop));
- }
- //// 获取 Service.dll 程序集服务,并注册
- //var assemblysServices = Assembly.LoadFrom(servicesDllFile);
- ////支持属性注入依赖重复
- //builder.RegisterAssemblyTypes(assemblysServices).AsImplementedInterfaces().InstancePerDependency()
- // .PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies);
- //// 获取 Repository.dll 程序集服务,并注册
- //var assemblysRepository = Assembly.LoadFrom(repositoryDllFile);
- ////支持属性注入依赖重复
- //builder.RegisterAssemblyTypes(assemblysRepository).AsImplementedInterfaces().InstancePerDependency()
- // .PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies);
- // 获取 Service.dll 程序集服务,并注册
- var assemblysServices = Assembly.LoadFrom(servicesDllFile);
- builder.RegisterAssemblyTypes(assemblysServices)
- .AsImplementedInterfaces()
- .InstancePerDependency()
- .PropertiesAutowired()
- .EnableInterfaceInterceptors()//引用Autofac.Extras.DynamicProxy;
- .InterceptedBy(cacheType.ToArray());//允许将拦截器服务的列表分配给注册。
- // 获取 Repository.dll 程序集服务,并注册
- var assemblysRepository = Assembly.LoadFrom(repositoryDllFile);
- builder.RegisterAssemblyTypes(assemblysRepository)
- .AsImplementedInterfaces()
- .PropertiesAutowired()
- .InstancePerDependency();
- #endregion
- }
- }
复制代码 在program里注册缓存

6.设置数据库链接
在appsettings.json设置文件上设置数据库链接,以及redis缓存连接,本文档暂时先不讲解redis缓存(等待下一篇)
如果设置UseCache=true,那么就开启redis缓存,设置UseCache=false则开启memoryCache缓存- //redis为必须启动项,请保持redis为正常可用
- "RedisConfig": {
- "UseCache": false, //启用redis作为内存选择
- // 如果采用容器化部署Service 要写成redis的服务名,否则写地址
- "ConnectionString": "127.0.0.1:6379,password=CoreShop,connectTimeout=3000,connectRetry=1,syncTimeout=10000,DefaultDatabase=10" //redis数据库连接字符串
- },
复制代码 这里我设置了环境变量,包管我的数据库真实连接不袒露,你也可以改为自己的数据库连接:
设置完后需要先关闭vs,然后再打开才能生效
7.怎样使用缓存拦截
起首在服务的基类实现中添加Caching缓存特性;
在BaseServices里添加缓存特性,并设置过期时间为:30分钟:Caching(AbsoluteExpiration = 3)
其次在GetUserInfo方法调用QueryByIdAsync方法查询id=1的用户
再次调用GetUserCache方法,获取缓存中的id=1的用户数据,可以查看输出结果:
8.启动项目,分别调用方法:GetUserInfo和GetUserCache
调用GetUserInfo查询数据,写入缓存成功
再次调用GetUserCache,一样的缓存key和返回一样的用户信息:
通过以上的讲解:你是否已司理解了MemoryCache的使用呢?
欢迎点赞,关注,批评!
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
|