动态重载示例
本页展示如何实现配置的动态重载(热更新)。
基本重载
文件变更重载
csharp
var cfg = new CfgBuilder()
.AddJson("config.json", level: 0, writeable: false, reloadOnChange: true)
.Build();
// 监听变更
cfg.ConfigChanges.Subscribe(e =>
{
Console.WriteLine("配置已更新:");
foreach (var (key, change) in e.Changes)
{
Console.WriteLine($" [{change.Type}] {key}: {change.OldValue} -> {change.NewValue}");
}
});远程配置重载
csharp
var cfg = new CfgBuilder()
.AddSource(new ConsulCfgSource("http://consul:8500", "myapp/config", level: 10, writeable: false, watch: true))
.Build();
cfg.ConfigChanges.Subscribe(e =>
{
Console.WriteLine("Consul 配置已更新");
});变更事件处理
详细变更信息
csharp
cfg.ConfigChanges.Subscribe(e =>
{
Console.WriteLine($"配置变更时间: {e.Timestamp}");
foreach (var (key, change) in e.Changes)
{
Console.WriteLine($" [{change.Type}] {key}");
Console.WriteLine($" 旧值: {change.OldValue ?? "(null)"}");
Console.WriteLine($" 新值: {change.NewValue ?? "(null)"}");
}
});按键过滤
csharp
cfg.ConfigChanges.Subscribe(e =>
{
// 只处理数据库配置变更
var dbChanges = e.Changes.Where(c => c.Key.StartsWith("Database:"));
if (dbChanges.Any())
{
Console.WriteLine("数据库配置已更新,需要重新连接");
ReconnectDatabase();
}
});使用 Rx 操作符
csharp
using System.Reactive.Linq;
// 防抖处理
cfg.ConfigChanges
.Throttle(TimeSpan.FromSeconds(1))
.Subscribe(e =>
{
Console.WriteLine("配置已更新(防抖后)");
});
// 只关注特定键
cfg.ConfigChanges
.Where(e => e.Changes.ContainsKey("App:Name"))
.Subscribe(e =>
{
var change = e.Changes["App:Name"];
Console.WriteLine($"应用名称已更改: {change.OldValue} -> {change.NewValue}");
});与 IOptionsMonitor 集成
自动更新
csharp
public class ConfigurableService : IDisposable
{
private readonly IOptionsMonitor<ServiceOptions> _options;
private readonly IDisposable _changeListener;
private ServiceOptions _currentOptions;
public ConfigurableService(IOptionsMonitor<ServiceOptions> options)
{
_options = options;
_currentOptions = options.CurrentValue;
_changeListener = _options.OnChange(OnOptionsChanged);
}
private void OnOptionsChanged(ServiceOptions newOptions)
{
Console.WriteLine("服务配置已更新");
// 比较变更
if (newOptions.Timeout != _currentOptions.Timeout)
{
Console.WriteLine($"超时时间: {_currentOptions.Timeout} -> {newOptions.Timeout}");
}
_currentOptions = newOptions;
// 应用新配置
ApplyConfiguration(newOptions);
}
private void ApplyConfiguration(ServiceOptions options)
{
// 应用配置变更
}
public void Dispose() => _changeListener?.Dispose();
}后台服务
csharp
public class DynamicConfigService : BackgroundService
{
private readonly IOptionsMonitor<WorkerOptions> _options;
private readonly ILogger<DynamicConfigService> _logger;
public DynamicConfigService(
IOptionsMonitor<WorkerOptions> options,
ILogger<DynamicConfigService> logger)
{
_options = options;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var options = _options.CurrentValue;
_logger.LogInformation("执行任务,间隔: {Interval}ms", options.Interval);
// 执行工作...
await Task.Delay(options.Interval, stoppingToken);
}
}
}层级覆盖感知
动态重载会正确处理层级覆盖:
csharp
var cfg = new CfgBuilder()
.AddJson("config.json", level: 0, writeable: false, reloadOnChange: true)
.AddJson("config.local.json", level: 1, writeable: false, reloadOnChange: true)
.Build();
// 假设两个文件都有 "Timeout" 配置
// config.json: Timeout = 30
// config.local.json: Timeout = 60
// 最终值: 60 (level 1 覆盖 level 0)
cfg.ConfigChanges.Subscribe(e =>
{
// 只有当最终合并值真正变化时才会触发
// 如果 config.json 的 Timeout 从 30 改为 45,
// 但 config.local.json 仍然是 60,
// 则不会触发变更通知(因为最终值仍是 60)
});可写配置的动态重载
csharp
var cfg = new CfgBuilder()
.AddJson("config.json", level: 0, writeable: true, isPrimaryWriter: true, reloadOnChange: true)
.Build();
// 修改配置
cfg.Set("App:Name", "NewName");
await cfg.SaveAsync();
// 文件变更会触发重载,但由于是自己的修改,
// 系统会智能处理避免重复通知完整示例
csharp
var builder = WebApplication.CreateBuilder(args);
// 配置动态重载
builder.Services.AddApqCfg(cfg => cfg
.AddJson("config.json", level: 0, writeable: false, reloadOnChange: true)
.AddJson($"config.{builder.Environment.EnvironmentName}.json", level: 1, writeable: false, optional: true, reloadOnChange: true)
.AddSource(new ConsulCfgSource("http://consul:8500", "myapp/config", level: 10, writeable: false, watch: true, optional: true))
.AddEnvironmentVariables(level: 20, prefix: "APP_"));
// 配置选项
builder.Services.AddOptions<AppOptions>()
.Bind(builder.Configuration.GetSection("App"));
// 注册配置监听服务
builder.Services.AddHostedService<ConfigWatcherService>();
var app = builder.Build();
// 获取配置实例并监听变更
var cfg = app.Services.GetRequiredService<ICfgRoot>();
cfg.ConfigChanges.Subscribe(e =>
{
app.Logger.LogInformation("配置已更新: {Keys}",
string.Join(", ", e.Changes.Keys));
});
app.Run();配置监听服务示例
csharp
public class ConfigWatcherService : IHostedService, IDisposable
{
private readonly ICfgRoot _cfg;
private readonly ILogger<ConfigWatcherService> _logger;
private IDisposable? _subscription;
public ConfigWatcherService(ICfgRoot cfg, ILogger<ConfigWatcherService> logger)
{
_cfg = cfg;
_logger = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_subscription = _cfg.ConfigChanges.Subscribe(OnConfigChanged);
_logger.LogInformation("配置监听服务已启动");
return Task.CompletedTask;
}
private void OnConfigChanged(ConfigChangeEvent e)
{
_logger.LogInformation("检测到配置变更,共 {Count} 项", e.Changes.Count);
foreach (var (key, change) in e.Changes)
{
_logger.LogInformation(" [{Type}] {Key}: {OldValue} -> {NewValue}",
change.Type, key, change.OldValue ?? "(null)", change.NewValue ?? "(null)");
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
_subscription?.Dispose();
_logger.LogInformation("配置监听服务已停止");
return Task.CompletedTask;
}
public void Dispose() => _subscription?.Dispose();
}