Z从壹开始前后端分离【 .NET Core2.0/3.0 +Vue2.0 】框架之十 || AOP面向切面编程浅解析:简单日志记录 + 服务切面缓存
本文梯子
- 本文3.0版本文章
- 代码已上传Github+Gitee,文末有地址
- 大神反馈:
- 零、今天完成的深红色部分
- 一、AOP 之 实现日志记录(服务层)
- 1、定义服务接口与实现类
- 2、在API层中添加对该接口引用
- 3、添加AOP拦截器
- 4、添加到Autofac容器中,实现注入
- 5、运行项目,查看效果
- 二、AOP 之 实现接口数据的缓存功能
- 1、定义 Memory 缓存类和接口
- 2、定义一个缓存拦截器
- 3、注入缓存拦截器
- 4、运行,查看效果
- 5、多个AOP执行顺序问题
- 6、无接口如何实现AOP
- 如果没有接口
- 如果是没有接口的单独实体类
- 三、还有其他的一些问题需要考虑
- 四、结语
- 1、网友好资料
- 五、Github && Gitee
正文
本文3.0版本文章
代码已上传Github+Gitee,文末有地址
大神反馈:
1、群里小伙伴 大龄Giser 根据本文,成功的应用在工作中,点赞,欢迎围观:【ABP】面向切面编程(AOP)知识总结
零、今天完成的深红色部分
一、AOP 之 实现日志记录(服务层)
1、定义服务接口与实现类
首先这里使用到了 BlogArticle 的实体类(这里我保留了sqlsugar的特性,没需要的可以手动删除):

public class BlogArticle { /// <summary> /// 主键 /// </summary> /// 这里之所以没用RootEntity,是想保持和之前的数据库一致,主键是bID,不是Id [SugarColumn(IsNullable = false, IsPrimaryKey = true, IsIdentity = true)] public int bID { get; set; } /// <summary> /// 创建人 /// </summary> [SugarColumn(Length = 60, IsNullable = true)] public string bsubmitter { get; set; } /// <summary> /// 标题blog /// </summary> [SugarColumn(Length = 256, IsNullable = true)] public string btitle { get; set; } /// <summary> /// 类别 /// </summary> [SugarColumn(Length = int.MaxValue, IsNullable = true)] public string bcategory { get; set; } /// <summary> /// 内容 /// </summary> [SugarColumn(IsNullable = true, ColumnDataType = "text")] public string bcontent { get; set; } /// <summary> /// 访问量 /// </summary> public int btraffic { get; set; } /// <summary> /// 评论数量 /// </summary> public int bcommentNum { get; set; } /// <summary> /// 修改时间 /// </summary> public DateTime bUpdateTime { get; set; } /// <summary> /// 创建时间 /// </summary> public System.DateTime bCreateTime { get; set; } /// <summary> /// 备注 /// </summary> [SugarColumn(Length = int.MaxValue, IsNullable = true)] public string bRemark { get; set; } /// <summary> /// 逻辑删除 /// </summary> [SugarColumn(IsNullable = true)] public bool? IsDeleted { get; set; } }


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、添加AOP拦截器
在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 } }

提示:这里展示了如何在项目中使用AOP实现对 service 层进行日志记录,如果你想实现异常信息记录的话,很简单,
注意,这个方法仅仅是针对同步的策略,如果你的service是异步的,这里获取不到,正确的写法,在文章底部的 GitHub 代码里,因为和 AOP 思想没有直接的关系,这里就不赘述。


/// <summary> /// 实例化IInterceptor唯一方法 /// </summary> /// <param name="invocation">包含被拦截方法的信息</param> public void Intercept(IInvocation invocation) { //记录被拦截方法信息的日志信息 var dataIntercept = "" + $"【当前执行方法】:{ invocation.Method.Name} \r\n" + $"【携带的参数有】: {string.Join(", ", invocation.Arguments.Select(a => (a ?? "").ToString()).ToArray())} \r\n"; try { MiniProfiler.Current.Step($"执行Service方法:{invocation.Method.Name}() -> "); //在被拦截的方法执行完毕后 继续执行当前方法,注意是被拦截的是异步的 invocation.Proceed(); // 异步获取异常,先执行 if (IsAsyncMethod(invocation.Method)) { //Wait task execution and modify return value if (invocation.Method.ReturnType == typeof(Task)) { invocation.ReturnValue = InternalAsyncHelper.AwaitTaskWithPostActionAndFinally( (Task)invocation.ReturnValue, async () => await TestActionAsync(invocation), ex => { LogEx(ex, ref dataIntercept); }); } else //Task<TResult> { invocation.ReturnValue = InternalAsyncHelper.CallAwaitTaskWithPostActionAndFinallyAndGetResult( invocation.Method.ReturnType.GenericTypeArguments[0], invocation.ReturnValue, async () => await TestActionAsync(invocation), ex => { LogEx(ex, ref dataIntercept); }); } } else {// 同步1 } } catch (Exception ex)// 同步2 { LogEx(ex, ref dataIntercept); } dataIntercept += ($"【执行完成结果】:{invocation.ReturnValue}"); Parallel.For(0, 1, e => { LogLock.OutSql2Log("AOPLog", new string[] { dataIntercept }); }); _hubContext.Clients.All.SendAsync("ReceiveUpdate", LogLock.GetLogData()).Wait(); }

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、运行项目,查看效果

