mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-07 00:39:00 +08:00
refactor(cqrs): 收敛生成注册器激活反射路径
- 优化 generated registry 激活流程,使用缓存工厂委托优先替代 ConstructorInfo.Invoke\n- 补充私有无参构造 registry 的回归测试,保持生成器产物兼容性\n- 更新 CQRS ai-plan 恢复点与验证记录,指向新的 Phase 8 下一步
This commit is contained in:
parent
a926748def
commit
7cf0a75568
@ -140,6 +140,31 @@ internal sealed class CqrsHandlerRegistrarTests
|
|||||||
Is.EqualTo([typeof(GeneratedRegistryNotificationHandler)]));
|
Is.EqualTo([typeof(GeneratedRegistryNotificationHandler)]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证 generated registry 使用私有无参构造器时,运行时仍可激活它并完成处理器注册。
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void RegisterHandlers_Should_Activate_Generated_Registry_With_Private_Parameterless_Constructor()
|
||||||
|
{
|
||||||
|
var generatedAssembly = new Mock<Assembly>();
|
||||||
|
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<INotificationHandler<GeneratedRegistryNotification>>();
|
||||||
|
|
||||||
|
Assert.That(
|
||||||
|
handlers.Select(static handler => handler.GetType()),
|
||||||
|
Is.EqualTo([typeof(GeneratedRegistryNotificationHandler)]));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 验证当生成注册器元数据损坏时,运行时会记录告警并回退到反射扫描路径。
|
/// 验证当生成注册器元数据损坏时,运行时会记录告警并回退到反射扫描路径。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -608,3 +633,33 @@ internal sealed class PartialGeneratedNotificationHandlerRegistry : ICqrsHandler
|
|||||||
$"Registered CQRS handler {typeof(GeneratedRegistryNotificationHandler).FullName} as {typeof(INotificationHandler<GeneratedRegistryNotification>).FullName}.");
|
$"Registered CQRS handler {typeof(GeneratedRegistryNotificationHandler).FullName} as {typeof(INotificationHandler<GeneratedRegistryNotification>).FullName}.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 模拟生成注册器使用私有无参构造器的场景,验证运行时仍可通过缓存工厂激活它。
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class PrivateConstructorNotificationHandlerRegistry : ICqrsHandlerRegistry
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 初始化一个新的私有生成注册器实例。
|
||||||
|
/// </summary>
|
||||||
|
private PrivateConstructorNotificationHandlerRegistry()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将测试通知处理器注册到目标服务集合。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="services">承载处理器映射的服务集合。</param>
|
||||||
|
/// <param name="logger">用于记录注册诊断的日志器。</param>
|
||||||
|
public void Register(IServiceCollection services, ILogger logger)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(services);
|
||||||
|
ArgumentNullException.ThrowIfNull(logger);
|
||||||
|
|
||||||
|
services.AddTransient(
|
||||||
|
typeof(INotificationHandler<GeneratedRegistryNotification>),
|
||||||
|
typeof(GeneratedRegistryNotificationHandler));
|
||||||
|
logger.Debug(
|
||||||
|
$"Registered CQRS handler {typeof(GeneratedRegistryNotificationHandler).FullName} as {typeof(INotificationHandler<GeneratedRegistryNotification>).FullName}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
using GFramework.Core.Abstractions.Ioc;
|
using GFramework.Core.Abstractions.Ioc;
|
||||||
using GFramework.Core.Abstractions.Logging;
|
using GFramework.Core.Abstractions.Logging;
|
||||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||||
|
using System.Reflection.Emit;
|
||||||
|
|
||||||
namespace GFramework.Cqrs.Internal;
|
namespace GFramework.Cqrs.Internal;
|
||||||
|
|
||||||
@ -323,7 +324,51 @@ internal static class CqrsHandlerRegistrar
|
|||||||
: new RegistryActivationMetadata(
|
: new RegistryActivationMetadata(
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
() => (ICqrsHandlerRegistry)constructor.Invoke(null));
|
CreateRegistryFactory(registryType, constructor));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 为生成注册器创建可复用的激活工厂,优先使用一次性编译的动态方法,
|
||||||
|
/// 避免后续每次命中缓存时仍走 <see cref="ConstructorInfo" /> 的反射激活路径。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="registryType">生成注册器类型。</param>
|
||||||
|
/// <param name="constructor">已解析的无参构造函数。</param>
|
||||||
|
/// <returns>可直接实例化注册器的工厂委托。</returns>
|
||||||
|
private static Func<ICqrsHandlerRegistry> 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<ICqrsHandlerRegistry>)dynamicMethod.CreateDelegate(typeof(Func<ICqrsHandlerRegistry>));
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 某些受限运行环境若不允许动态方法,仍保留原有的反射激活语义,避免阻塞 generated registry 路径。
|
||||||
|
return () => (ICqrsHandlerRegistry)constructor.Invoke(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -7,12 +7,13 @@ CQRS 迁移与收敛。
|
|||||||
|
|
||||||
## 当前恢复点
|
## 当前恢复点
|
||||||
|
|
||||||
- 恢复点编号:`CQRS-REWRITE-RP-045`
|
- 恢复点编号:`CQRS-REWRITE-RP-046`
|
||||||
- 当前阶段:`Phase 8`
|
- 当前阶段:`Phase 8`
|
||||||
- 当前焦点:
|
- 当前焦点:
|
||||||
- 当前功能历史已归档,active 跟踪仅保留 `Phase 8` 主线的恢复入口
|
- 当前功能历史已归档,active 跟踪仅保留 `Phase 8` 主线的恢复入口
|
||||||
- 已完成 `PR #253` 的 latest head review thread 复核,确认远端剩余 open thread 属于未关闭的 stale review 噪音
|
- 已完成 generated registry 激活路径收敛:`CqrsHandlerRegistrar` 现优先复用缓存工厂委托,避免重复 `ConstructorInfo.Invoke`
|
||||||
- 中期上继续 `Phase 8` 主线:参考 `ai-libs/Mediator`,扩大 generator 覆盖、减少 dispatch/invoker 热路径反射,并继续收口 package / facade / 兼容层
|
- 已补充私有无参构造 generated registry 的回归测试,确保兼容现有生成器产物
|
||||||
|
- 中期上继续 `Phase 8` 主线:参考 `ai-libs/Mediator`,继续扩大 generator 覆盖,并选择下一个收益明确的 dispatch / invoker 反射收敛点
|
||||||
|
|
||||||
## 当前状态摘要
|
## 当前状态摘要
|
||||||
|
|
||||||
@ -29,7 +30,11 @@ CQRS 迁移与收敛。
|
|||||||
- `PR #253` 当前状态为 `CLOSED`
|
- `PR #253` 当前状态为 `CLOSED`
|
||||||
- latest reviewed commit 仍显示 `1` 条 open thread,但其内容针对的是已过时的 `Phase 7` 恢复建议
|
- latest reviewed commit 仍显示 `1` 条 open thread,但其内容针对的是已过时的 `Phase 7` 恢复建议
|
||||||
- 当前 active tracking / trace 已统一到 `Phase 8`,因此该 thread 不再作为当前主线阻塞项
|
- 当前 active tracking / trace 已统一到 `Phase 8`,因此该 thread 不再作为当前主线阻塞项
|
||||||
- 若 PR review 噪音已收敛,再回到以下主线优先级:
|
- `2026-04-20` 已完成一轮冷启动反射收敛:
|
||||||
|
- generated registry 类型首次分析后,会缓存一个可复用的激活工厂,而不是在后续容器注册时重复走 `ConstructorInfo.Invoke`
|
||||||
|
- 若运行环境不允许动态方法,仍保留原有的反射激活回退,避免阻塞 generated registry 路径
|
||||||
|
- `GFramework.Cqrs.Tests` 已补充“私有无参构造 registry 仍可激活”的回归覆盖
|
||||||
|
- 当前主线优先级:
|
||||||
- generator 覆盖面继续扩大
|
- generator 覆盖面继续扩大
|
||||||
- dispatch/invoker 反射占比继续下降
|
- dispatch/invoker 反射占比继续下降
|
||||||
- package / facade / 兼容层继续收口
|
- package / facade / 兼容层继续收口
|
||||||
@ -50,9 +55,12 @@ CQRS 迁移与收敛。
|
|||||||
|
|
||||||
- `RP-043` 之前的详细阶段记录、定向验证命令和阶段性决策均已移入主题内归档
|
- `RP-043` 之前的详细阶段记录、定向验证命令和阶段性决策均已移入主题内归档
|
||||||
- active 跟踪文件只保留当前恢复点、当前活跃事实、风险和下一步,避免 `boot` 在默认入口中重复扫描 1000+ 行历史 trace
|
- 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 / 命名空间表述
|
2. 若继续文档主线,优先再扫 `docs/zh-CN/api-reference` 与教程入口页,补齐仍过时的 CQRS API / 命名空间表述
|
||||||
3. 若后续再出现新的 PR review 或 review thread 变化,再重新执行 `$gframework-pr-review` 作为独立验证步骤
|
3. 若后续再出现新的 PR review 或 review thread 变化,再重新执行 `$gframework-pr-review` 作为独立验证步骤
|
||||||
|
|||||||
@ -2,12 +2,16 @@
|
|||||||
|
|
||||||
## 2026-04-20
|
## 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`
|
- 已在 `CqrsHandlerRegistrar` 中将 generated registry 的无参构造激活改为类型级缓存工厂
|
||||||
- latest reviewed commit 仍显示 `1` 条 open thread,但评论指向的是 tracking 文件中已经修正的旧版 `Phase 7` 恢复建议
|
- 默认路径优先使用一次性动态方法直接创建 registry,避免后续每次命中缓存仍走 `ConstructorInfo.Invoke`
|
||||||
- 复核当前 active tracking / trace 后确认:默认 boot 入口已经统一到 `Phase 8`,该 thread 属于未关闭的 stale review 噪音
|
- 若运行环境不允许动态方法,则保留原有反射激活回退,确保 generated registry 路径不因运行时限制失效
|
||||||
- 当前功能主线恢复为 `Phase 8` 的 generator / dispatch / package 收口工作
|
- 已补充“私有无参构造 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
|
### Archive Context
|
||||||
|
|
||||||
@ -18,6 +22,6 @@
|
|||||||
|
|
||||||
### 当前下一步
|
### 当前下一步
|
||||||
|
|
||||||
1. 回到 `Phase 8` 主线,优先选一个明确的反射缩减点继续推进
|
1. 回到 `Phase 8` 主线,优先选一个明确的 dispatch / invoker 反射缩减点继续推进
|
||||||
2. 若继续文档主线,优先补齐 `docs/zh-CN/api-reference` 与教程入口页中仍过时的 CQRS API / 命名空间表述
|
2. 若继续文档主线,优先补齐 `docs/zh-CN/api-reference` 与教程入口页中仍过时的 CQRS API / 命名空间表述
|
||||||
3. 若后续 review thread 或 PR 状态再次变化,再重新执行 `$gframework-pr-review` 复核远端信号
|
3. 若后续 review thread 或 PR 状态再次变化,再重新执行 `$gframework-pr-review` 复核远端信号
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user