使用 Harmony 替代 Castle DynamicProxy 实现方案
需求背景
用户希望用 Harmony 替代 Castle DynamicProxy,主要原因:
- 无法拦截非虚方法 - Castle 的核心限制
- 拦截第三方库代码 - Castle 无法做到
- 更好的性能 - 减少代理调用开销
- 简化实现 - 减少适配器层
现有条件
✅ 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.cs | IMoMethodInvocation 适配器 |
DynamicProxy/Harmony/HarmonyInterceptorPatchManager.cs | Patch 管理器 |
DynamicProxy/Harmony/HarmonyInterceptorPatches.cs | Prefix/Postfix/Finalizer 实现 |
DynamicProxy/Harmony/HarmonyInterceptingWrapper.cs | DispatchProxy 包装器 |
修改文件
| 文件 | 修改内容 |
|---|---|
DynamicProxy/DynamicProxyExtensions.cs | 切换到 Harmony 实现 |
Modules/ModuleDynamicProxy.cs | 初始化 Harmony |
MoLibrary.DependencyInjection.csproj | 移除 Castle.Core 依赖(如完全替代) |
可删除文件(如完全替代 Castle)
| 文件 | 说明 |
|---|---|
DynamicProxy/ProxyGeneratorWithDI.cs | Castle 代理生成器 |
DynamicProxy/Abstract/MoAsyncDeterminationInterceptor.cs | Castle 适配器 |
DynamicProxy/Abstract/CastleAsyncMoInterceptorAdapter.cs | Castle 适配器 |
DynamicProxy/Abstract/CastleMoMethodInvocationAdapter*.cs | Castle 适配器 |
对比:Castle vs Harmony 实现
| 方面 | Castle 实现 | Harmony 实现 |
|---|---|---|
| 代理方式 | 创建代理对象 | 直接 patch 原方法 |
| 方法限制 | 需要 virtual/interface | 无限制 |
| 适配器层 | 3层适配器 | 1层适配器 |
| DI 集成 | 原生支持 | 通过 AsyncLocal |
| 性能 | 代理调用开销 | 几乎零开销 |
| 依赖 | Castle.Core + Castle.Core.AsyncInterceptor | HarmonyLib(已有) |
验证方案
- 单元测试:验证拦截器正确执行
- 集成测试:
- AuthorizationInterceptor 权限验证
- MoUnitOfWorkInterceptor 事务管理
- 性能测试:对比 Castle 和 Harmony 的调用开销
- 运行现有项目:确保现有功能不受影响
风险与注意事项
-
全局性影响:Harmony patch 一旦应用,所有调用都受影响
- 缓解:通过
AsyncLocal上下文判断是否需要拦截
- 缓解:通过
-
调试困难:Harmony patch 的调用栈可能不直观
- 缓解:添加详细日志
-
兼容性:某些 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.cs | Patch 配置选项 |
确认事项
- ✅ 完全移除 Castle DynamicProxy,不保留 fallback
- ✅ 提供通用 Patch API,支持任意方法拦截