跳到主要内容

使用 Harmony 替代 Castle DynamicProxy 实现方案

需求背景

用户希望用 Harmony 替代 Castle DynamicProxy,主要原因:

  1. 无法拦截非虚方法 - Castle 的核心限制
  2. 拦截第三方库代码 - Castle 无法做到
  3. 更好的性能 - 减少代理调用开销
  4. 简化实现 - 减少适配器层

现有条件

MoLibrary 已使用 Harmony - 参见 MoLibrary.Core/Module/BuilderWrapper/HarmonyPatchManager.cs

  • 已有 Harmony 依赖和使用模式
  • 用于 patch ASP.NET Core 生命周期方法

实现方案

核心设计:AsyncLocal + DispatchProxy 混合架构

┌─────────────────────────────────────────────────────────────┐
│ HarmonyInterceptorBridge (AsyncLocal<Context>) │
│ - 存储当前调用上下文 │
│ - 支持异步流隔离 │
└──────────────────────────────────────────────���──────────────┘

┌─────────────────────────────────────────────────────────────┐
│ HarmonyInterceptingWrapper (DispatchProxy) │
│ - 设置 AsyncLocal 上下文 │
│ - 解析 DI 服务获取拦截器实例 │
│ - 调用原方法(已被 Harmony patch) │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ Harmony Prefix/Postfix/Finalizer │
│ - 从 AsyncLocal 读取上下文 │
│ - 执行拦截器链 │
│ - 处理异步方法包装 │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ 原有 IMoInterceptor 接口保持不变 │
│ - AuthorizationInterceptor │
│ - MoUnitOfWorkInterceptor │
│ - 等等... │
└─────────────────────────────────────────────────────────────┘

关键解决方案

问题解决方案
Harmony patch 是静态方法,无法注入 DI使用 AsyncLocal<HarmonyInvocationContext> 传递 IServiceProvider
全局性 patch 影响所有调用DispatchProxy 包装器设置上下文,无上下文时跳过拦截
保持现有 IMoInterceptor 接口兼容创建 HarmonyMoMethodInvocation 适配器
异步方法处理Postfix 包装返回的 Task,在 continuation 中执行拦截器链

实现步骤

步骤 1: 创建 Harmony 拦截上下文

文件: MoLibrary.DependencyInjection/DynamicProxy/Harmony/HarmonyInterceptorContext.cs

public class HarmonyInvocationContext
{
public required IServiceProvider ServiceProvider { get; init; }
public required object TargetInstance { get; init; }
public required MethodInfo Method { get; init; }
public required object[] Arguments { get; init; }
public required IReadOnlyList<IMoInterceptor> Interceptors { get; init; }
public object? InterceptedResult { get; set; }
public bool SkipOriginal { get; set; }
}

public static class HarmonyInterceptorBridge
{
private static readonly AsyncLocal<HarmonyInvocationContext?> _currentContext = new();

public static HarmonyInvocationContext? Current
{
get => _currentContext.Value;
set => _currentContext.Value = value;
}
}

步骤 2: 创建 Harmony 方法调用适配器

文件: MoLibrary.DependencyInjection/DynamicProxy/Harmony/HarmonyMoMethodInvocation.cs

保持与现有 IMoMethodInvocation 接口兼容,使现有拦截器无需修改。

步骤 3: 创建 Harmony Patch 管理器

文件: MoLibrary.DependencyInjection/DynamicProxy/Harmony/HarmonyInterceptorPatchManager.cs

  • 管理已 patch 的方法(避免重复 patch)
  • 提供 PatchMethod() API
  • 区分同步/异步方法使用不同的 patch 策略

步骤 4: 创建 DispatchProxy 包装器

文件: MoLibrary.DependencyInjection/DynamicProxy/Harmony/HarmonyInterceptingWrapper.cs

  • 继承 DispatchProxy
  • Invoke 方法中设置 AsyncLocal 上下文
  • 从 DI 解析拦截器实例

步骤 5: 修改 DynamicProxyExtensions

文件: MoLibrary.DependencyInjection/DynamicProxy/DynamicProxyExtensions.cs

  • 修改 ApplyInterceptors 方法
  • 对于 interface 类型:继续使用 DispatchProxy(无需 Castle)
  • 对于 class 类型:使用 Harmony patch 非虚方法