这里,面向服务层的日志记录就完成了,大家感觉是不是很平时的不一样?
二、AOP 之 实现接口数据的缓存功能
1、定义 Memory 缓存类和接口

/// <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、定义一个缓存拦截器

/// <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、注入缓存拦截器
注意:
//将 TService 中指定的类型的范围服务添加到实现
services.AddScoped<ICaching, MemoryCaching>();//记得把缓存注入!!!

4、运行,查看效果

5、多个AOP执行顺序问题
在我最新的 Github 项目中,我定义了三个 AOP :除了上边两个 LogAOP和 CacheAOP 以外,还有一个 RedisCacheAOP,并且通过开关的形式在项目中配置是否启用:
那具体的执行顺序是什么呢,这里说下,就是从上至下的顺序,或者可以理解成挖金矿的形式,执行完上层的,然后紧接着来下一个AOP,最后想要回家,就再一个一个跳出去,在往上层走的时候,矿肯定就执行完了,就不用再操作了,直接出去,就像 break 一样,可以参考这个动图:
6、无接口如何实现AOP
上边我们讨论了很多,但是都是接口框架的,
比如:Service.dll 和与之对应的 IService.dll,Repository.dll和与之对应的 IRepository.dll,我们可以直接在对应的层注入的时候,匹配上 AOP 信息,但是如果我们没有使用接口怎么办?
这里大家可以安装下边的实验下:
Autofac它只对接口方法 或者 虚virtual方法或者重写方法override才能起拦截作用。
如果没有接口
案例是这样的:
如果我们的项目是这样的,没有接口,会怎么办:

// 服务层类 public class StudentService { StudentRepository _studentRepository; public StudentService(StudentRepository studentRepository) { _studentRepository = studentRepository; } public string Hello() { return _studentRepository.Hello(); } } // 仓储层类 public class StudentRepository { public StudentRepository() { } public string Hello() { return "hello world!!!"; } } // controller 接口调用 StudentService _studentService; public ValuesController(StudentService studentService) { _studentService = studentService; }

如果是没有接口的单独实体类

