肉夹馍(https://github.com/inversionhourglass/Rougamo)通过静态代码织入方式实现AOP的组件,其主要特点是在编译时完成AOP代码织入,相比动态代理可以减少应用启动的初始化时间让服务更快可用,同时还能对静态方法进行AOP。

1.0.0 版本中,肉夹馍提供了最基础的AOP功能,可以进行日志记录和APM埋点。在 1.1.0 版本中新增了对更加实用的AOP操作的支持,可以进行异常处理和修改返回值。本次的 1.2.0 版本没有新增功能,主要是对 1.1.0 版本的增强,新增了一个ExMoAttribute,这个Attribute可能会替代MoAttribute成为大家更常用的Attribute.

前言

这次无法直接从快速开始入手了,了解一下前因后果会让你对肉夹馍最初的设定和ExMoAttribute的出现所解决的问题有更清晰的认识,这样也方便后续使用时能够明确的知道是应该使用MoAttribute还是ExMoAttribute.

在1.1.0版本发布后陆续收到两个issue,都是反馈在没有使用async语法的Task/ValueTask返回值的方法无法正确的在方法执行成功(OnSuccess)和方法退出前(OnExit)执行织入的代码,比如记录方法的执行耗时的示例:

static async Task Main(string[] args)
{
await Test();
} [Timeline]
static Task Test()
{
Console.WriteLine($"{nameof(Test)} start");
return Task.Run(() =>
{
Thread.Sleep(1000);
Console.WriteLine($"{nameof(Test)} end");
});
} class TimelineAttribute : MoAttribute
{
private Stopwatch _stopwatch; public override void OnEntry(MethodContext context)
{
_stopwatch = Stopwatch.StartNew();
Console.WriteLine($"{context.Method.Name} {nameof(OnEntry)}");
} public override void OnExit(MethodContext context)
{
_stopwatch.Stop();
Console.WriteLine($"{context.Method.Name} {nameof(OnExit)} - {_stopwatch.ElapsedMilliseconds}ms");
}
}

期望的输出可能是下面这样,等Test方法返回值Task执行完成之后再执行OnExit,统计耗时到返回的Task执行完毕之后:

Test OnEntry
Test start
Test end
Test OnExit - 1096ms

而实际的输出是下面这样的,方法的耗时仅为Task对象创建后返回的执行耗时,并没有等待Task执行:

Test OnEntry
Test start
Test OnExit - 96ms
Test end

这种表现其实是最开始设计时的设定。在我们刚接触async/await语法时,我们或许有听到这样的介绍“async/await让我们像写同步方法那样去写异步方法”。是的,有了async/await,我们就不用像以前EAP/APM那样去编写callback了,代码整体看起来和同步代码无异,同时我们也会注意到一点,异步方法的返回值类型是Task/ValueTask,但是我们实际return的对象类型却是其泛型参数类型(Task/ValueTask中的那个T),肉夹馍采用了这一设定。所以对于返回值时Task/ValueTask的方法,如果使用了async/await语法,那么你通过MethodContext.RealReturnType获取到的返回值类型就是其泛型参数类型(没有泛型参数时就是void),同时通过MethodContext.HandledExceptionMethodContext.ReplaceReturnValue设置/修改返回值时,返回值的类型也是Task/ValueTask的泛型参数类型。而对于没有使用async/await语法的方法,那么返回值类型就是Task/ValueTask本身。也可以简单的理解为MoAttribute里采用的方法返回值类型与你编写代码时return的对象类型相同。也因为这样的设定,在上面的示例中由于没有使用async/await语法,其实际的返回值类型就是Task,并不会去等待Task执行完毕,所以有了上面那段代码的执行效果。

如果希望上面那段代码达到预期的效果,有没有什么方案呢?答案是:有的

// 仅对TimelineAttribute的OnExit方法进行改造
class TimelineAttribute : MoAttribute
{
// ... public override void OnExit(MethodContext context)
{
if (typeof(Task).IsAssignableFrom(context.RealReturnType))
{
((Task)context.ReturnValue).ContinueWith(t => _OnExit());
}
else
{
_OnExit();
} void _OnExit()
{
_stopwatch.Stop();
Console.WriteLine($"{context.Method.Name} {nameof(OnExit)} - {_stopwatch.ElapsedMilliseconds}ms");
}
}
}

上面的方案通过自行判断返回值类型,对继承自Task的返回值通过显式转换后调用ContinueWith达到需要的效果。这个思路是通用的,但对有些需求实现起来就比较麻烦并且需要对肉夹馍的执行逻辑有一定的了解,比如异常处理,上面的例子中如果在Task.Run之前抛出异常,你需要在OnException中进行异常处理,而如果是在Task.Run里的Action中抛出异常,那就需要到OnSuccess中通过ContinueWith判断Task是否执行异常:

[Timeline]
static Task Test()
{
Console.WriteLine($"{nameof(Test)} start");
// throw new Exception(); // 这里抛出异常在OnException中处理
return Task.Run(() =>
{
// throw new Exception(); // 这里抛出异常在OnSuccess中通过ContinueWith处理
Thread.Sleep(1000);
Console.WriteLine($"{nameof(Test)} end");
});
}

在两个issue提出之后,细细想来这种需求或许才是大家最常用的,有时不使用async语法,可能仅仅是因为方法的重载只需对参数稍作处理然后直接调用重载方法即可,这种情况下不使用async语法也是很正常的。针对这类情况,就有了本次版本推出的ExMoAttribute了。

ExMoAttribute

ExMoAttribute的目标是解决前面提到的问题,对没有使用async语法的方法采用MoAttribute中使用了async语法相同的逻辑。

快速开始

# 添加NuGet引用
dotnet add package Rougamo.Fody
class TimelineAttribute : ExMoAttribute
{
private Stopwatch _stopwatch; protected override void ExOnEntry(MethodContext context)
{
_stopwatch = Stopwatch.StartNew();
Console.WriteLine($"{context.Method.Name} {nameof(OnEntry)}");
} protected override void ExOnExit(MethodContext context)
{
_stopwatch.Stop();
Console.WriteLine($"{context.Method.Name} {nameof(OnExit)} - {_stopwatch.ElapsedMilliseconds}ms");
}
}

TimelineAttribute改成上面的代码即可完成最初统计耗时的需求了,应用了该Attribute的方法无论是否使用async语法都能达到同样的效果,再也不用去判断MethodContext.RealReturnType了。

ExMoAttribute的使用差异(重要)

ExMoAttributeMoAttribute除了类名和方法名有所区别之外,在使用时也有些许区别,下面是使用ExMoAttribute时与之前不同的地方:

  • 使用MethodContext.ExReturnValue获取方法返回值。如果是没有使用async语法的方法,使用之前的MethodContext.ReturnValue获取返回值,你获取到的会是Task/ValueTask类型的返回值,而不是其泛型参数类型;
  • 使用MethodContext.ExReturnType获取返回值类型。如果你要修改返回值或处理异常,你设置的返回值类型需要与MethodContext.ExReturnType相同;
  • 使用MethodContext.ExReturnValueReplaced获取返回值是否被修改。如果是没有使用async语法的方法,使用之前的MethodContext.ReturnValueReplaced获取到的一直都会是true,因为返回值被ContinueWith返回的Task替换了。

当前版本除了上面三个属性在使用时需要注意,其他的与MoAttribute基本无异,包括处理异常时依旧调用MethodContext.HandledException方法,修改/设置返回值时依旧调用MethodContext.ReplaceReturnValue方法,后续还会不会有其他差异就需要大家关注一下版本日志了。

.NET静态代码织入——肉夹馍(Rougamo) 发布1.2.0的更多相关文章

  1. .NET静态代码织入——肉夹馍(Rougamo) 发布1.1.0

    肉夹馍(https://github.com/inversionhourglass/Rougamo)通过静态代码织入方式实现AOP的组件,其主要特点是在编译时完成AOP代码织入,相比动态代理可以减少应 ...

  2. .NET静态代码织入——肉夹馍(Rougamo)

    肉夹馍是什么 肉夹馍通过静态代码织入方式实现AOP的组件..NET常用的AOP有Castle DynamicProxy.AspectCore等,以上两种AOP组件都是通过运行时生成一个代理类执行AOP ...

  3. 30个类手写Spring核心原理之AOP代码织入(5)

    本文节选自<Spring 5核心原理> 前面我们已经完成了Spring IoC.DI.MVC三大核心模块的功能,并保证了功能可用.接下来要完成Spring的另一个核心模块-AOP,这也是最 ...

  4. Spring的LoadTimeWeaver(代码织入)

    在Java 语言中,从织入切面的方式上来看,存在三种织入方式:编译期织入.类加载期织入和运行期织入.编译期织入是指在Java编译期,采用特殊的编译器,将切面织入到Java类中:而类加载期织入则指通过特 ...

  5. Spring的LoadTimeWeaver(代码织入)(转)

    https://www.cnblogs.com/wade-luffy/p/6073702.html 在Java 语言中,从织入切面的方式上来看,存在三种织入方式:编译期织入.类加载期织入和运行期织入. ...

  6. 【开源】.Net Aop(静态织入)框架 BSF.Aop

    BSF.Aop .Net 免费开源,静态Aop织入(直接修改IL中间语言)框架,类似PostSharp(收费): 实现前后Aop切面和INotifyPropertyChanged注入方式. 开源地址: ...

  7. Java AOP (1) compile time weaving 【Java 切面编程 (1) 编译期织入】

    According to wikipedia  aspect-oriented programming (AOP) is a programming paradigm that aims to inc ...

  8. AOP静态代理解析2-代码织入

    当我们完成了所有的AspectJ的准备工作后便可以进行织入分析了,首先还是从LoadTimeWeaverAwareProcessor开始. LoadTimeWeaverAwareProcessor实现 ...

  9. 框架源码系列三:手写Spring AOP(AOP分析、AOP概念学习、切面实现、织入实现)

    一.AOP分析 问题1:AOP是什么? Aspect Oriented Programming 面向切面编程,在不改变类的代码的情况下,对类方法进行功能增强. 问题2:我们需要做什么? 在我们的框架中 ...

随机推荐

  1. Burnside 引理与 Pólya 定理

    群 群的定义 在数学中,群是由一种集合以及一个二元运算所组成的,符合"群公理"的代数结构. 一个群是一个集合 \(G\) 加上对 \(G\) 的二元运算.二元运算用 \(\cdot ...

  2. Session会话 Cookie JSTL标签

    Cookie  1) HTTP是无状态协议(连接结束后就自动断开),服务器不能记录浏览器的访问状态,也就是说服务器不能区分中两次请求是否由一个客户端发出.这样的设计严重阻碍的Web程序的设计.如:在我 ...

  3. Identity Server 4资源拥有者密码认证控制访问API

    基于上一篇文章中的代码进行继续延伸,只需要小小的改动即可,不明白的地方可以先看看本人上一篇文章及源码: Identity Server 4客户端认证控制访问API 一.QuickStartIdenti ...

  4. 经典面试题:==和equals的区别

    1.== 既可以比较基本类型也可以比较引用类型.对于基本类型就是比较值,对于引用类型就是比较内存地址 2.equals的话,它是属于java.lang.Object类里面的方法,如果该方法没有被重写过 ...

  5. WannaRen来袭:螣龙安科带你盘点那些年的勒索病毒

    2020年4月7日,360CERT监测发现网络上出现一款新型勒索病毒wannaRen,该勒索病毒会加密windows系统中几乎所有的文件,并且以.WannaRen作为后缀.360CERT该事件评定:危 ...

  6. zip格式文件编码检测

    解压后文件名乱码 由于zip格式文件无编码存储的结构,因此解压时无法知道原先的编码. 当解压zip格式文件时使用的编码和原编码不一致时,就可能会出现解压后文件名乱码问题. 猜测编码 基于上述问题,需要 ...

  7. JavaScript进阶内容——BOM详解

    JavaScript进阶内容--BOM详解 在上一篇文章中我们学习了DOM,接下来让我们先通过和DOM的对比来简单了解一下BOM 首先我们先来复习一下DOM: 文档对象模型 DOM把文档当作一个对象来 ...

  8. SQL语句的整合

    基础语法 https://blog.csdn.net/m0_37989980/article/details/103413942 CRUD 提供给数据库管理员的基本操作,CRUD(Create, Re ...

  9. python 操作json数据

    简介 JSON(JavaScript Object Notation, JS对象简谱)是一种轻量级的数据交换格式,通常是以键值对的方式呈现,其简洁和清晰的层次结构使得JSON成为理想的数据交换语言,而 ...

  10. Odoo14 需要哪些技术

    1 PostgreSQL:数据库,存储数据. 2 Python :主要作用是控制数据库,如:建表.关联字段.批量数据-- 3 html.css.javascript:基础前端. 4 scss:前端样式 ...