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

上一篇文章至此共发布两个版本,本篇文章中将依次介绍2.1和2.2版本中新增的功能。

2.1

Pattern增强-支持Attribute匹配

在2.0版本推出了 表达式匹配 功能,支持通过字符串表达式匹配方法。2.0版本共支持methodgettersetterpropertyexecutionregex六种匹配规则,之后在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进行初始化之外,还会在执行OnSuccessOnException之前也进行一次更新,保证各阶段获取到的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属性包含了ArgsRewriteArgsFreshArgs中的任意一个,那么就表示需要使用到参数,此时指定该值将无效,参数依旧会被存储下来
  • All,会遗弃所有数据,当前版本就是Mos和Arguments,后续如果有增加枚举项,也将包含在内

使用优化-静默内置属性

依旧是github社区朋友反馈的issue,在使用肉夹馍封装中间件后,其他人在使用中间件定义的Attribute时IDE会提示出MoAttribute内置的属性,情况大致如下图:



MoAttribute内置的几个属性(FeaturesFlagsOrderPattern)都提示出来了,虽然说这几个属性在应用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的更多相关文章

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

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

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

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

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

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

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

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

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

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

  6. Spring的LoadTimeWeaver(代码织入)

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

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

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

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

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

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

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

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

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

随机推荐

  1. 【scikit-learn基础】--『预处理』之 离散化

    数据的预处理是数据分析,或者机器学习训练前的重要步骤.通过数据预处理,可以 提高数据质量,处理数据的缺失值.异常值和重复值等问题,增加数据的准确性和可靠性 整合不同数据,数据的来源和结构可能多种多样, ...

  2. ElasticSearch之Merge

    Elasticsearch的shard,即对应Lucene的index. Lucene的index由多个segment组成. segment是index保存数据的最小单位,不支持修改. Elastic ...

  3. 【笔记】负载均衡Robbin之不同服务使用不同的策略

    裂开裂开,搞这么久忘记导入依赖 妈卖批 又不报错 还能让我玩 我以为全部导入了. 话不多说,开始演示. 介绍 给不同的服务 配置 不同的 负载均衡策略 这里使用 用户模块 进行访问其它两个模块的con ...

  4. vulnhub - Aragog - writeup

    信息收集 目标开放了80.22端口. root@Lockly temp/tmp » arp-scan -I eth1 -l Interface: eth1, type: EN10MB, MAC: 00 ...

  5. 前端 Git 使用约定

    前端 Git 使用约定 背景 开发前端项目,有以下困惑: 使用哪个分支开发,哪个分支发布 修复线上bug的流程是什么,如何避免修复完了下次却又出现了 cms分支有十多个,是否都有用 如何快速找到之前某 ...

  6. Python——第一章:语言介绍

    随着Python的语言在世界受欢迎程度持续高涨,如今也成功夺得了第一的宝座. 在计算机领域,没有谁(诺基亚.微软.苹果.亚马逊等等)能永远稳坐第一,随着时间更迭,就会推陈出新,一定会有更好的.更先进的 ...

  7. Java中常用不可变类

    Java中常用的不可变类是指一旦被创建,它们的值就不可更改的类.在实际开发中,使用不可变类时可以带来多种优点,比如线程安全.缓存.副本等.下面我们将介绍Java中常见的不可变类: 1.字符串(Stri ...

  8. Java 中常见类型的判空方式

    引用类型(Reference Types): 使用 == 运算符判断是否为 null. 使用 != 运算符判断是否不为 null. 使用 Objects.isNull() 方法判断是否为 null. ...

  9. 云图说丨OLAP开源引擎的一匹黑马,MRS集群组件之ClickHouse

    摘要:ClickHouse是俄罗斯公司 Yandex 在2016年开源的高性能.开源联机分析列式数据库管理系统.开源后,凭借卓越的分析性能.极好的线性扩展能力和丰富的功能,被业界公认为实时分析领域 O ...

  10. aPaaS将如何改变软件行业?

    摘要:当SaaS在云计算中的占比越来越高的时候,几乎所有软件厂商言必谈SaaS,各大云厂商.咨询机构也都将目光瞄准了SaaS.如此火爆的现象背后,真实情况如何呢? 本文分享自华为云社区<[开天a ...