.net core 源码分析(1) 启动过程-IHostStartup
1.要了解启动过程,先介绍 IHostingStartup和IStartup 接口
/// <summary> /// Represents platform specific configuration that will be applied to a <see cref="IWebHostBuilder"/> when building an <see cref="IWebHost"/>. /// </summary> public interface IHostingStartup { /// <summary> /// Configure the <see cref="IWebHostBuilder"/>. /// </summary> /// <remarks> /// Configure is intended to be called before user code, allowing a user to overwrite any changes made. /// </remarks> /// <param name="builder"></param> void Configure(IWebHostBuilder builder); }
/// <summary> /// Provides an interface for initializing services and middleware used by an application. /// </summary> public interface IStartup { /// <summary> /// Register services into the <see cref="IServiceCollection" />. /// </summary> /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param> IServiceProvider ConfigureServices(IServiceCollection services); /// <summary> /// Configures the application. /// </summary> /// <param name="app">An <see cref="IApplicationBuilder"/> for the app to configure.</param> void Configure(IApplicationBuilder app); }
从上面2个接口可以看出,IHostingStartup 比 IStartup 不同点是缺少 IServiceProvider ConfigureServices(IServiceCollection services);,其实二者的运行顺序也是不同。
IHostingStartup 用以配置 IWebHostBuilder,.net core 允许有多个 IHostingStartup来实现在不同得地方配置 WebHostBuilder,下面展示一下 IHostingStartup得调用过程。
项目启动首先会执行Program.cs文件中的 Main方法,Program文件只是默认项,可以通过更改项目配置 来实现调用指定类的Main方法,
public class Program { // Entry point for the application. public static Task Main(string[] args) { var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseKestrel() // Each of these three sets ApplicationName to the current assembly, which is needed in order to // scan the assembly for HostingStartupAttributes. // .UseSetting(WebHostDefaults.ApplicationKey, "SampleStartups") // .Configure(_ => { }) .UseStartup<Startup>(); }) .Build(); return host.RunAsync(); } }
例1:例子来源.net core 8.0 源码 中 Hosting 的SampleStartups
项目配置
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework> <StartupObject>SampleStartups.StartupBlockingOnStart</StartupObject> <OutputType>exe</OutputType> </PropertyGroup> <ItemGroup> <Reference Include="Microsoft.AspNetCore.Hosting" /> <Reference Include="Microsoft.AspNetCore.Server.Kestrel" /> <Reference Include="Microsoft.Extensions.Configuration.CommandLine" /> <Reference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" /> <Reference Include="Microsoft.Extensions.Configuration.Json" /> </ItemGroup> </Project>
通过配置 <StartupObject></StartupObject> 程序启动文件为 SampleStartups.StartupBlockingOnStart
namespace SampleStartups; public class StartupInjection : IHostingStartup { public void Configure(IWebHostBuilder builder) { builder.UseStartup<InjectedStartup>(); } // Entry point for the application. public static Task Main(string[] args) { var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseKestrel() // Each of these three sets ApplicationName to the current assembly, which is needed in order to // scan the assembly for HostingStartupAttributes. // .UseSetting(WebHostDefaults.ApplicationKey, "SampleStartups") // .Configure(_ => { }) .UseStartup<NormalStartup>(); }) .Build(); return host.RunAsync(); } }
HostBuilder.ConfigureXXX
这里优先介绍HostBuilder一下ConfigureHostConfiguration
、ConfigureAppConfiguration
、ConfigureServices
等方法到底做了什么。其实就是将Action暂存到了一个私有属性变量中,
并没有立即执行。先存在一个list中,后边按一定顺序执行。稍后会介绍在哪里执行这些Action。
public class HostBuilder : IHostBuilder { private List<Action<IConfigurationBuilder>> _configureHostConfigActions = new List<Action<IConfigurationBuilder>>(); private List<Action<HostBuilderContext, IConfigurationBuilder>> _configureAppConfigActions = new List<Action<HostBuilderContext, IConfigurationBuilder>>(); private List<Action<HostBuilderContext, IServiceCollection>> _configureServicesActions = new List<Action<HostBuilderContext, IServiceCollection>>(); public IHostBuilder ConfigureHostConfiguration(Action<IConfigurationBuilder> configureDelegate) { _configureHostConfigActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate))); return this; } public IHostBuilder ConfigureAppConfiguration(Action<HostBuilderContext, IConfigurationBuilder> configureDelegate) { _configureAppConfigActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate))); return this; } public IHostBuilder ConfigureServices(Action<HostBuilderContext, IServiceCollection> configureDelegate) { _configureServicesActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate))); return this; } }
我们看一下 new HostBuilder().ConfigureWebHost(Action<IWebHostBuilder> configure) 中 ConfigureWebHost() 方法中具体执行了什么 ,
private static IHostBuilder ConfigureWebHost( this IHostBuilder builder, Func<IHostBuilder, WebHostBuilderOptions, IWebHostBuilder> createWebHostBuilder, Action<IWebHostBuilder> configure, Action<WebHostBuilderOptions> configureWebHostBuilder) { ArgumentNullException.ThrowIfNull(configure); ArgumentNullException.ThrowIfNull(configureWebHostBuilder); //先不介绍这里 // Light up custom implementations namely ConfigureHostBuilder which throws. if (builder is ISupportsConfigureWebHost supportsConfigureWebHost) { return supportsConfigureWebHost.ConfigureWebHost(configure, configureWebHostBuilder); } //重点看下这里 var webHostBuilderOptions = new WebHostBuilderOptions(); configureWebHostBuilder(webHostBuilderOptions); //这里创建了webhostBuilder对象,并立即执行传入的action var webhostBuilder = createWebHostBuilder(builder, webHostBuilderOptions); //执行传入的action configure(webhostBuilder); //此处暂存了一个action ,其实在 createWebHostBuilder(builder, webHostBuilderOptions) 中已经执行了逻辑,暂存多个关键的action builder.ConfigureServices((context, services) => services.AddHostedService<GenericWebHostService>()); return builder; }
我们看一下在 new GenericWebHostBuilder(hostBuilder, options) 中执行的逻辑
internal sealed class GenericWebHostBuilder : WebHostBuilderBase, ISupportsStartup { //用于暂存关于IStartup的相关类型, private object? _startupObject; private readonly object _startupKey = new object(); private AggregateException? _hostingStartupErrors; private HostingStartupWebHostBuilder? _hostingStartupWebHostBuilder; public GenericWebHostBuilder(IHostBuilder builder, WebHostBuilderOptions options) : base(builder, options) { //暂存一个HostConfigration的action _builder.ConfigureHostConfiguration(config => { config.AddConfiguration(_config); //重点关注下这里,稍后介绍这里会涉及到IHostStartup.Configure(IWebHostBuilder builder 的执行),这里的参数builder 就是 this // We do this super early but still late enough that we can process the configuration // wired up by calls to UseSetting ExecuteHostingStartups(); }); //暂存 // IHostingStartup needs to be executed before any direct methods on the builder // so register these callbacks first _builder.ConfigureAppConfiguration((context, configurationBuilder) => { if (_hostingStartupWebHostBuilder != null) { var webhostContext = GetWebHostBuilderContext(context); _hostingStartupWebHostBuilder.ConfigureAppConfiguration(webhostContext, configurationBuilder); } }); //暂存CongfigureServices action _builder.ConfigureServices((context, services) => { var webhostContext = GetWebHostBuilderContext(context); var webHostOptions = (WebHostOptions)context.Properties[typeof(WebHostOptions)]; // Add the IHostingEnvironment and IApplicationLifetime from Microsoft.AspNetCore.Hosting services.AddSingleton(webhostContext.HostingEnvironment); #pragma warning disable CS0618 // Type or member is obsolete services.AddSingleton((AspNetCore.Hosting.IHostingEnvironment)webhostContext.HostingEnvironment); services.AddSingleton<IApplicationLifetime, GenericWebHostApplicationLifetime>(); #pragma warning restore CS0618 // Type or member is obsolete services.Configure<GenericWebHostServiceOptions>(options => { // Set the options options.WebHostOptions = webHostOptions; // Store and forward any startup errors options.HostingStartupExceptions = _hostingStartupErrors; }); // REVIEW: This is bad since we don't own this type. Anybody could add one of these and it would mess things up // We need to flow this differently services.TryAddSingleton(sp => new DiagnosticListener("Microsoft.AspNetCore")); services.TryAddSingleton<DiagnosticSource>(sp => sp.GetRequiredService<DiagnosticListener>()); services.TryAddSingleton(sp => new ActivitySource("Microsoft.AspNetCore")); services.TryAddSingleton(DistributedContextPropagator.Current); services.TryAddSingleton<IHttpContextFactory, DefaultHttpContextFactory>(); services.TryAddScoped<IMiddlewareFactory, MiddlewareFactory>(); services.TryAddSingleton<IApplicationBuilderFactory, ApplicationBuilderFactory>(); services.AddMetrics(); services.TryAddSingleton<HostingMetrics>(); // IMPORTANT: This needs to run *before* direct calls on the builder (like UseStartup) _hostingStartupWebHostBuilder?.ConfigureServices(webhostContext, services); //如果特别指定了startupAssembly ,程序就会扫描程序集获取,这个行为也被暂存在当前的这个action // Support UseStartup(assemblyName) if (!string.IsNullOrEmpty(webHostOptions.StartupAssembly)) { ScanAssemblyAndRegisterStartup(context, services, webhostContext, webHostOptions); } }); } //删除部分代码 }
在webHostBuilder创建过程中暂存了ConfigureHostConfiguration,ConfigureAppConfiguration,ConfigureServices,那么他们都在什么时候执行,并且是一个什么执行顺序。这就要看一下他们在HostBuilder 中Build过程中的执行顺序了,不废话,直接上代码
public partial class HostBuilder : IHostBuilder { /// <summary> /// Run the given actions to initialize the host. This can only be called once. /// </summary> /// <returns>An initialized <see cref="IHost"/></returns> /// <remarks>Adds basic services to the host such as application lifetime, host environment, and logging.</remarks> public IHost Build() { if (_hostBuilt) { throw new InvalidOperationException(SR.BuildCalled); } _hostBuilt = true; // REVIEW: If we want to raise more events outside of these calls then we will need to // stash this in a field. using DiagnosticListener diagnosticListener = LogHostBuilding(this); // 1.初始化主机(Host)配置 InitializeHostConfiguration(); // 2.初始化 HostingEnvironment InitializeHostingEnvironment(); // 3.初始化 HostBuilderContext InitializeHostBuilderContext(); // 4.初始化应用(App)配置 InitializeAppConfiguration(); // 5.初始化服务并创建 Service Provider InitializeServiceProvider(); return ResolveHost(_appServices, diagnosticListener); } }
不难看出 在 BuildHostConfiguration()方法中,会执行在上面介绍 new WebHostBuilder() 构造函数中暂存的一个action ,看下方代码暂存的那个action
internal sealed class GenericWebHostBuilder : WebHostBuilderBase, ISupportsStartup { //省略一下代码。。。。 public GenericWebHostBuilder(IHostBuilder builder, WebHostBuilderOptions options) : base(builder, options) { //暂存一个HostConfigration的action,这个action 最终会在Build()方法中的第一阶段初始 化主机Host配置InitializeHostConfiguration()中执行。 _builder.ConfigureHostConfiguration(config => { config.AddConfiguration(_config); //重点关注下这里,稍后介绍 // We do this super early but still late enough that we can process the configuration // wired up by calls to UseSetting ExecuteHostingStartups(); }); } }
此时为执行 ExecuteHostingStartups,此处涉及执行 HostStartup相关逻辑。
internal sealed class GenericWebHostBuilder : WebHostBuilderBase, ISupportsStartup { private object? _startupObject; private readonly object _startupKey = new object(); private AggregateException? _hostingStartupErrors; private HostingStartupWebHostBuilder? _hostingStartupWebHostBuilder; private void ExecuteHostingStartups() { var webHostOptions = new WebHostOptions(_config); if (webHostOptions.PreventHostingStartup) { return; } var exceptions = new List<Exception>(); var processed = new HashSet<Assembly>(); _hostingStartupWebHostBuilder = new HostingStartupWebHostBuilder(this); // Execute the hosting startup assemblies foreach (var assemblyName in webHostOptions.GetFinalHostingStartupAssemblies()) { try { var assembly = Assembly.Load(new AssemblyName(assemblyName)); if (!processed.Add(assembly)) { // Already processed, skip it continue; } //找出有标记HostingStartupAttribute的信息 foreach (var attribute in assembly.GetCustomAttributes<HostingStartupAttribute>()) { //根据标签中指定的类型,类型需要继承IHostSrartup接口 ,实例化该类型的对象,然后执行对象Configue方法 来配置WebHostBuilder var hostingStartup = (IHostingStartup)Activator.CreateInstance(attribute.HostingStartupType)!; hostingStartup.Configure(_hostingStartupWebHostBuilder); } } catch (Exception ex) { // Capture any errors that happen during startup exceptions.Add(new InvalidOperationException($"Startup assembly {assemblyName} failed to execute. See the inner exception for more details.", ex)); } } if (exceptions.Count > 0) { _hostingStartupErrors = new AggregateException(exceptions); } } }
总结:
执行IHostStartup.Configure(IWebHostBuilder builder) 方法的一个委托被暂存在_configureHostCofigActions中,
最终会在HostBuilder.Build()的第一阶段InitializeHostConfiguration()方法中执行,执行顺序早于 InitializeHostingEnvironment()、InitializeHostBuilderContext()、
InitializeAppConfiguration()、InitializeServiceProvider();
.net core 源码分析