diff --git a/GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs b/GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs
index 3e92281d..349c80f7 100644
--- a/GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs
+++ b/GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs
@@ -140,6 +140,31 @@ internal sealed class CqrsHandlerRegistrarTests
Is.EqualTo([typeof(GeneratedRegistryNotificationHandler)]));
}
+ ///
+ /// 验证 generated registry 使用私有无参构造器时,运行时仍可激活它并完成处理器注册。
+ ///
+ [Test]
+ public void RegisterHandlers_Should_Activate_Generated_Registry_With_Private_Parameterless_Constructor()
+ {
+ var generatedAssembly = new Mock();
+ generatedAssembly
+ .SetupGet(static assembly => assembly.FullName)
+ .Returns("GFramework.Core.Tests.Cqrs.PrivateGeneratedRegistryAssembly, Version=1.0.0.0");
+ generatedAssembly
+ .Setup(static assembly => assembly.GetCustomAttributes(typeof(CqrsHandlerRegistryAttribute), false))
+ .Returns([new CqrsHandlerRegistryAttribute(typeof(PrivateConstructorNotificationHandlerRegistry))]);
+
+ var container = new MicrosoftDiContainer();
+ CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object);
+ container.Freeze();
+
+ var handlers = container.GetAll>();
+
+ Assert.That(
+ handlers.Select(static handler => handler.GetType()),
+ Is.EqualTo([typeof(GeneratedRegistryNotificationHandler)]));
+ }
+
///
/// 验证当生成注册器元数据损坏时,运行时会记录告警并回退到反射扫描路径。
///
@@ -608,3 +633,33 @@ internal sealed class PartialGeneratedNotificationHandlerRegistry : ICqrsHandler
$"Registered CQRS handler {typeof(GeneratedRegistryNotificationHandler).FullName} as {typeof(INotificationHandler).FullName}.");
}
}
+
+///
+/// 模拟生成注册器使用私有无参构造器的场景,验证运行时仍可通过缓存工厂激活它。
+///
+internal sealed class PrivateConstructorNotificationHandlerRegistry : ICqrsHandlerRegistry
+{
+ ///
+ /// 初始化一个新的私有生成注册器实例。
+ ///
+ private PrivateConstructorNotificationHandlerRegistry()
+ {
+ }
+
+ ///
+ /// 将测试通知处理器注册到目标服务集合。
+ ///
+ /// 承载处理器映射的服务集合。
+ /// 用于记录注册诊断的日志器。
+ public void Register(IServiceCollection services, ILogger logger)
+ {
+ ArgumentNullException.ThrowIfNull(services);
+ ArgumentNullException.ThrowIfNull(logger);
+
+ services.AddTransient(
+ typeof(INotificationHandler),
+ typeof(GeneratedRegistryNotificationHandler));
+ logger.Debug(
+ $"Registered CQRS handler {typeof(GeneratedRegistryNotificationHandler).FullName} as {typeof(INotificationHandler).FullName}.");
+ }
+}
diff --git a/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs b/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs
index 031cb7e4..c9562b17 100644
--- a/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs
+++ b/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs
@@ -1,6 +1,7 @@
using GFramework.Core.Abstractions.Ioc;
using GFramework.Core.Abstractions.Logging;
using GFramework.Cqrs.Abstractions.Cqrs;
+using System.Reflection.Emit;
namespace GFramework.Cqrs.Internal;
@@ -323,7 +324,51 @@ internal static class CqrsHandlerRegistrar
: new RegistryActivationMetadata(
true,
false,
- () => (ICqrsHandlerRegistry)constructor.Invoke(null));
+ CreateRegistryFactory(registryType, constructor));
+ }
+
+ ///
+ /// 为生成注册器创建可复用的激活工厂,优先使用一次性编译的动态方法,
+ /// 避免后续每次命中缓存时仍走 的反射激活路径。
+ ///
+ /// 生成注册器类型。
+ /// 已解析的无参构造函数。
+ /// 可直接实例化注册器的工厂委托。
+ private static Func CreateRegistryFactory(
+ Type registryType,
+ ConstructorInfo constructor)
+ {
+ ArgumentNullException.ThrowIfNull(registryType);
+ ArgumentNullException.ThrowIfNull(constructor);
+
+ try
+ {
+ // 生成器产物通常是稳定的无参 registry;这里把构造反射收敛为一次性 IL 工厂,
+ // 这样同一 registry 类型在多个容器间复用缓存时不会重复付出 ConstructorInfo.Invoke 成本。
+ var dynamicMethod = new DynamicMethod(
+ $"Create_{registryType.Name}_CqrsHandlerRegistry",
+ typeof(ICqrsHandlerRegistry),
+ Type.EmptyTypes,
+ registryType.Module,
+ skipVisibility: true);
+ var il = dynamicMethod.GetILGenerator();
+ il.Emit(OpCodes.Newobj, constructor);
+
+ if (registryType.IsValueType)
+ {
+ il.Emit(OpCodes.Box, registryType);
+ }
+
+ il.Emit(OpCodes.Castclass, typeof(ICqrsHandlerRegistry));
+ il.Emit(OpCodes.Ret);
+
+ return (Func)dynamicMethod.CreateDelegate(typeof(Func));
+ }
+ catch
+ {
+ // 某些受限运行环境若不允许动态方法,仍保留原有的反射激活语义,避免阻塞 generated registry 路径。
+ return () => (ICqrsHandlerRegistry)constructor.Invoke(null);
+ }
}
///
diff --git a/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md b/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md
index c4c68a2f..7e4a4198 100644
--- a/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md
+++ b/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md
@@ -7,12 +7,13 @@ CQRS 迁移与收敛。
## 当前恢复点
-- 恢复点编号:`CQRS-REWRITE-RP-045`
+- 恢复点编号:`CQRS-REWRITE-RP-046`
- 当前阶段:`Phase 8`
- 当前焦点:
- 当前功能历史已归档,active 跟踪仅保留 `Phase 8` 主线的恢复入口
- - 已完成 `PR #253` 的 latest head review thread 复核,确认远端剩余 open thread 属于未关闭的 stale review 噪音
- - 中期上继续 `Phase 8` 主线:参考 `ai-libs/Mediator`,扩大 generator 覆盖、减少 dispatch/invoker 热路径反射,并继续收口 package / facade / 兼容层
+ - 已完成 generated registry 激活路径收敛:`CqrsHandlerRegistrar` 现优先复用缓存工厂委托,避免重复 `ConstructorInfo.Invoke`
+ - 已补充私有无参构造 generated registry 的回归测试,确保兼容现有生成器产物
+ - 中期上继续 `Phase 8` 主线:参考 `ai-libs/Mediator`,继续扩大 generator 覆盖,并选择下一个收益明确的 dispatch / invoker 反射收敛点
## 当前状态摘要
@@ -29,7 +30,11 @@ CQRS 迁移与收敛。
- `PR #253` 当前状态为 `CLOSED`
- latest reviewed commit 仍显示 `1` 条 open thread,但其内容针对的是已过时的 `Phase 7` 恢复建议
- 当前 active tracking / trace 已统一到 `Phase 8`,因此该 thread 不再作为当前主线阻塞项
-- 若 PR review 噪音已收敛,再回到以下主线优先级:
+- `2026-04-20` 已完成一轮冷启动反射收敛:
+ - generated registry 类型首次分析后,会缓存一个可复用的激活工厂,而不是在后续容器注册时重复走 `ConstructorInfo.Invoke`
+ - 若运行环境不允许动态方法,仍保留原有的反射激活回退,避免阻塞 generated registry 路径
+ - `GFramework.Cqrs.Tests` 已补充“私有无参构造 registry 仍可激活”的回归覆盖
+- 当前主线优先级:
- generator 覆盖面继续扩大
- dispatch/invoker 反射占比继续下降
- package / facade / 兼容层继续收口
@@ -50,9 +55,12 @@ CQRS 迁移与收敛。
- `RP-043` 之前的详细阶段记录、定向验证命令和阶段性决策均已移入主题内归档
- active 跟踪文件只保留当前恢复点、当前活跃事实、风险和下一步,避免 `boot` 在默认入口中重复扫描 1000+ 行历史 trace
+- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --no-restore -p:RestoreFallbackFolders= -m:1 -nodeReuse:false`
+ - 结果:通过
+ - 备注:`63/63` 测试通过;当前沙箱限制了 MSBuild named pipe,验证需在提权环境下运行
## 下一步
-1. 回到 `Phase 8` 主线,优先选择一个收益明确的反射收敛点继续推进
+1. 继续 `Phase 8` 主线,优先选择下一个收益明确的 dispatch / invoker 反射收敛点继续推进
2. 若继续文档主线,优先再扫 `docs/zh-CN/api-reference` 与教程入口页,补齐仍过时的 CQRS API / 命名空间表述
3. 若后续再出现新的 PR review 或 review thread 变化,再重新执行 `$gframework-pr-review` 作为独立验证步骤
diff --git a/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md b/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
index 65de190d..9353cdd9 100644
--- a/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
+++ b/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
@@ -2,12 +2,16 @@
## 2026-04-20
-### 阶段:PR #253 latest head review thread 复核(CQRS-REWRITE-RP-045)
+### 阶段:generated registry 激活反射收敛(CQRS-REWRITE-RP-046)
-- 已重新执行 `$gframework-pr-review`,确认 `PR #253` 当前状态为 `CLOSED`
-- latest reviewed commit 仍显示 `1` 条 open thread,但评论指向的是 tracking 文件中已经修正的旧版 `Phase 7` 恢复建议
-- 复核当前 active tracking / trace 后确认:默认 boot 入口已经统一到 `Phase 8`,该 thread 属于未关闭的 stale review 噪音
-- 当前功能主线恢复为 `Phase 8` 的 generator / dispatch / package 收口工作
+- 已在 `CqrsHandlerRegistrar` 中将 generated registry 的无参构造激活改为类型级缓存工厂
+- 默认路径优先使用一次性动态方法直接创建 registry,避免后续每次命中缓存仍走 `ConstructorInfo.Invoke`
+- 若运行环境不允许动态方法,则保留原有反射激活回退,确保 generated registry 路径不因运行时限制失效
+- 已补充“私有无参构造 generated registry 仍可激活”的回归测试,覆盖现有生成器产物兼容性
+- 定向验证已通过:
+ - `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --no-restore -p:RestoreFallbackFolders= -m:1 -nodeReuse:false`
+ - `63/63` passed
+ - 当前沙箱限制 MSBuild named pipe,因此验证在提权环境下执行
### Archive Context
@@ -18,6 +22,6 @@
### 当前下一步
-1. 回到 `Phase 8` 主线,优先选一个明确的反射缩减点继续推进
+1. 回到 `Phase 8` 主线,优先选一个明确的 dispatch / invoker 反射缩减点继续推进
2. 若继续文档主线,优先补齐 `docs/zh-CN/api-reference` 与教程入口页中仍过时的 CQRS API / 命名空间表述
3. 若后续 review thread 或 PR 状态再次变化,再重新执行 `$gframework-pr-review` 复核远端信号