Rougamo.Fody
Rougamo.Fody
是一个基于 .NET
的 AOP
(面向切面编程)库,它借助 Fody
这个代码编织工具,在编译时将 AOP 逻辑植入入选定的方法中,实现诸如日志记录、异常处理、性能监控等横切关注点的功能。与运行时通过反射实现的 AOP 框架(如 Castle DynamicProxy
)相比,它的优势在于无运行时性能损耗,因为所有代码注入都在编译阶段完成。
一、核心特性
编译时织入:通过
Fody
在编译时修改 IL 代码,将 AOP 逻辑直接嵌入目标方法,避免反射带来的性能开销。特性驱动:通过自定义特性(
Attribute
)标记需要增强的方法,使用简单直观。支持多种切入点:可在方法执行前(
Before
)、执行后(After
)、抛出异常时(OnException
)插入逻辑。低侵入性:无需修改原有业务代码结构,仅通过特性标记即可实现增强。
二、基本使用步骤
1. 安装依赖包
通过 NuGet 安装以下两个包(Rougamo.Fody
依赖 Fody
):
Install-Package Rougamo.Fody
Install-Package Fody
安装后,项目根目录会自动生成 FodyWeavers.xml
配置文件,确保其中包含 Rougamo
:
<?xml version="1.0" encoding="utf-8"?>
<Weavers>
<Rougamo />
</Weavers>
2. 定义 AOP 特性(切面)
创建继承自 MoAttribute
的自定义特性,重写需要的方法(Before
、After
、OnException
等):
using System;
using Rougamo;
// 定义一个日志切面,记录方法调用信息
public class LogAttribute : MoAttribute
{
// 方法执行前调用
public override void Before(MethodContext context)
{
var methodName = context.Method.Name;
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 开始执行方法:{methodName}");
}
// 方法执行后调用(无论是否抛出异常)
public override void After(MethodContext context)
{
var methodName = context.Method.Name;
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 方法执行结束:{methodName}");
}
// 方法抛出异常时调用
public override void OnException(MethodContext context, Exception exception)
{
var methodName = context.Method.Name;
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 方法 {methodName} 抛出异常:{exception.Message}");
}
}
3. 使用特性标记目标方法
在需要增强的方法上添加自定义特性:
public class Calculator
{
// 用 LogAttribute 标记,实现日志记录
[Log]
public int Add(int a, int b)
{
return a + b;
}
[Log]
public int Divide(int a, int b)
{
if (b == 0)
{
throw new ArgumentException("除数不能为0");
}
return a / b;
}
}
4. 运行效果
调用标记了 [Log]
的方法时,会自动执行切面中的逻辑:
var calculator = new Calculator();
calculator.Add(1, 2);
calculator.Divide(6, 2);
calculator.Divide(6, 0); // 触发异常
输出结果:
[14:30:00] 开始执行方法:Add
[14:30:00] 方法执行结束:Add
[14:30:01] 开始执行方法:Divide
[14:30:01] 方法执行结束:Divide
[14:30:02] 开始执行方法:Divide
[14:30:02] 方法 Divide 抛出异常:除数不能为0
[14:30:02] 方法执行结束:Divide
三、高级用法
1. 方法参数与返回值获取
通过 MethodContext
可获取方法的参数、返回值等信息:
public override void Before(MethodContext context)
{
var parameters = context.Parameters; // 方法参数数组
var paramStr = string.Join(", ", parameters.Select(p => p.ToString()));
Console.WriteLine($"参数:{paramStr}");
}
public override void After(MethodContext context)
{
var returnValue = context.ReturnValue; // 方法返回值
Console.WriteLine($"返回值:{returnValue}");
}
2. 过滤需要增强的方法
通过重写 Match
方法,动态决定是否对目标方法应用切面:
public override bool Match(MethodContext context)
{
// 仅对 public 方法生效
return context.Method.IsPublic;
}
3. 异步方法支持
Rougamo.Fody
对异步方法(async/await
)有原生支持,无需额外配置:
[Log]
public async Task<int> AsyncAdd(int a, int b)
{
await Task.Delay(100);
return a + b;
}
切面中的 Before
、After
会正确对应异步方法的执行前后(等待 await
完成后触发 After
)。
四、优势与适用场景
性能优异:编译时织入,无运行时反射开销,适合性能敏感场景。
使用简单:通过特性标记即可,无需复杂的配置或接口实现。
适用场景:
日志记录、性能计时(如接口响应时间)。
异常统一处理(如全局异常捕获与上报)。
权限验证、缓存控制等横切关注点。
五、注意事项
调试体验:由于代码在编译时被修改,调试时看到的源代码与实际执行的 IL 代码可能存在差异(但通常不影响断点调试)。
版本兼容性:需确保
Rougamo.Fody
、Fody
与 .NET 版本兼容(建议使用最新稳定版)。第三方库方法:无法增强外部库中的方法(仅对当前项目编译的代码生效)。
总结
Rougamo.Fody
是一个轻量、高效的 AOP 框架,通过编译时织入实现无性能损耗的方法增强。它适合需要在 .NET 项目中实现日志、异常处理等横切逻辑,且对性能有要求的场景。相比运行时 AOP 框架,它的侵入性更低,性能更优,是 .NET 生态中 AOP 方案的良好选择。
Castle DynamicProxy
Castle DynamicProxy
是 .NET 生态中一款经典的运行时 AOP(面向切面编程)框架,它通过动态生成代理类的方式,在不修改原始代码的前提下为方法添加额外逻辑(如日志、缓存、事务等)。与编译时织入的 AOP 框架(如 Rougamo.Fody
)不同,它的代理类在运行时动态创建,灵活性更高,但会带来少量反射开销。
一、核心原理
Castle DynamicProxy
的核心是代理模式:
为目标类(或接口)动态动态生成一个代理类(继承自目标类或实现目标接口)。
代理类中包含目标对象的引用,并在目标方法执行前后插入自定义逻辑(如
Before
、After
)。客户端通过代理类调用方法,间接执行目标对象的逻辑,同时触发增强逻辑。
二、基本使用步骤
1. 安装 NuGet 包
Install-Package Castle.Core
2. 定义拦截器(AOP 逻辑)
拦截器是实现 IInterceptor
接口的类,包含需要插入的增强逻辑:
using Castle.DynamicProxy;
using System;
// 日志拦截器:记录方法调用信息
public class LogInterceptor : IInterceptor
{
// 拦截方法调用的核心方法
public void Intercept(IInvocation invocation)
{
// 1. 方法执行前(Before)
var methodName = invocation.Method.Name;
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 开始执行:{methodName}");
try
{
// 2. 执行目标方法(调用原始方法)
invocation.Proceed();
}
catch (Exception ex)
{
// 3. 方法抛出异常时
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 方法 {methodName} 异常:{ex.Message}");
throw; // 可选:继续抛出异常,让上层处理
}
finally
{
// 4. 方法执行后(After,无论是否异常)
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 结束执行:{methodName}");
}
}
}
3. 创建代理对象
通过 ProxyGenerator
为目标类生成代理对象,并关联拦截器:
场景 1:代理接口(推荐)
// 定义接口
public interface ICalculator
{
int Add(int a, int b);
int Divide(int a, int b);
}
// 实现接口
public class Calculator : ICalculator
{
public int Add(int a, int b) => a + b;
public int Divide(int a, int b)
{
if (b == 0)
throw new ArgumentException("除数不能为0");
return a / b;
}
}
// 生成代理对象
var generator = new ProxyGenerator();
var interceptor = new LogInterceptor();
// 为接口创建代理(传入目标实现类)
ICalculator proxy = generator.CreateInterfaceProxyWithTarget<ICalculator>(
new Calculator(), // 目标对象
interceptor // 拦截器
);
场景 2:代理类(需目标类为 public
且非密封类)
// 目标类(非密封、public)
public class Calculator
{
public virtual int Add(int a, int b) => a + b; // 需标记为 virtual
public virtual int Divide(int a, int b)
{
if (b == 0)
throw new ArgumentException("除数不能为0");
return a / b;
}
}
// 生成类代理(目标类方法需为 virtual)
var generator = new ProxyGenerator();
var proxy = generator.CreateClassProxy<Calculator>(new LogInterceptor());
4. 调用代理对象
通过代理对象调用方法,拦截器逻辑会自动执行:
// 调用接口代理
proxy.Add(1, 2);
proxy.Divide(6, 2);
try
{
proxy.Divide(6, 0);
}
catch { }
输出结果:
[15:30:00] 开始执行:Add
[15:30:00] 结束执行:Add
[15:30:01] 开始执行:Divide
[15:30:01] 结束执行:Divide
[15:30:02] 开始执行:Divide
[15:30:02] 方法 Divide 异常:除数不能为0
[15:30:02] 结束执行:Divide
三、高级用法
1. 获取方法参数与返回值
通过 IInvocation
对象可获取方法的详细信息:
public void Intercept(IInvocation invocation)
{
// 方法参数
var parameters = invocation.Arguments;
var paramStr = string.Join(", ", parameters);
Console.WriteLine($"参数:{paramStr}");
// 执行目标方法
invocation.Proceed();
// 方法返回值
var returnValue = invocation.ReturnValue;
Console.WriteLine($"返回值:{returnValue}");
}
2. 多个拦截器组合
支持为代理对象添加多个拦截器,按顺序执行:
// 定义第二个拦截器(性能计时)
public class TimingInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
invocation.Proceed();
stopwatch.Stop();
Console.WriteLine($"耗时:{stopwatch.ElapsedMilliseconds}ms");
}
}
// 组合拦截器(按数组顺序执行)
var proxy = generator.CreateInterfaceProxyWithTarget<ICalculator>(
new Calculator(),
new LogInterceptor(), // 第一个执行
new TimingInterceptor() // 第二个执行
);
3. 异步方法支持
需对异步方法进行特殊处理(等待 Task
完成后再执行后续逻辑):
public class AsyncLogInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
Console.WriteLine($"开始执行:{invocation.Method.Name}");
// 执行目标方法
invocation.Proceed();
// 判断是否为异步方法(返回 Task 或 Task<T>)
if (invocation.ReturnValue is Task task)
{
// 异步等待任务完成后执行后续逻辑
task.ContinueWith(t =>
{
Console.WriteLine($"异步方法结束:{invocation.Method.Name}");
});
}
else
{
// 同步方法逻辑
Console.WriteLine($"同步方法结束:{invocation.Method.Name}");
}
}
}
// 异步方法示例
public interface IDataService
{
Task<string> FetchDataAsync();
}
四、优缺点与适用场景
优点
灵活性高:运行时动态生成代理,支持动态添加 / 移除拦截器。
无侵入性:无需修改目标类代码,仅通过代理增强。
支持接口和类代理:适配不同场景(接口代理更推荐,避免类的继承限制)。
缺点
性能开销:运行时反射和动态代理生成会带来少量性能损耗(相比编译时织入)。
限制:类代理要求目标方法为
virtual
,且类不能是密封类(sealed
)。
适用场景
日志记录、性能监控、权限验证等横切逻辑。
依赖注入框架中的拦截器(如 Autofac 的
RegisterInterceptor
)。需要动态调整增强逻辑的场景(如根据配置启用 / 禁用拦截器)。
五、与其他 AOP 框架的对比
总结
Castle DynamicProxy
是 .NET 中成熟的运行时 AOP 框架,通过动态代理实现方法增强,适合需要灵活调整横切逻辑的场景。尽管存在少量性能开销,但其易用性和与 DI 框架的良好集成(如 Autofac、Unity)使其成为许多项目的首选 AOP 方案。在使用时,优先选择接口代理以避免类代理的限制(如 virtual
方法要求)。