
public interface IBlogArticleServices :IBaseServices<BlogArticle> { Task<List<BlogArticle>> getBlogs(); } public class BlogArticleServices : BaseServices<BlogArticle>, IBlogArticleServices { IBlogArticleRepository dal; public BlogArticleServices(IBlogArticleRepository dal) { this.dal = dal; base.baseDal = dal; } /// <summary> /// 获取博客列表 /// </summary> /// <param name="id"></param> /// <returns></returns> public async Task<List<BlogArticle>> getBlogs() { var bloglist = await dal.Query(a => a.bID > 0, a => a.bID); return bloglist; } }
2、在API层中添加对该接口引用(注意RESTful接口路径命名规范,我这么写只是为了测试)
/// <summary> /// 获取博客列表 /// </summary> /// <returns></returns> [HttpGet] [Route("GetBlogs")] public async Task<List<BlogArticle>> GetBlogs() { return await blogArticleServices.getBlogs(); }
3、在Blog.Core新建文件夹AOP,并添加拦截器BlogLogAOP,并设计其中用到的日志记录Logger方法或者类
关键的一些知识点,注释中已经说明了,主要是有以下:
1、继承接口IInterceptor
2、实例化接口IINterceptor的唯一方法Intercept
3、void Proceed();表示执行当前的方法和object ReturnValue { get; set; }执行后调用,object[] Arguments参数对象
4、中间的代码是新建一个类,还是单写,就很随意了。
/// <summary> /// 拦截器BlogLogAOP 继承IInterceptor接口 /// </summary> public class BlogLogAOP : IInterceptor { /// <summary> /// 实例化IInterceptor唯一方法 /// </summary> /// <param name="invocation">包含被拦截方法的信息</param> public void Intercept(IInvocation invocation) { //记录被拦截方法信息的日志信息 var dataIntercept = $"{DateTime.Now.ToString("yyyyMMddHHmmss")} " + $"当前执行方法:{ invocation.Method.Name} " + $"参数是: {string.Join(", ", invocation.Arguments.Select(a => (a ?? "").ToString()).ToArray())} \r\n"; //在被拦截的方法执行完毕后 继续执行当前方法 invocation.Proceed(); dataIntercept += ($"被拦截方法执行完毕,返回结果:{invocation.ReturnValue}"); #region 输出到当前项目日志 var path = Directory.GetCurrentDirectory() + @"\Log"; if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } string fileName = path + $@"\InterceptLog-{DateTime.Now.ToString("yyyyMMddHHmmss")}.log"; StreamWriter sw = File.AppendText(fileName); sw.WriteLine(dataIntercept); sw.Close(); #endregion } }4、添加到Autofac容器中,实现注入 还记得昨天的容器么,先把拦截器注入,然后对程序集的注入方法中添加拦截器服务即可
builder.RegisterType<BlogLogAOP>();//可以直接替换其他拦截器!一定要把拦截器进行注册 var assemblysServices = Assembly.Load("Blog.Core.Services"); //builder.RegisterAssemblyTypes(assemblysServices).AsImplementedInterfaces();//指定已扫描程序集中的类型注册为提供所有其实现的接口。 builder.RegisterAssemblyTypes(assemblysServices) .AsImplementedInterfaces() .InstancePerLifetimeScope() .EnableInterfaceInterceptors()//引用Autofac.Extras.DynamicProxy; .InterceptedBy(typeof(BlogLogAOP));//可以直接替换拦截器
注意其中的俩个方法 .EnableInterfaceInterceptors()//对目标类型启用接口拦截。拦截器将被确定,通过在类或接口上截取属性, 或添加 InterceptedBy () .InterceptedBy(typeof(BlogLogAOP));//允许将拦截器服务的列表分配给注册。 说人话就是,将拦截器加上要注入容器的的接口或者类上5、运行项目,嗯,你就看到这根目录下生成了一个Log文件夹,里边有日志记录,当然记录很简陋,里边是获取到的实体类,大家可以自己根据需要扩展
这里,面向服务层的日志记录就完成了,大家感觉是不是很平时的不一样?
/// <summary> /// 简单的缓存接口,只有查询和添加,以后会进行扩展 /// </summary> public interface ICaching { object Get(string cacheKey); void Set(string cacheKey, object cacheValue); } /// <summary> /// 实例化缓存接口ICaching /// </summary> public class MemoryCaching : ICaching { //引用Microsoft.Extensions.Caching.Memory;这个和.net 还是不一样,没有了Httpruntime了 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) { _cache.Set(cacheKey, cacheValue, TimeSpan.FromSeconds(7200)); } }
2、定义一个拦截器还是继承IInterceptor,并实现Intercept
/// <summary> /// 面向切面的缓存使用 /// </summary> public class BlogCacheAOP : IInterceptor { //通过注入的方式,把缓存操作接口通过构造函数注入 private ICaching _cache; public BlogCacheAOP(ICaching cache) { _cache = cache; } //Intercept方法是拦截的关键所在,也是IInterceptor接口中的唯一定义 public void Intercept(IInvocation invocation) { //获取自定义缓存键 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); } } //自定义缓存键 private 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 += $"{param}:"; } return key.TrimEnd(‘:‘); } //object 转 string private string GetArgumentValue(object arg) { if (arg is int || arg is long || arg is string) return arg.ToString(); if (arg is DateTime) return ((DateTime)arg).ToString("yyyyMMddHHmmss"); return ""; } }
注释的很清楚,基本都是情况
3、ConfigureServices不用动,只需要改下拦截器的名字就行注意://将 TService 中指定的类型的范围服务添加到实现
services.AddScoped<ICaching, MemoryCaching>();//记得把缓存注入!!!
4、运行,你会发现,首次缓存是空的,然后将Repository仓储中取出来的数据存入缓存,第二次使用就是有值了,其他所有的地方使用,都不用再写了,而且也是面向整个程序集合的

配合Attribute就可以只拦截相应的方法了。因为拦截器里面是根据Attribute进行相应判断的!! builder.RegisterAssemblyTypes(assembly) .Where(type => typeof(IQCaching).IsAssignableFrom(type) && !type.GetTypeInfo().IsAbstract) .AsImplementedInterfaces() .InstancePerLifetimeScope() .EnableInterfaceInterceptors() .InterceptedBy(typeof(QCachingInterceptor));2、时间问题,阻塞,浪费资源问题等 定义切面有时候是方便,初次使用会很别扭,使用多了,可能会对性能有些许的影响,因为会大量动态生成代理类,性能损耗,是特别高的请求并发,比如万级每秒,还是不建议生产环节推荐。所以说切面编程要深入的研究,不可随意使用,我说的也是九牛一毛,大家继续加油吧! 3、静态注入
基于Net的IL语言层级进行注入,性能损耗可以忽略不计,Net使用最多的Aop框架PostSharp(好像收费了;)采用的即是这种方式。
大家可以参考这个博文:https://www.cnblogs.com/mushroom/p/3932698.html
今天的讲解就到了这里了,通过这两个小栗子,大家应该能对面向切面编程有一些朦胧的感觉了吧,感兴趣的可以深入的研究,也欢迎一起讨论,刚刚在缓存中,我说到了缓存接口,就引入了下次的讲解内容,Redis的高性能缓存框架,内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。下次再见咯~