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

上一篇文章 中介绍了1.0.0版本肉夹馍的功能,1.0.0版本能够进行的AOP操作主要是日志记录以及APM操作,给出的示例项目也是OpenTelemetry的APM项目。在上一篇文章的评论以及github issue中都有朋友询问是否能处理异常以及修改返回值等操作,最终拖了较长一段时间于近期发布了1.1.0版本实现了这些功能。

快速开始

# 添加NuGet引用
dotnet add package Rougamo.Fody
public class TestService
{
[Fact]
public async void Test1()
{
var v1 = await M1();
Assert.Null(v1); var v2 = Sum(1, null);
Assert.Equal(-1, v2); var v3 = await M2();
Assert.Empty(v3);
} [MuteException]
public async Task<string> M1()
{
throw new NotImplementedException();
} [ArgNullCheck]
public int Sum(int? a, int? b)
{
return a.Value + b.Value;
} [ReturnNullCheck]
public async Task<string> M2()
{
await Task.Yield();
return null;
}
} public class MuteExceptionAttribute : MoAttribute
{
public override void OnException(MethodContext context)
{
if (context.RealReturnType == typeof(string))
{
context.HandledException(this, null);
}
}
} public class ArgNullCheckAttribute : MoAttribute
{
public override void OnEntry(MethodContext context)
{
foreach (var arg in context.Arguments)
{
if (arg == null)
{
context.ReplaceReturnValue(this, -1);
}
}
}
} public class ReturnNullCheckAttribute : MoAttribute
{
public override void OnSuccess(MethodContext context)
{
if (context.ReturnValue == null)
{
context.ReplaceReturnValue(this, string.Empty);
}
}
}

在上面的示例代码中MuteExceptionAttribute重写了OnException通过MethodContext.HandledException表明异常已处理并将返回值设置为null

ArgNullCheckAttribute重写了OnEntry通过MethodContext.ReplaceReturnValue设置了返回值,由于OnEntry是在执行方法前调用,这种方式会在OnEntry执行完毕之后直接将ReplaceReturnValue设置的返回值作为方法的返回值直接返回,一般参数验证、缓存逻辑会用到;

ReturnNullCheckAttribute重写了OnSuccess通过MethodContext.ReplaceReturnValue修改了实际的返回值,示例中通过这种方式避免返回null值。

注意事项

  • 如果方法是async Task那么MethodContext.RealReturnType取值为typeof(void),如果是async Task<T>那么取值为typeof(T),但如果返回值为TaskTask<T>但并没有使用async写法,那么其值就是typeof(Task)typeof(Task<T>),这样设定的好处是,你设置的返回值类型与该属性的值相同即可,不用考虑方法是否异步
  • 不论是异常处理还是设置/修改返回值,设置的返回值类型必须与方法定义的返回类型(MethodContext.RealReturnType)相同,类型不同时运行时会报错
  • OnExit中调用MethodContext.ReplaceReturnValue无法修改返回值

补充说明

