.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一下ConfigureHostConfigurationConfigureAppConfigurationConfigureServices等方法到底做了什么。其实就是将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 源码分析

 

posted @ 2024-08-07 17:28  Hi同学  阅读(53)  评论(0编辑  收藏  举报