Skip to content
ihourglass edited this page Aug 10, 2024 · 4 revisions

Rougamo - 肉夹馍

基础功能介绍

肉夹馍作为一款AOP组件,其基本功能就是在方法几个主要生命周期节点执行额外操作,肉夹馍支持四个生命周期节点:

  1. 方法执行前;
  2. 方法执行成功后;
  3. 方法抛出异常后;
  4. 方法退出时(无法方法执行成功还是抛出异常,都会进入该生命周期节点,类似try..finally..)。

用一个简单示例进行说明生命周期节点在代码中的表现形式:

// 定义一个类型继承MoAttribute
public class TestAttribute : MoAttribute
{
    public override void OnEntry(MethodContext context)
    {
        // OnEntry对应方法执行前
    }

    public override void OnException(MethodContext context)
    {
        // OnException对应方法抛出异常后
    }

    public override void OnSuccess(MethodContext context)
    {
        // OnSuccess对应方法抛出异常后
    }

    public override void OnExit(MethodContext context)
    {
        // OnExit对应方法退出时
    }
}

在方法的各个生命周期节点,除了能够执行类似记录日志、统计方法耗时、APM埋点等不影响方法执行的操作,还可以进行以下这些控制方法执行的操作:

  1. 修改方法参数
  2. 方法执行拦截
  3. 修改方法返回值
  4. 处理方法异常
  5. 重试执行方法

如何应用肉夹馍到方法上

最简单直接的方式便是将定义的Attribute直接应用到方法上,这个方法可以是同步的也可以是异步的,可以是实例方法也可以是静态方法,可以是属性也可以是属性getter/setter,可以是示例构造方法也可以是静态构造方法:

class Abc
{
    [Test]
    static Abc() { }

    [Test]
    public Abc() { }

    [Test]
    public int X { get; set; }

    public static Y
    {
        [Test]
        get;
        [Test]
        set;
    }

    [Test]
    public void M() { }

    [Test]
    public static async ValueTask MAsync() => await Task.Yeild();
}

直接应用到方法上的方式简单明了,但对于一些非常常用的AOP类型,每个方法都需要添加Attribute就显得非常麻烦,所以肉夹馍还提供了多种批量应用的方式:

  1. 类或程序集级别的Attribute的方式
  2. 低侵入性的实现空接口IRougamo的方式
  3. 指定某Attribute为代理Attribute,寻找应用了代理Attribute方法的方式

我们在进行批量应用的时候,比如将TestAttribute直接应用到一个类上,我们一般并不是希望这个类的所有方法都应用TestAttribute,而是希望选定那些满足特定特征的部分方法。肉夹馍在方法筛选上提供了两种方案:

  1. 粗粒度的方法特征匹配,可以指定匹配静态、实例、共有、私有、属性、构造方法等一个或多个粗粒度特征;
  2. 类AspectJ表达式匹配,提供了类似AspectJ格式的表达式,可以进行更为细致的匹配,比如方法名称、返回值类型、参数类型等。

异步切面

现在异步编程在日常开发中已经是非常常见的了,在最上面的示例中,方法生命周期节点对应的方法全是同步的,如果希望进行一些异步操作,就不得不手动阻塞异步操作等待返回结果了,这是存在一定的资源浪费和性能损耗的。肉夹馍除了同步切面,还提供了异步切面,更好的支持异步操作:

// 定义一个类型继承AsyncMoAttribute
public class TestAttribute : AsyncMoAttribute
{
    public override async ValueTask OnEntryAsync(MethodContext context) { }

    public override async ValueTask OnExceptionAsync(MethodContext context) { }

    public override async ValueTask OnSuccessAsync(MethodContext context) { }

    public override async ValueTask OnExitAsync(MethodContext context) { }
}

不过需要注意的是,如果并不需要做异步操作,那么还是推荐使用同步切面,更多关于异步切面的说明,请跳转 异步切面

性能优化

肉夹馍是方法级AOP组件,在对方法应用肉夹馍后,每次调用方法都会实例化肉夹馍的相关对象,增加GC的负担。虽然这些负担是轻微的,很多时候是可以忽略不计的,但肉夹馍关注其对性能的影响,所以从不同的方向提供了各种优化方式:

  1. 部分编织,如果你只是想在执行方法前记录一下调用日志,那么其他生命周期节点以及异常处理等功能实际并用不上,此时通过部分编织功能仅选择你需要功能进行编织,这样可以缩减实际编织的IL代码,同时减少运行时实际执行的指令数量;
  2. 结构体,类与结构体的其中一个区别便是,类分配在堆中,而结构体在栈中,使用结构体可以使肉夹馍的部分类型分配到栈中,减少GC压力;
  3. 瘦身MethodContextMethodContext中保存了当前方法的一些上下文信息,这些信息需要使用额外的对象进行保存,同时伴随着一些装箱拆箱操作,比如方法参数、返回值等,如果确定不需要这些信息,可以通过瘦身MethodContext达到一定的优化效果;
  4. 强制同步,在异步切面中介绍了异步切面与同步切面的关系,异步切面虽然使用了ValueTask来优化同步执行,但终究还是存在额外的开销,在编写异步切面时,如果确定不需要异步操作,可以通过强制调用同步切面来避免异步切面的额外开销。

了解更多