上一篇文章 中由于是第一篇文章,介绍的东西较多,部分功能并没有在文章中详细说明,本篇由于篇幅较短,所以会补上一些说明,不过这里也不会介绍全部的,详细的介绍可以移步 github(https://github.com/inversionhourglass/Rougamo)

Iterator / AsyncIterator 不支持修改返回值和异常处理

IteratorAsyncIterator也就是下面的写法

public IEnumerable<int> Iterator(int count)
{
yield return 1;
yield return 2;
yield return 3;
} public async IAsyncEnumerable<int> AsyncIterator(int count)
{
yield return 3;
await Task.Yield();
yield return 2;
await Task.Yield();
yield return 1;
}

之所以不支持,是因为它们并不直接返回一个集合,而是返回一个状态机(StateMachine),使用foreach迭代时实际每次迭代执行状态机的MoveNext方法获取本次迭代的返回值,考虑到实现这种特殊机制的复杂性以及平时使用的频率,当前对此种类型不进行支持。

Iterator / AsyncIterator 不支持记录返回值

同样的,IteratorAsyncIterator默认也无法通过MethodContext.ReturnValue获取方法的返回值,但可以通过FodyWeavers.xmlRougamo节点增加属性配置enumerable-returns="true"来记录IteratorAsyncIterator的返回值到MethodContext.ReturnValue

<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<Rougamo enumerable-returns="true" />
</Weavers>

这个设定是因为状态机并没有保存所有的元素到一个集合中,每个元素都是一次一次调用MoveNext执行代码返回的,如果你使用foreach遍历IteratorAsyncIterator,并且对每次遍历的元素使用玩之后并没有进行保存,那么上一个元素可能在你遍历下一个元素时被GC回收。记录它们的返回值的实现方式是额外建立一个集合保存每次迭代的元素值,这种方式对上面说的的foreach遍历的情况来说会产生额外的内存消耗,而如果迭代器的元素很多,或者每个元素本身很占内存,那么这种方式可能会额外占用大量内存空间,所以开启这个开关前需要考虑一番。

最后

如果在使用肉夹馍的过程中遇到了什么问题,或者希望增加一些什么样的功能,欢迎到github(https://github.com/inversionhourglass/Rougamo)里提issue,不过对于新功能,可能会有一个较长的周期才能完成并发布正式版。

随着SourceGenerator的应用越来越广泛,Mono.Cecil的应用场景被进一步压缩,一开始提到的动态代理现在也能通过SourceGenerator在编译时生成代理类,这是一件好事,相比晦涩易错的IL,SourceGenerator提供的语法树更加方便易懂且不易出错,但这并不代表Mono.Cecil应该退场了(至少现在不是),Mono.Cecil虽然门槛高,但他的功能也同样强大,直接修改IL是SourceGenerator和`Emit所无法做到的(至少现在是这样),如果在以后的编程之路中遇到了SourceGenerator和`Emit无法解决的问题,希望你能想起还有Mono.CecilFody这条路,如果有时间可以尝试一下,也希望肉夹馍这个项目能给你带来一些参考价值。

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

  1. .NET静态代码织入——肉夹馍(Rougamo) 发布1.2.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. 个人冲刺(四)——体温上报app(二阶段)

    冲刺任务:完成用户注册功能和数据库类 RegisterActivity.java package com.example.helloworld; import android.content.Inte ...

  2. K8S 使用Kubeadm搭建单个Master节点的Kubernetes(K8S)~本文仅用于测试学习

    01.集群规划 系统版本:CentOS Linux release 7.6.1810 (Core) 软件版本:kubeadm.kubernetes-1.15.docker-ce-18.09 硬件要求: ...

  3. Property or method "xxx" is not defined on the instance but referenced during render

    是xxx中的data写成date了,因此报错. 这个错误属于粗心

  4. vue项目|在弹窗中引入uchart图表子组件不显示

    为了解决uchart作为子组件在主组件里引用但不显示的情况,(同样适用于弹窗之中)目前有三种方法. 1-解决方式 1>如果你使用的uchart子组件是从官方拿的例子:进入到uchart子组件将o ...

  5. SSE图像算法优化系列三十二:Zhang\Guo图像细化算法的C语言以及SIMD指令优化

    二值图像的细化算法也有很多种,比较有名的比如Hilditch细化.Rosenfeld细化.基于索引表的细化.还有Opencv自带的THINNING_ZHANGSUEN.THINNING_GUOHALL ...

  6. 使用 vim 快速对当前文件夹下的文件批量重命名

    前言 我们在使用 Linux 的时候,有很多种方法可以对文件进行重命名,例如 命令行 下的 mv 命令,或者是使用像 dolphin 这样的图形文件管理器.但是有时候需要对一个文件夹下的所有文件批量重 ...

  7. JAVA - 启动一个线程是用run()还是start()?

    JAVA - 启动一个线程是用run()还是start()? 启动一个线程是调用start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM调度并执行.这并不意味着线程就会立即运 ...

  8. CVPR2022 | 重新审视池化:你的感受野不是最理想的

    前言 本文提出了一种简单而有效的动态优化池操作( Dynamically Optimized Pooling operation),称为DynOPool,它通过学习每一层感受野的最佳大小和形状来优化特 ...

  9. python基础学习5

    Python的基础学习5 内容概要 流程控制理论 if判断 while循环 内容详情 流程控制理论 # 流程控制:即控制事物执行的流程 # 执行流程的分类 1.顺序结构 从上往下按顺序依次执行 2.分 ...

  10. Wireshark学习笔记(一)常用功能案例和技巧

    @ 目录 常用功能 1.统计->捕获属性 2.统计->协议分级 3.过滤包Apply as filter E1:过滤出特定序号的包 E2:过滤出某IP地址或端口 E3:导出php文件 E4 ...