mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-06 16:16:44 +08:00
refactor(cqrs): 缓存处理器接口反射元数据
- 优化 CqrsHandlerRegistrar 复用 supported handler interface 缓存 - 补充 registrar 静态缓存隔离与接口缓存复用回归测试 - 更新 cqrs-rewrite 跟踪与 trace 到 RP-048
This commit is contained in:
parent
5f3964d4c0
commit
110666d06b
@ -32,6 +32,7 @@ internal sealed class CqrsHandlerRegistrarTests
|
||||
|
||||
_container.Freeze();
|
||||
_context = new ArchitectureContext(_container);
|
||||
ClearRegistrarCaches();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -43,6 +44,7 @@ internal sealed class CqrsHandlerRegistrarTests
|
||||
_context = null;
|
||||
_container = null;
|
||||
DeterministicNotificationHandlerState.Reset();
|
||||
ClearRegistrarCaches();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -435,6 +437,123 @@ internal sealed class CqrsHandlerRegistrarTests
|
||||
|
||||
partiallyLoadableAssembly.Verify(static assembly => assembly.GetTypes(), Times.Once);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证同一 handler 类型跨容器重复注册时,会复用已筛选的 supported handler interface 列表,
|
||||
/// 而不是为每个容器重新执行接口反射分析。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void RegisterHandlers_Should_Cache_Supported_Handler_Interfaces_Across_Containers()
|
||||
{
|
||||
var supportedHandlerInterfacesCache = GetRegistrarCacheField("SupportedHandlerInterfacesCache");
|
||||
var firstHandlerType = typeof(AlphaDeterministicNotificationHandler);
|
||||
var secondHandlerType = typeof(ZetaDeterministicNotificationHandler);
|
||||
var handlerAssembly = new Mock<Assembly>();
|
||||
handlerAssembly
|
||||
.SetupGet(static assembly => assembly.FullName)
|
||||
.Returns("GFramework.Core.Tests.Cqrs.CachedHandlerInterfacesAssembly, Version=1.0.0.0");
|
||||
handlerAssembly
|
||||
.Setup(static assembly => assembly.GetTypes())
|
||||
.Returns([firstHandlerType, secondHandlerType]);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(GetSingleKeyCacheValue(supportedHandlerInterfacesCache, firstHandlerType), Is.Null);
|
||||
Assert.That(GetSingleKeyCacheValue(supportedHandlerInterfacesCache, secondHandlerType), Is.Null);
|
||||
});
|
||||
|
||||
var firstContainer = new MicrosoftDiContainer();
|
||||
var secondContainer = new MicrosoftDiContainer();
|
||||
|
||||
CqrsTestRuntime.RegisterHandlers(firstContainer, handlerAssembly.Object);
|
||||
var firstHandlerInterfaces =
|
||||
GetSingleKeyCacheValue(supportedHandlerInterfacesCache, firstHandlerType);
|
||||
var secondHandlerInterfaces =
|
||||
GetSingleKeyCacheValue(supportedHandlerInterfacesCache, secondHandlerType);
|
||||
|
||||
CqrsTestRuntime.RegisterHandlers(secondContainer, handlerAssembly.Object);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(firstHandlerInterfaces, Is.Not.Null);
|
||||
Assert.That(secondHandlerInterfaces, Is.Not.Null);
|
||||
Assert.That(
|
||||
GetSingleKeyCacheValue(supportedHandlerInterfacesCache, firstHandlerType),
|
||||
Is.SameAs(firstHandlerInterfaces));
|
||||
Assert.That(
|
||||
GetSingleKeyCacheValue(supportedHandlerInterfacesCache, secondHandlerType),
|
||||
Is.SameAs(secondHandlerInterfaces));
|
||||
});
|
||||
|
||||
handlerAssembly.Verify(static assembly => assembly.GetTypes(), Times.Once);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空本测试依赖的 registrar 静态缓存,避免跨用例共享进程级状态导致断言漂移。
|
||||
/// </summary>
|
||||
private static void ClearRegistrarCaches()
|
||||
{
|
||||
ClearCache(GetRegistrarCacheField("AssemblyMetadataCache"));
|
||||
ClearCache(GetRegistrarCacheField("RegistryActivationMetadataCache"));
|
||||
ClearCache(GetRegistrarCacheField("LoadableTypesCache"));
|
||||
ClearCache(GetRegistrarCacheField("SupportedHandlerInterfacesCache"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过反射读取 registrar 的静态缓存对象。
|
||||
/// </summary>
|
||||
private static object GetRegistrarCacheField(string fieldName)
|
||||
{
|
||||
var registrarType = GetRegistrarType();
|
||||
var field = registrarType.GetField(
|
||||
fieldName,
|
||||
BindingFlags.NonPublic | BindingFlags.Static);
|
||||
|
||||
Assert.That(field, Is.Not.Null, $"Missing registrar cache field {fieldName}.");
|
||||
|
||||
return field!.GetValue(null)
|
||||
?? throw new InvalidOperationException(
|
||||
$"Registrar cache field {fieldName} returned null.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空指定缓存对象。
|
||||
/// </summary>
|
||||
private static void ClearCache(object cache)
|
||||
{
|
||||
_ = InvokeInstanceMethod(cache, "Clear");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取单键缓存中当前保存的对象。
|
||||
/// </summary>
|
||||
private static object? GetSingleKeyCacheValue(object cache, Type key)
|
||||
{
|
||||
return InvokeInstanceMethod(cache, "GetValueOrDefaultForTesting", key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调用缓存对象上的实例方法。
|
||||
/// </summary>
|
||||
private static object? InvokeInstanceMethod(object target, string methodName, params object[] arguments)
|
||||
{
|
||||
var method = target.GetType().GetMethod(
|
||||
methodName,
|
||||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
|
||||
Assert.That(method, Is.Not.Null, $"Missing cache method {target.GetType().FullName}.{methodName}.");
|
||||
|
||||
return method!.Invoke(target, arguments);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 CQRS handler registrar 运行时类型。
|
||||
/// </summary>
|
||||
private static Type GetRegistrarType()
|
||||
{
|
||||
return typeof(CqrsReflectionFallbackAttribute).Assembly
|
||||
.GetType("GFramework.Cqrs.Internal.CqrsHandlerRegistrar", throwOnError: true)!;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -26,6 +26,11 @@ internal static class CqrsHandlerRegistrar
|
||||
private static readonly WeakKeyCache<Assembly, IReadOnlyList<Type>> LoadableTypesCache =
|
||||
new();
|
||||
|
||||
// 卸载安全的进程级缓存:同一 handler 类型跨容器重复注册时,
|
||||
// 复用已筛选且排序好的 supported handler interface 列表,避免重复执行 GetInterfaces()。
|
||||
private static readonly WeakKeyCache<Type, IReadOnlyList<Type>> SupportedHandlerInterfacesCache =
|
||||
new();
|
||||
|
||||
/// <summary>
|
||||
/// 扫描指定程序集并注册所有 CQRS 请求/通知/流式处理器。
|
||||
/// </summary>
|
||||
@ -163,11 +168,7 @@ internal static class CqrsHandlerRegistrar
|
||||
foreach (var implementationType in GetCandidateHandlerTypes(assembly, logger, reflectionFallbackMetadata)
|
||||
.Where(IsConcreteHandlerType))
|
||||
{
|
||||
var handlerInterfaces = implementationType
|
||||
.GetInterfaces()
|
||||
.Where(IsSupportedHandlerInterface)
|
||||
.OrderBy(GetTypeSortKey, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
var handlerInterfaces = GetSupportedHandlerInterfaces(implementationType);
|
||||
|
||||
if (handlerInterfaces.Count == 0)
|
||||
continue;
|
||||
@ -184,12 +185,30 @@ internal static class CqrsHandlerRegistrar
|
||||
// Request/notification handlers receive context injection before every dispatch.
|
||||
// Transient registration avoids sharing mutable Context across concurrent requests.
|
||||
services.AddTransient(handlerInterface, implementationType);
|
||||
logger.Debug(
|
||||
logger.Debug(
|
||||
$"Registered CQRS handler {implementationType.FullName} as {handlerInterface.FullName}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定实现类型上所有受支持的 CQRS handler 接口,并缓存筛选与排序结果。
|
||||
/// </summary>
|
||||
/// <param name="implementationType">要分析的处理器实现类型。</param>
|
||||
/// <returns>当前实现类型声明的受支持 handler 接口列表。</returns>
|
||||
private static IReadOnlyList<Type> GetSupportedHandlerInterfaces(Type implementationType)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(implementationType);
|
||||
|
||||
return SupportedHandlerInterfacesCache.GetOrAdd(
|
||||
implementationType,
|
||||
static key => key
|
||||
.GetInterfaces()
|
||||
.Where(IsSupportedHandlerInterface)
|
||||
.OrderBy(GetTypeSortKey, StringComparer.Ordinal)
|
||||
.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据生成器提供的 fallback 清单或整程序集扫描结果,获取本轮要注册的候选处理器类型。
|
||||
/// </summary>
|
||||
|
||||
@ -7,7 +7,7 @@ CQRS 迁移与收敛。
|
||||
|
||||
## 当前恢复点
|
||||
|
||||
- 恢复点编号:`CQRS-REWRITE-RP-047`
|
||||
- 恢复点编号:`CQRS-REWRITE-RP-048`
|
||||
- 当前阶段:`Phase 8`
|
||||
- 当前焦点:
|
||||
- 当前功能历史已归档,active 跟踪仅保留 `Phase 8` 主线的恢复入口
|
||||
@ -15,6 +15,7 @@ CQRS 迁移与收敛。
|
||||
- 已补充私有无参构造 generated registry 的回归测试,确保兼容现有生成器产物
|
||||
- 已补充 pointer 响应类型的 precise runtime type 生成,避免这类 handler 再退回程序集级 reflection fallback
|
||||
- 已收紧 function pointer 签名的可直接生成判定,仅在其返回值与参数类型都可安全引用时才走静态注册路径
|
||||
- 已为 registrar 的 reflection 注册路径补充 handler-interface 元数据缓存,减少跨容器重复注册时的 `GetInterfaces()` 反射
|
||||
- 中期上继续 `Phase 8` 主线:参考 `ai-libs/Mediator`,继续扩大 generator 覆盖,并选择下一个收益明确的 dispatch / invoker 反射收敛点
|
||||
|
||||
## 当前状态摘要
|
||||
@ -40,6 +41,10 @@ CQRS 迁移与收敛。
|
||||
- `CqrsHandlerRegistryGenerator` 现可为 pointer 类型递归重建 runtime type,并通过 `MakePointerType()` 生成精确 service type
|
||||
- function pointer 签名不再默认视为“可直接引用”;只有当返回值与每个参数类型都可从 generated registry 安全引用时,才允许直接生成
|
||||
- 含隐藏类型的 function pointer handler 仍会保留原有 fallback / 诊断路径,避免此次覆盖扩展误伤已有回退边界
|
||||
- `2026-04-20` 已完成一轮 registrar reflection 路径收敛:
|
||||
- `CqrsHandlerRegistrar` 现会按 `Type` 弱键缓存已筛选且排序好的 supported handler interface 列表
|
||||
- 同一 handler 类型跨容器重复注册时,不再重复执行 `GetInterfaces()` 与支持接口筛选
|
||||
- `GFramework.Cqrs.Tests` 已补充 registrar 静态缓存隔离与 supported interface 缓存复用回归
|
||||
- 当前主线优先级:
|
||||
- generator 覆盖面继续扩大
|
||||
- dispatch/invoker 反射占比继续下降
|
||||
@ -67,6 +72,9 @@ CQRS 迁移与收敛。
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore -p:RestoreFallbackFolders= -m:1 -nodeReuse:false --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests"`
|
||||
- 结果:通过
|
||||
- 备注:`14/14` 测试通过;本轮覆盖 pointer precise registration 与 function pointer fallback 边界
|
||||
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --no-restore -p:RestoreFallbackFolders= -m:1 -nodeReuse:false --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsHandlerRegistrarTests"`
|
||||
- 结果:通过
|
||||
- 备注:`10/10` 测试通过;本轮覆盖 registrar 的 supported handler interface 缓存
|
||||
|
||||
## 下一步
|
||||
|
||||
|
||||
@ -2,6 +2,16 @@
|
||||
|
||||
## 2026-04-20
|
||||
|
||||
### 阶段:registrar handler-interface 反射缓存(CQRS-REWRITE-RP-048)
|
||||
|
||||
- 已在 `CqrsHandlerRegistrar` 中新增按 `Type` 弱键缓存的 supported handler interface 元数据,reflection 注册路径现会复用已筛选且排序好的接口列表
|
||||
- 同一 handler 类型跨容器重复注册时,不再重复执行 `GetInterfaces()` 与支持接口筛选;缓存仍保持卸载安全,不会长期钉住 collectible 类型
|
||||
- `GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs` 已补充 registrar 静态缓存清理与 supported interface 缓存复用回归
|
||||
- 定向验证已通过:
|
||||
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --no-restore -p:RestoreFallbackFolders= -m:1 -nodeReuse:false --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsHandlerRegistrarTests"`
|
||||
- `10/10` passed
|
||||
- 当前沙箱限制 MSBuild named pipe,因此验证在提权环境下执行
|
||||
|
||||
### 阶段:pointer precise runtime type 覆盖扩展(CQRS-REWRITE-RP-047)
|
||||
|
||||
- 已在 `CqrsHandlerRegistryGenerator` 中补充 pointer 类型的 runtime type 递归建模与源码发射,precise registration 现可通过 `MakePointerType()` 还原隐藏 pointer 响应类型
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user