一个通过静态代码织入方式实现AOP的组件Rougamo

大家好,我是宝弟!

今天给大家推荐一个通过静态代码织入方式实现AOP的组件Rougamo(肉夹馍)。在.NET中常用的AOP有Castle DynamicProxy、AspectCore等,以上两种AOP组件都是通过运行时生成一个代理类执行AOP代码的,Rougamo则是在代码编译时直接修改原始方法IL代码,在原始方法内织入AOP代码的。.NET静态AOP的组件或许有人使用过PostSharp,这是一个功能完善且强大的静态代码织入组件,Postsharp有社区版,但可惜的是社区版不支持异步方法,Rougamo的实现方式与Postsharp类似,同时也支持了异步方法,如果你仅仅使用了Postsharp方法层级的AOP代码织入功能,可以尝试使用Rougamo来替代Postsharp。.

快速开始

添加依赖

dotnet add package Rougamo.Fody//ORNuGet\Install-Package Rougamo.Fody

定义类继承MoAttribute,在该类中定义你在方法执行各阶段需要织入的代码

public class LoggingAttribute : MoAttribute{    public override void OnEntry(MethodContext context)    {        // 从context对象中能取到包括入参、类实例、方法描述等信息        Log.Info("方法执行前");    }    public override void OnException(MethodContext context)    {        Log.Error("方法执行异常", context.Exception);    }    public override void OnExit(MethodContext context)    {        Log.Info("方法退出时,不论方法执行成功还是异常,都会执行");    }    public override void OnSuccess(MethodContext context)    {        Log.Info("方法执行成功后");    }}

2.在需要织入代码的方法上应用LoggingAttribute

public class Service{    [Logging]    public static int Sync(Model model)    {        // ...    }    [Logging]    private async Task<Data> Async(int id)    {        // ...    }}
通过实现空接口的方式进行代码织入

在上面的示例中,我们通过在方法上应用Attribute进行AOP,这种方式目标明确但有些AOP代码我们可能希望应用于某一场景或某一层级,每个方法都去应用Attribute很繁琐,而且代码侵入严重。此时就可以考虑使用实现空接口(IRougamo<>)的方式进行批量Attribute应用

public interface IService : IRougamo<LoggingAttribute> { }public interface IMyService : IService { }public class MyService : IMyService{}

上面的示例中,MyService所有的public实例方法都将应用LoggingAttribute,你可能注意到我标红的部分了,为什么是public实例方法呢?这是默认值,你可以在继承MoAttribute时通过重写Flags属性来修改这一默认值,比如下面的示例中FullLoggingAttribute将会应用于所有方法。另外需要注意的是Flags属性在Attribute直接应用到方法上时是无效的,比如LoggingAttribute默认仅应用public实例方法,但像快速开始里的代码那样Async方法虽然是private的但还是会应用LoggingAttribute

public class FullLoggingAttribute : LoggingAttribute{    public override AccessFlags Flags => AccessFlags.All;}
异常处理和修改返回值

在OnException方法中可以通过调用MethodContext的HandledException方法表明异常已处理并设置返回值, 在OnEntry和OnSuccess方法中可以通过调用MethodContext的ReplaceReturnValue方法修改方法实际的返回值,需要注意的是, 不要直接通过ReturnValue、ExceptionHandled等这些属性来修改返回值和处理异常,HandledException和 ReplaceReturnValue包含一些其他逻辑,后续可能还会更新。同时还需要注意,Iterator/AsyncIterator没有该功能。

public class TestAttribute : MoAttribute{    public override void OnException(MethodContext context)    {        // 处理异常并将返回值设置为newReturnValue,如果方法无返回值(void),直接传入null即可        context.HandledException(this, newReturnValue);    }    public override void OnSuccess(MethodContext context)    {        // 修改方法返回值        context.ReplaceReturnValue(this, newReturnValue);    }}
重写方法参数

在OnEntry中可以通过修改MethodContext.Arguments中的元素来修改方法的参数值,为了兼容在没有该功能的老版本中可能使用MethodContext.Arguments 存储一些临时值的情况(虽然可能性很小),所以还需要将MethodContext.RewriteArguments设置为true来确认重写参数。

public class DefaultValueAttribute : MoAttribute{    public override void OnEntry(MethodContext context)    {        context.RewriteArguments = true;        // 判断参数类型最好通过下面ParameterInfo来判断,而不要通过context.Arguments[i].GetType()        // 因为context.Arguments[i]可能为null        var parameters = context.Method.GetParameters();        for (var i = 0; i < parameters.Length; i++)        {            if (parameters[i].ParameterType == typeof(string) && context.Arguments[i] == null)            {                context.Arguments[i] = string.Empty;            }        }    }}public class Test{    // 当传入null值时将返回空字符串    [DefaultValue]    public string EmptyIfNull(string value) => value;}
忽略织入

在快速开始中,我们介绍了如何批量应用,由于批量引用的规则只限定了方法可访问性,所以可能有些符合规则的方法并不想应用织入, 此时便可使用IgnoreMoAttribute对指定方法/类进行标记,那么该方法/类(的所有方法)都将忽略织入。如果将IgnoreMoAttribute 应用到程序集(assembly)或模块(module),那么该程序集(assembly)/模块(module)将忽略所有织入。另外,在应用IgnoreMoAttribute 时还可以通过MoTypes指定忽略的织入类型。

// 当前程序集忽略所有织入[assembly: IgnoreMo]// 当前程序集忽略TheMoAttribute的织入[assembly: IgnoreMo(MoTypes = new[] { typeof(TheMoAttribute))]// 当前类忽略所有织入[IgnoreMo]class Class1{    // ...}// 当前类忽略TheMoAttribute的织入[IgnoreMo(MoTypes = new[] { typeof(TheMoAttribute))]class Class2{    // ...}

 资源获取方式 

https://github.com/inversionhourglass/Rougamo