.NET静态代码织入——肉夹馍(Rougamo)发布2.2
肉夹馍(https://github.com/inversionhourglass/Rougamo)通过静态代码织入方式实现AOP的组件,其主要特点是在编译时完成AOP代码织入,相比动态代理可以减少应用启动的初始化时间让服务更快可用,同时还能对静态方法进行AOP操作。
上一篇文章至此共发布两个版本,本篇文章中将依次介绍2.1和2.2版本中新增的功能。
2.1
Pattern增强-支持Attribute匹配
在2.0版本推出了 表达式匹配 功能,支持通过字符串表达式匹配方法。2.0版本共支持method、getter、setter、property、execution和regex六种匹配规则,之后在github上收到社区朋友的issue反馈,希望能够直接筛选出应用了某个Attribute的方法,因此在2.1版本中新增匹配规则attr。
attr的基本格式为attr(POS TYPE):
TYPE,表示我们匹配的Attribute类型,其格式与其他匹配规则中类型的格式相同POS,表示应用Attribute的位置,我们知道Attribute可以应用于程序集、类、方法、属性、字段、参数、返回值等,POS支持以下几种位置type,表示Attribute应用于类型上exec,表示Attribute应用于方法或属性或属性getter或属性setter上(后续统一简称方法)para x,表示Attribute应用于方法参数上,其中x表示参数的位置,0表示第一个参数,x为*时表示任意参数ret,表示Attribute应用于方法返回值上
下面展示几个简单的表达式示例:
// 1. 匹配类型上应用了类名为ObsoleteAttribute(任意命名空间)的类型,选取该类型下的所有方法
"attr(type ObsoleteAttribute)"
// 2. 匹配应用了Test.XAttribute(限定Test命名空间)的方法
"attr(exec Test.XAttribute)"
// 3. 匹配方法第3个参数(参数索引从0开始)上应用了Restrict命名空间下类名以Attribute结尾的任意Attribute的方法
"attr(para 2 Restrict.*Attribute)"
// 4. 匹配方法返回值上应用了任意Attribute的方法
"attr(ret *)"
// 5. 联合其他匹配规则完成更复杂的匹配
"attr(type ObsoleteAttribute) && method(public * *(..))"
我们知道,在应用Attribute时对于同一个Attribute类型,我们还可以设置其构造参数和属性,不同的构造参数和属性所表达的含义可能完全不同,我们可能会有希望能够更详细的匹配其参数值和属性值的需求。但遗憾的是,肉夹馍目前没有计划在这个目标上继续细化,表达式匹配相对于最早的AccessFlags提供了更灵活细化的匹配方式,但同样增加了学习成本和复杂度,目前是希望在灵活度和复杂度上取一个平衡,不希望表达式演变到晦涩难懂,也同样希望能满足基本的需求。当然,另一方面来说,相对简单的表达式也为后续的维护省下不少精力。对于确实有需要对参数值和属性值进行细分处理的,目前建议在OnEntry等方法中通过MethodContext.Method获取更多方法相关信息进行处理。
参数值实时更新
这同样是github上社区的朋友通过issue反馈的,方法包含out参数值,该参数在OnSuccess中无法从MethodContext.Arguments中获取到。
在此前的版本中,MethodContext.Arguments实际只有在执行OnEntry前会进行一次初始化,之后并不会对其进行更新,也就是说实际上不仅out的参数值没有更新,其他参数同样没有更新。可能有的朋友之前也从MethodContext.Arguments中获取过参数值并且发现是最新的,那大概是因为你这个参数是引用类型,你只是修改了应用对象的内部值,而不是直接为这个参数重新赋值。
2.1版本除了在执行OnEntry之前对MethodConetext.Arguments进行初始化之外,还会在执行OnSuccess和OnException之前也进行一次更新,保证各阶段获取到的Arguments都是最新的。该功能在升级到2.1+版本后自动生效。
这样的更新操作势必要额外产生一些代码的,所以Feature同时新增枚举项FreshArgs,如果你并不需要这个刷新参数值的功能,可以通过排除该枚举值来减少代码的织入,对Feature不理解的朋友可以回顾往期的 部分织入 介绍。
2.2
2.2版本的主要内容是性能优化,同样是来自github社区朋友的issue反馈,使用肉夹馍后GC相应的也增加了。在2.2版本从引入结构体、延迟初始化(没用到则不初始化)、减少装箱等各方面进行优化,下面介绍的是我们使用上的一些变化,内部的细节优化这里不做阐述。
结构体
结构体大家都知道,但是使用可能并不是很频繁,类对象是分配在堆上的,最后由GC回收,而结构体是保存在栈上的,在调用栈结束后直接释放不走GC,所以一般结构体的效率是优于类的。关于类和结构体有一个很有趣的事情分享一下,我们知道C#项目在编译时可以选择Debug模式和Release模式,Debug模式下包含了很多调试信息,并且不会对我们的代码进行优化,而Release模式则会精简很多,无意义的分支跳转甚至都会被优化掉,不仅如此,Release模式下还使用结构体代替类对异步方法进行了优化。每个异步方法都会生成一个实现了IAsyncStateMachine的类型,在Debug模式下生成的类型时一个class,而Release模式则是struct,感兴趣的同学可以反编译看看,这里不再展开了。
那么回归主题,既然结构体这么好,那是不是无脑将class改为struct就好了呢,答案显而易见,不然就不会有class了。结构体是值传递,一个结构体在方法间进行传递时实际是复制了一个副本进行传递,那么最直观的表现就是你将一个结构体传入方法M,方法M内部修改了结构体的字段,但是在方法M执行完毕后,你外部的结构体字段并没有变化。再回到肉夹馍中,肉夹馍定义的类型主要包含两个,一个是继承了MoAttribute定义AOP内容的类型,另一个是MethodContext,其中MethodContext用于在多个MoAttribute子类之间传递,所以无法改造为结构体,那么结构体的优化就落在了MoAttribute上了。
MoAttribute继承自Attribute,而Attribute是一个class,MoAttribute是无法直接定义为struct的。如果对肉夹馍已经比较熟悉的朋友可能知道,MoAttribute除了继承自Attribute还实现了IMo接口,同时肉夹馍除了可以MoAttribute直接应用于类或方法上的方式之外,还有一种方式是让被织入类型实现空接口IRougamo<T>,而IRougamo<T>的泛型约束是where T : IMo, new(),所以最本质上还是IMo这个接口。那么下面的例子展示了如何使用结构体进行优化:
// 1. 定义结构体实现IMo接口
struct ValueMo : IMo
{
// 实现接口,定义AOP操作
}
// 2.1. 通过RougamoAttribute指定结构体类型
[Rougamo(typeof(ValueMo))]
class Cls
{
// 如果项目使用C#11及以上语法,可以直接使用下面这种泛型Attribute
[Rougamo<ValueMo>]
public void M() { }
}
// 2.2. 同样可以通过IRougamo<T>配合使用
class Clss : IRougamo<ValueMo>
{
}
遗弃部分数据
2.0版本中介绍了部分织入的功能,我们可以通过选择自己需要的功能来减少织入的IL代码量。现在,我们还可以选择丢掉部分我们不需要的数据。MethodContext中保存了方法上下文信息,同样的,也不是所有信息大家都会需要。在2.2版本中,将部分相对有性能开销的部分数据设置为可选,在实现IMo接口或继承MoAttribute时可以通过MethodContextOmits属性进行设置,该属性类型为枚举,包含以下枚举项:
None,不会遗弃任何数据,默认值Mos,会遗弃MethodContext.Mos属性值,该属性包含了当前方法织入的所有IMo对象,如果你使用了结构体,那么在存储到该属性中时将包含一个装箱操作,同时也会增加一个IReadOnlyList<IMo>对象。需要注意的是,使用ExMoAttribute时请务必不要指定该值Arguments,会遗弃MethodContext.Arguments属性值,该属性存储了调用放方法的所有入参值,如果入参中包含值类型,那么将产生一个装箱操作,同时也会增加一个object[]对象。需要注意的是,如果Features属性包含了Args、RewriteArgs或FreshArgs中的任意一个,那么就表示需要使用到参数,此时指定该值将无效,参数依旧会被存储下来All,会遗弃所有数据,当前版本就是Mos和Arguments,后续如果有增加枚举项,也将包含在内
使用优化-静默内置属性
依旧是github社区朋友反馈的issue,在使用肉夹馍封装中间件后,其他人在使用中间件定义的Attribute时IDE会提示出MoAttribute内置的属性,情况大致如下图:

MoAttribute内置的几个属性(Features、Flags、Order、Pattern)都提示出来了,虽然说这几个属性在应用Attribute时进行设置是肉夹馍提供的功能之一,但是如果咱们的中间件已经为某些属性设置了默认值,同时不希望开发者在应用Attribute时再去修改。比如上图中,如果RetryAttribute在定义时已经重写了Feature属性,限定了要启用的功能,就不希望使用RetryAttribute的人再修改该值,最好的方法就是让IDE都不提示出这个属性,但此时又会发现这些属性根本无法屏蔽。虽然可以人为提醒开发者不要设置该属性,但这在使用上非常不友好。
这个现象主要原因是MoAttribute将属性的getter和setter都设置为了public,所有继承自MoAttribute的类都无法屏蔽public的setter,所以在2.2版本中,MoAttribute的所有属性的setter都修改为private,是否将属性公开由继承MoAttribute的类型决定,可以通过new关键字覆盖原属性然后将setter改为public:
public class OrderableAttribute : MoAttribute
{
// 通过new关键字覆盖了MoAttribute的Order属性,同时将setter设置为public
// virtual关键字是可选的,如果你确定没有类型会继承自TestAttribute并重写Order属性,那么可以去掉virtual
// 这里为Order设置了一个默认值,当然也可以不设置
public new virtual double Order { get; set; } = 10;
}
注意:这个功能会影响到现在在应用Attribute时设置MoAttribute内置属性的开发者,更新到该版本之后将会产生报错,请及时按需按上面的方式重新将需要的属性进行公开。由于不必要的属性提示确实会给中间件开发者带来困扰,且该功能也无法平滑过度,所以只能一刀切,对受到影响的开发者深表抱歉。
.NET静态代码织入——肉夹馍(Rougamo)发布2.2的更多相关文章
- .NET静态代码织入——肉夹馍(Rougamo) 发布1.4.0
肉夹馍(https://github.com/inversionhourglass/Rougamo)通过静态代码织入方式实现AOP的组件,其主要特点是在编译时完成AOP代码织入,相比动态代理可以减少应 ...
- .NET静态代码织入——肉夹馍(Rougamo) 发布1.1.0
肉夹馍(https://github.com/inversionhourglass/Rougamo)通过静态代码织入方式实现AOP的组件,其主要特点是在编译时完成AOP代码织入,相比动态代理可以减少应 ...
- .NET静态代码织入——肉夹馍(Rougamo) 发布1.2.0
肉夹馍(https://github.com/inversionhourglass/Rougamo)通过静态代码织入方式实现AOP的组件,其主要特点是在编译时完成AOP代码织入,相比动态代理可以减少应 ...
- .NET静态代码织入——肉夹馍(Rougamo)
肉夹馍是什么 肉夹馍通过静态代码织入方式实现AOP的组件..NET常用的AOP有Castle DynamicProxy.AspectCore等,以上两种AOP组件都是通过运行时生成一个代理类执行AOP ...
- 30个类手写Spring核心原理之AOP代码织入(5)
本文节选自<Spring 5核心原理> 前面我们已经完成了Spring IoC.DI.MVC三大核心模块的功能,并保证了功能可用.接下来要完成Spring的另一个核心模块-AOP,这也是最 ...
- Spring的LoadTimeWeaver(代码织入)
在Java 语言中,从织入切面的方式上来看,存在三种织入方式:编译期织入.类加载期织入和运行期织入.编译期织入是指在Java编译期,采用特殊的编译器,将切面织入到Java类中:而类加载期织入则指通过特 ...
- Spring的LoadTimeWeaver(代码织入)(转)
https://www.cnblogs.com/wade-luffy/p/6073702.html 在Java 语言中,从织入切面的方式上来看,存在三种织入方式:编译期织入.类加载期织入和运行期织入. ...
- 【开源】.Net Aop(静态织入)框架 BSF.Aop
BSF.Aop .Net 免费开源,静态Aop织入(直接修改IL中间语言)框架,类似PostSharp(收费): 实现前后Aop切面和INotifyPropertyChanged注入方式. 开源地址: ...
- Java AOP (1) compile time weaving 【Java 切面编程 (1) 编译期织入】
According to wikipedia aspect-oriented programming (AOP) is a programming paradigm that aims to inc ...
- AOP静态代理解析2-代码织入
当我们完成了所有的AspectJ的准备工作后便可以进行织入分析了,首先还是从LoadTimeWeaverAwareProcessor开始. LoadTimeWeaverAwareProcessor实现 ...
随机推荐
- 基于.NetCore开发博客项目 StarBlog - (30) 实现评论系统
前言 时隔五个月,终于又来更新 StarBlog 系列了~ 这次是呼声很大的评论系统. 由于涉及的代码量比较大,所以本文不会贴出所有代码,只介绍关键逻辑,具体代码请同学们自行查看 GitHub 仓库. ...
- Redis全文搜索教程之创建索引并关联源数据
Redis 全文搜索是依赖于 Redis 官方提供的 RediSearch 来实现的.RediSearch 提供了一种简单快速的方法对 hash 或者 json 类型数据的任何字段建立二级索引,然后就 ...
- 数字孪生和GIS融合:激发数字孪生技术的进步潜力
数字孪生技术和地理信息系统(GIS)的融合,为数字孪生领域带来了巨大的进步和创新.这种强大的联合不仅扩展了数字孪生技术的应用领域,还提供了更全面.更精确的数据支持,从而推动了数字孪生技术的发展和应用. ...
- python tkinter 使用(十)
python tkinter 使用(十) #!/usr/bin/python3 # -*- coding: UTF-8 -*- """ @Author: zh @Time ...
- #11独立开发周总结|核心OKR1000元/月已达标
核心OKR:1000元/月达成情况 算上微信上收费了200多元,核心OKR已达标 12.25-12.29本周完成事项 产品方面 本周产品上主要是在进行重构的测试,顺利上线,线上问题也比较少 运营方面 ...
- android Handler应用
android在运行时改变ui需要在ui线程中修改才行,不然就会报错或者无法启动应用. 我们怎么可以做事不管呢? 既然不能在ui线程外的地方运行修改ui的代码,我们可以用Handler解决这个问题, ...
- 文心一言 VS 讯飞星火 VS chatgpt (35)-- 算法导论5.3 5题
五.证明:在过程 PERMUTE-BY-SORTING的数组 P中,所有元素都唯一的概率至少是1-1/n. 文心一言: 证明: 在过程PERMUTE-BY-SORTING中,对于输入数组P中的每个元素 ...
- 网络性能总不好?网络调优专家AOE帮你来“看看”
摘要:为提升网络性能.降低人工调优成本,CANN推出了自动化网络调优工具AOE,通过子图调优.算子调优与梯度调优的功能,让网络可以在AI硬件上获得最佳性能. 本文分享自华为云社区<网络性能总不好 ...
- 从原理到实践,手把手带你轻松get数仓双集群容灾
摘要:本文通过介绍双集群的架构.log结构.分析步骤来介绍双集群容灾的问题分析方法. 本文分享自华为云社区<从原理到实践,手把手带你轻松get数仓双集群容灾>,原文作者:Puyol . 双 ...
- ByConity 社区回顾|ByConity 和开发者们一起展望未来,携手共进!
更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 新年伊始,我们想在这里感谢一群 ByConity 社区的小伙伴们. 正是因为有社区的开发者的支持,截止到 2023 ...