Administrator
发布于 2025-08-08 / 8 阅读
0
0

dotnet动态代理

Rougamo.Fody

Rougamo.Fody 是一个基于 .NETAOP(面向切面编程)库,它借助 Fody 这个代码编织工具,在编译时将 AOP 逻辑植入入选定的方法中,实现诸如日志记录、异常处理、性能监控等横切关注点的功能。与运行时通过反射实现的 AOP 框架(如 Castle DynamicProxy)相比,它的优势在于无运行时性能损耗,因为所有代码注入都在编译阶段完成。

一、核心特性

  1. 编译时织入:通过 Fody 在编译时修改 IL 代码,将 AOP 逻辑直接嵌入目标方法,避免反射带来的性能开销。

  2. 特性驱动:通过自定义特性(Attribute)标记需要增强的方法,使用简单直观。

  3. 支持多种切入点:可在方法执行前(Before)、执行后(After)、抛出异常时(OnException)插入逻辑。

  4. 低侵入性:无需修改原有业务代码结构,仅通过特性标记即可实现增强。

二、基本使用步骤

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 的自定义特性,重写需要的方法(BeforeAfterOnException 等):

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;
}

切面中的 BeforeAfter 会正确对应异步方法的执行前后(等待 await 完成后触发 After)。

四、优势与适用场景

  1. 性能优异:编译时织入,无运行时反射开销,适合性能敏感场景。

  2. 使用简单:通过特性标记即可,无需复杂的配置或接口实现。

  3. 适用场景:

    • 日志记录、性能计时(如接口响应时间)。

    • 异常统一处理(如全局异常捕获与上报)。

    • 权限验证、缓存控制等横切关注点。

五、注意事项

  1. 调试体验:由于代码在编译时被修改,调试时看到的源代码与实际执行的 IL 代码可能存在差异(但通常不影响断点调试)。

  2. 版本兼容性:需确保 Rougamo.FodyFody 与 .NET 版本兼容(建议使用最新稳定版)。

  3. 第三方库方法:无法增强外部库中的方法(仅对当前项目编译的代码生效)。

总结

Rougamo.Fody 是一个轻量、高效的 AOP 框架,通过编译时织入实现无性能损耗的方法增强。它适合需要在 .NET 项目中实现日志、异常处理等横切逻辑,且对性能有要求的场景。相比运行时 AOP 框架,它的侵入性更低,性能更优,是 .NET 生态中 AOP 方案的良好选择。

Castle DynamicProxy

Castle DynamicProxy 是 .NET 生态中一款经典的运行时 AOP(面向切面编程)框架,它通过动态生成代理类的方式,在不修改原始代码的前提下为方法添加额外逻辑(如日志、缓存、事务等)。与编译时织入的 AOP 框架(如 Rougamo.Fody)不同,它的代理类在运行时动态创建,灵活性更高,但会带来少量反射开销。

一、核心原理

Castle DynamicProxy 的核心是代理模式

  1. 为目标类(或接口)动态动态生成一个代理类(继承自目标类或实现目标接口)。

  2. 代理类中包含目标对象的引用,并在目标方法执行前后插入自定义逻辑(如 BeforeAfter)。

  3. 客户端通过代理类调用方法,间接执行目标对象的逻辑,同时触发增强逻辑。

二、基本使用步骤

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();
}

四、优缺点与适用场景

优点

  1. 灵活性高:运行时动态生成代理,支持动态添加 / 移除拦截器。

  2. 无侵入性:无需修改目标类代码,仅通过代理增强。

  3. 支持接口和类代理:适配不同场景(接口代理更推荐,避免类的继承限制)。

缺点

  1. 性能开销:运行时反射和动态代理生成会带来少量性能损耗(相比编译时织入)。

  2. 限制:类代理要求目标方法为 virtual,且类不能是密封类(sealed)。

适用场景

  • 日志记录、性能监控、权限验证等横切逻辑。

  • 依赖注入框架中的拦截器(如 Autofac 的 RegisterInterceptor)。

  • 需要动态调整增强逻辑的场景(如根据配置启用 / 禁用拦截器)。

五、与其他 AOP 框架的对比

框架

实现方式

性能

灵活性

适用场景

Castle DynamicProxy

运行时动态代理

中等(有反射开销)

动态增强、DI 集成

Rougamo.Fody

编译时织入

优(无运行时开销)

性能敏感场景、静态增强

PostSharp

编译时织入

复杂 AOP 需求(收费)

总结

Castle DynamicProxy 是 .NET 中成熟的运行时 AOP 框架,通过动态代理实现方法增强,适合需要灵活调整横切逻辑的场景。尽管存在少量性能开销,但其易用性和与 DI 框架的良好集成(如 Autofac、Unity)使其成为许多项目的首选 AOP 方案。在使用时,优先选择接口代理以避免类代理的限制(如 virtual 方法要求)。


评论