大家好,我是宝弟!
今天给大家推荐一个通过静态代码织入方式实现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
//OR
NuGet\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