二、内部处理机制解析
1. 系统启动阶段,依赖注入
上一节的例子中涉及到了三个接口IOptions、IOptionsSnapshot和IOptionsMonitor,那么就从这三个接口说起。既然Options模式是通过这三个接口的泛型方式注入提供服务的,那么在这之前系统就需要将它们对应的实现注入到依赖注入容器中。这发生在系统启动阶段创建IHost的时候,这时候HostBuilder的Build方法中调用了一个services.AddOptions()方法,这个方法定义在OptionsServiceCollectionExtensions中,代码如下:
public static class OptionsServiceCollectionExtensions { public static IServiceCollection AddOptions(this IServiceCollection services) { if (services == null) { throw new ArgumentNullException(nameof(services)); } services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>))); services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>))); services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitor<>), typeof(OptionsMonitor<>))); services.TryAdd(ServiceDescriptor.Transient(typeof(IOptionsFactory<>), typeof(OptionsFactory<>))); services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitorCache<>), typeof(OptionsCache<>))); return services; } public static IServiceCollection Configure<TOptions>(this IServiceCollection services, Action<TOptions> configureOptions) where TOptions : class => services.Configure(Options.Options.DefaultName, configureOptions); public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, Action<TOptions> configureOptions) where TOptions : class { //省略非空验证代码 services.AddOptions(); services.AddSingleton<IConfigureOptions<TOptions>>(new ConfigureNamedOptions<TOptions>(name, configureOptions)); return services; } public static IServiceCollection ConfigureAll<TOptions>(this IServiceCollection services, Action<TOptions> configureOptions) where TOptions : class => services.Configure(name: null, configureOptions: configureOptions); //省略部分代码 }
可见这个AddOptions方法的作用就是进行服务注入,IOptions<>、IOptionsSnapshot<>对应的实现是OptionsManager<>,只是分别采用了Singleton和Scoped两种生命周期模式,IOptionsMonitor<>对应的实现是OptionsMonitor<>,同样为Singleton模式,这也验证了上一节例子中的猜想。除了上面提到的三个接口外,还有IOptionsFactory<>和IOptionsMonitorCache<>两个接口,这也是Options模式中非常重要的两个组成部分,接下来的内容中会用到。
另外的两个Configure方法就是上一节例子中用到的将具体的Theme注册到Options中的方法了。二者的区别就是是否为配置的option命名,而第一个Configure方法就未命名的方法,通过上面的代码可知它实际上是传入了一个默认的Options.Options.DefaultName作为名称,这个默认值是一个空字符串,也就是说,未命名的Option相当于是被命名为空字符串。最终都是按照已命名的方式也就是第二个Configure方法进行处理。还有一个ConfigureAll方法,它是传入了一个null作为Option的名称,也是交由第二个Configure处理。
在第二个Configure方法中仍调用了一次AddOptions方法,然后才是将具体的类型进行注入。AddOptions方法中采用的都是TryAdd方法进行注入,已被注入的不会被再次注入。接下来注册了一个IConfigureOptions<TOptions>接口,对应的实现是ConfigureNamedOptions<TOptions>(name, configureOptions),它的代码如下:
public class ConfigureNamedOptions<TOptions> : IConfigureNamedOptions<TOptions> where TOptions : class { public ConfigureNamedOptions(string name, Action<TOptions> action) { Name = name; Action = action; } public string Name { get; } public Action<TOptions> Action { get; } public virtual void Configure(string name, TOptions options) { if (options == null) { throw new ArgumentNullException(nameof(options)); } // Null name is used to configure all named options. if (Name == null || name == Name) { Action?.Invoke(options); } } public void Configure(TOptions options) => Configure(Options.DefaultName, options); }
它在构造方法中存储了配置的名称(Name)和创建方法(Action),它的两个Configure方法用于在获取Options的值的时候执行对应的Action来创建实例(例如示例中的Theme)。在此时不会被执行。所以在此会出现3中类型的ConfigureNamedOptions,分别是Name值为具体值的、Name值为为空字符串的和Name值为null的。这分别对应了第一节的例子中的为Option命名的Configure方法、不为Option命名的Configure方法、以及ConfigureAll方法。
此处用到的OptionsServiceCollectionExtensions和ConfigureNamedOptions对应的是通过代码直接注册Option的方式,例如第一节例子中的如下方式:
services.Configure<Theme>("ThemeBlack", theme => { new Theme { Color = "#000000", Name = "Black" }; });
如果是以Configuration作为数据源的方式,例如如下代码
services.Configure<Theme>("ThemeBlue", Configuration.GetSection("Themes:0"));
用到的是OptionsServiceCollectionExtensions和ConfigureNamedOptions这两个类的子类,分别为OptionsConfigurationServiceCollectionExtensions和NamedConfigureFromConfigurationOptions两个类,通过它们的名字也可以知道是专门用于采用Configuration作为数据源用的,代码类似,只是多了一条关于IOptionsChangeTokenSource的依赖注入,作用是将Configuration的关于数据源变化的监听和Options的关联起来,当数据源发生改变的时候可以及时更新Options中的值,主要的Configure方法代码如下:
public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, IConfiguration config, Action<BinderOptions> configureBinder) where TOptions : class { //省略验证代码 services.AddOptions(); services.AddSingleton<IOptionsChangeTokenSource<TOptions>>(new ConfigurationChangeTokenSource<TOptions>(name, config)); return services.AddSingleton<IConfigureOptions<TOptions>>(new NamedConfigureFromConfigurationOptions<TOptions>(name, config, configureBinder)); }
同样还有PostConfigure和PostConfigureAll方法,和Configure、ConfigureAll方法类似,只不过注入的类型为IPostConfigureOptions<TOptions>。