步骤 6: 更新模块配置

文件: MoLibrary.DependencyInjection/Modules/ModuleDynamicProxy.cs

  • 初始化 Harmony 实例
  • 在 PostConfig 阶段应用拦截器

文件清单

新增文件

文件说明
DynamicProxy/Harmony/HarmonyInvocationContext.cs上下文和 AsyncLocal 桥接
DynamicProxy/Harmony/HarmonyMoMethodInvocation.csIMoMethodInvocation 适配器
DynamicProxy/Harmony/HarmonyInterceptorPatchManager.csPatch 管理器
DynamicProxy/Harmony/HarmonyInterceptorPatches.csPrefix/Postfix/Finalizer 实现
DynamicProxy/Harmony/HarmonyInterceptingWrapper.csDispatchProxy 包装器

修改文件

文件修改内容
DynamicProxy/DynamicProxyExtensions.cs切换到 Harmony 实现
Modules/ModuleDynamicProxy.cs初始化 Harmony
MoLibrary.DependencyInjection.csproj移除 Castle.Core 依赖(如完全替代)

可删除文件(如完全替代 Castle)

文件说明
DynamicProxy/ProxyGeneratorWithDI.csCastle 代理生成器
DynamicProxy/Abstract/MoAsyncDeterminationInterceptor.csCastle 适配器
DynamicProxy/Abstract/CastleAsyncMoInterceptorAdapter.csCastle 适配器
DynamicProxy/Abstract/CastleMoMethodInvocationAdapter*.csCastle 适配器

对比:Castle vs Harmony 实现

方面Castle 实现Harmony 实现
代理方式创建代理对象直接 patch 原方法
方法限制需要 virtual/interface无限制
适配器层3层适配器1层适配器
DI 集成原生支持通过 AsyncLocal
性能代理调用开销几乎零开销
依赖Castle.Core + Castle.Core.AsyncInterceptorHarmonyLib(已有)

验证方案

  1. 单元测试:验证拦截器正确执行
  2. 集成测试
    • AuthorizationInterceptor 权限验证
    • MoUnitOfWorkInterceptor 事务管理
  3. 性能测试:对比 Castle 和 Harmony 的调用开销
  4. 运行现有项目:确保现有功能不受影响

风险与注意事项

  1. 全局性影响:Harmony patch 一旦应用,所有调用都受影响

    • 缓解:通过 AsyncLocal 上下文判断是否需要拦截
  2. 调试困难:Harmony patch 的调用栈可能不直观

    • 缓解:添加详细日志
  3. 兼容性:某些 AOT 环境可能不支持

    • 缓解:保留 Castle 作为 fallback 选项(可选)

通用 Patch API 设计

目标

提供通用 API 允许用户 patch 任意方法(包括第三方库代码)

API 设计

// 1. 通过特性注册第三方方法拦截
services.AddMoInterceptor<MyInterceptor>()
.PatchMethod<ThirdPartyClass>("MethodName")
.PatchMethod(typeof(ThirdPartyClass).GetMethod("MethodName2"));

// 2. 直接 Patch API
services.AddHarmonyPatch<ThirdPartyClass>(options =>
{
options.PatchMethod("MethodName", new HarmonyMethod(typeof(MyPatches), "Prefix"));
options.PatchAllMethods(m => m.Name.StartsWith("Do")); // 批量 patch
});

// 3. 声明式 Patch(类似现有 HarmonyPatchManager)
[HarmonyPatch(typeof(ThirdPartyClass), "MethodName")]
public class ThirdPartyMethodPatch
{
[HarmonyPrefix]
static bool Prefix(object __instance, ref bool __runOriginal)
{
var context = HarmonyInterceptorBridge.Current;
if (context == null) return true; // 无上下文,不拦截
// 执行拦截逻辑...
return true;
}
}

新增文件

文件说明
DynamicProxy/Harmony/HarmonyPatchBuilder.cs通用 Patch 构建器
DynamicProxy/Harmony/HarmonyPatchOptions.csPatch 配置选项

确认事项

  • ✅ 完全移除 Castle DynamicProxy,不保留 fallback
  • ✅ 提供通用 Patch API,支持任意方法拦截