public class Love { // 一定要是虚方法 public virtual string SayLoveU() { return "I ♥ U"; } } //--------------------------- //只能注入该类中的虚方法 builder.RegisterAssemblyTypes(Assembly.GetAssembly(typeof(Love))) .EnableClassInterceptors() .InterceptedBy(typeof(BlogLogAOP));

三、还有其他的一些问题需要考虑
配合Attribute就可以只拦截相应的方法了。因为拦截器里面是根据Attribute进行相应判断的!!builder.RegisterAssemblyTypes(assembly).Where(type => typeof(IQCaching).IsAssignableFrom(type) && !type.GetTypeInfo().IsAbstract) .AsImplementedInterfaces().InstancePerLifetimeScope().EnableInterfaceInterceptors().InterceptedBy(typeof(QCachingInterceptor));
基于Net的IL语言层级进行注入,性能损耗可以忽略不计,Net使用最多的Aop框架PostSharp(好像收费了;)采用的即是这种方式。
大家可以参考这个博文:https://www.cnblogs.com/mushroom/p/3932698.html
四、结语
今天的讲解就到了这里了,通过这两个小栗子,大家应该能对面向切面编程有一些朦胧的感觉了吧,感兴趣的可以深入的研究,也欢迎一起讨论,刚刚在缓存中,我说到了缓存接口,就引入了下次的讲解内容,Redis的高性能缓存框架,内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。下次再见咯~
1、网友好资料
五、Github && Gitee
Z从壹开始前后端分离【 .NET Core2.0/3.0 +Vue2.0 】框架之十 || AOP面向切面编程浅解析:简单日志记录 + 服务切面缓存的更多相关文章
- Z从壹开始前后端分离【 .NET Core2.2/3.0 +Vue2.0 】框架之七 || API项目整体搭建 6.2 轻量级ORM
本文梯子 本文3.0版本文章 前言 零.今天完成的蓝色部分 0.创建实体模型与数据库 1.实体模型 2.创建数据库 一.在 IRepository 层设计接口 二.在 Repository 层实现相应 ...
- 从壹开始前后端分离【 .NET Core2.0 +Vue2.0 】框架之十 || AOP面向切面编程浅解析:简单日志记录 + 服务切面缓存
代码已上传Github+Gitee,文末有地址 上回<从壹开始前后端分离[ .NET Core2.0 Api + Vue 2.0 + AOP + 分布式]框架之九 || 依赖注入IoC学习 + ...
- Z从壹开始前后端分离【 .NET Core2.2/3.0 +Vue2.0 】框架之六 || API项目整体搭建 6.1 仓储+服务+抽象接口模式
本文梯子 本文3.0版本文章 前言 零.完成图中的粉色部分 2019-08-30:关于仓储的相关话题 一.创建实体Model数据层 二.设计仓储接口与其实现类 三.设计服务接口与其实现类 四.创建 C ...
- Z从壹开始前后端分离【 .NET Core2.0/3.0 +Vue2.0 】框架之四 || Swagger的使用 3.2
本文梯子 本文3.0版本文章 前言 一.swagger的一般用法 0.设置swagger页面为首页——开发环境 1.设置默认直接首页访问 —— 生产环境 2.为接口添加注释 3.对 Model 也添加 ...
- Z从壹开始前后端分离【 .NET Core2.0/3.0 +Vue2.0 】框架之三 || Swagger的使用 3.1
本文梯子 本文3.0版本文章 常见问题 1.Bug调试 2.经常有小伙伴遇到这个错误 3.路由重载 一.为什么使用Swagger 二.配置Swagger服务 1.引用Nuget包 2.配置服务 3.启 ...
- Z从壹开始前后端分离【 .NET Core2.0/3.0 +Vue2.0 】框架之二 || 后端项目搭建
本文梯子 前言 1..net core 框架性能测试 2..net core 执行过程 3.中间件执行过程 4.AOP切面 5.整体框架结构与数据库表UML 一.创建第一个Core 1.SDK 安装 ...
- Z从壹开始前后端分离【 .NET Core2.2/3.0 +Vue2.0 】框架之九 || 依赖注入IoC学习 + AOP界面编程初探
本文梯子 本文3.0版本文章 更新 代码已上传Github+Gitee,文末有地址 零.今天完成的绿色部分 一.依赖注入的理解和思考 二.常见的IoC框架有哪些 1.Autofac+原生 2.三种注入 ...
- Z从壹开始前后端分离【 .NET Core2.2/3.0 +Vue2.0 】框架之五 || Swagger的使用 3.3 JWT权限验证【必看】
本文梯子 本文3.0版本文章 前言 1.如何给接口实现权限验证? 零.生成 Token 令牌 一.JWT ——自定义中间件 0.Swagger中开启JWT服务 1:API接口授权策略 2.自定义认证之 ...
- Z从壹开始前后端分离【 .NET Core2.2/3.0 +Vue2.0 】框架之八 || API项目整体搭建 6.3 异步泛型仓储+依赖注入初探
本文梯子 本文3.0版本文章 回顾 1.Sqlsugar 的使用 2.修改数据连接字符串 今天要完成的浅紫色部分 一.设计仓储基类接口——IBaseRepository.cs 二.将其他的仓储接口,继 ...
随机推荐
- 如何获得大学教材的PDF版本?
最近急需一本算法书的配套答案,这本配套单独出售,好像在市面上还买不到,在淘宝上搜索也只是上一个版本,并没有最新版本,让我很无奈.加上平时肯定会有这么一种情况,想看一些书,但买回来也看不了几次,加上计算 ...
- Selenium(十二):操作Cookie、调用JavaScript、HTML5的视频播放
1. 操作Cookie 有时候我们想要验证浏览器中cookie是否正确,因为基于真实cookie的测试是无法通过白盒和集成测试的.WebDriver提供了操作Cookie的相关方法,可以读取.添加和删 ...
- linux指令大全(归类整理)
一.文件目录指令 1 pwd指令 pwd 显示当前所在的目录 2 ls指令 ls [选项] [目录或文件] 查看文件信息 ls -a 查看所有文件和目录,包括隐藏的 ls -l 以列表的方式显示 ll ...
- windows 下安装beego
好久没写博客了,最近忙于一些杂事,看见有几个博友留言了,未能及时回复,稍后晚点回复诸位博友.不多说了,windows安装beego(请先确保git环境已安装并设置了git环境变量.这个简单网上很多教程 ...
- Winform中自定义添加ZedGraph右键实现设置所有Y轴刻度的上下限
场景 Winforn中实现ZedGraph自定义添加右键菜单项(附源码下载): https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/10 ...
- ArcGIS api for JavaScript 3.27 在线浏览的一些小部件
var navOption; var navToolbar;// 当前选择的操作 require( [ "esri/toolbars/navigation", "esri ...
- Swift实战技巧
Swift实战技巧 从OC转战到Swift,差别还是蛮大的,本文记录了我再从OC转到Swift开发过程中遇到的一些问题,然后把我遇到的这些问题记录形成文章,大体上是一些Swift语言下面的一些技巧,希 ...
- 团队项目之Scrum2
小组:BLACK PANDA 时间:2019.11.17 每天举行站立式会议 提供当天站立式会议照片一张 2 昨天已完成的工作 2 确定用户登录与注册和编辑页面的接口 前端方面:详细确定页面的功能,并 ...
- 国内Maven仓库--阿里云Aliyun仓库地址及设置
aliyun Maven:http://maven.aliyun.com/nexus/#view-repositories 需要使用的话,要在maven的settings.xml 文 ...
- 失败的一天(找bug)
前天早上来到实验室,准备抓紧时间写写代码,毕竟第二天就是组会了.点了一下鼠标,发现显示屏无法唤醒,然后就准备强制关机再开机(我一般不关机,以前遇到过几次无法唤醒),低头发现主机不亮,然后按了开关也不亮 ...