ASP.NET Core 2.0 : 七.一張圖看透啟動背后的秘密


  為什么我們可以在Startup這個 “孤零零的” 類中配置依賴注入和管道?

  它是什么時候被實例化並且調用的?

  參數中的IServiceCollection services是怎么來的?

  處理管道是怎么構建起來的?

  啟動過程中,系統“默默的”做了哪些准備工作?

  上一篇文章講了ASP.NET Core中的依賴注入(系列目錄), 而它的配置是在Startup這個文件中的 ConfigureServices(IServiceCollection services) 方法,而且Startup這個類也沒有繼承任何類或者接口。 深入的想一想,可能會冒出類似上面列出的好多問題,下面用一幅圖來看透它。

一、整體流程圖

先上圖, 覺得看不清可以點擊看大圖或者下載后放大查看。

圖一  (點擊放大

二、WebHostBuilder

  應用程序在Main方法之后通過調用Create­DefaultBuilder方法創建並配置WebHostBuilder, 

 1     public class WebHostBuilder : IWebHostBuilder
 2     {
 3         private readonly List<Action<WebHostBuilderContext, IServiceCollection>> _configureServicesDelegates;
 4 
 5         private IConfiguration _config;
 6         public IWebHostBuilder UseSetting(string key, string value)
 7         {
 8             _config[key] = value;
 9             return this;
10         }
22         public IWebHostBuilder ConfigureServices(Action<WebHostBuilderContext, IServiceCollection> configureServices)
23         {
24             if (configureServices == null)
25             {
26                 throw new ArgumentNullException(nameof(configureServices));
27             }
29             _configureServicesDelegates.Add(configureServices);
30             return this;
31         }
32     }

WebHostBuilder存在一個重要的集合 private readonly List<Action<WebHostBuilderContext, IServiceCollection>> _configureServicesDelegates; , 通過 ConfigureServices 方法將需要的Action加入進來。

UseSetting是一個用於設置Key-Value的方法, 一些常用的配置均會通過此方法寫入_config中。

三、UseStartup<Startup>()

  Create­DefaultBuilder之后調用UseStartup<Startup>(),指定Startup為啟動類。

        public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType)
        {
            var startupAssemblyName = startupType.GetTypeInfo().Assembly.GetName().Name;

            return hostBuilder
                .UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName)
                .ConfigureServices(services =>
                {
                    if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo()))
                    {
                        services.AddSingleton(typeof(IStartup), startupType);
                    }
                    else
                    {
                        services.AddSingleton(typeof(IStartup), sp =>
                        {
                            var hostingEnvironment = sp.GetRequiredService<IHostingEnvironment>();
                            return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName));
                        });
                    }
                });
        }

首先獲取Startup類對應的AssemblyName, 調用UseSetting方法將其設置為WebHostDefaults.ApplicationKey(“applicationName”)的值。

然后調用WebHostBuilder的ConfigureServices方法,將一個Action寫入WebHostBuilder 的 configureServicesDelegates中。

這個Action的意思就是說,如果這個被指定的類startupType是一個實現了IStartup的類, 那么將其通過AddSingleton注冊到services 這個ServiceCollection中, 如果不是, 那么將其“轉換”成 ConventionBasedStartup 這個實現了 IStartup的類后再進行注冊。這里涉及到一個StartupLoader的LoadMethods()方法,會通過字符串的方式查找“ConfigureServices”、“Configure{ environmentName}Services”這樣的方法。

注意:這里只是將一個Action寫入了configureServicesDelegates, 而不是已經執行了對IStartup的注冊, 因為這個Action尚未執行,services也還不存在。就像菩薩對八戒說: 八戒(Startup)你先在高老庄等着吧, 將來有個和尚帶領一個取經小分隊(ServiceCollection services )過來的時候你加入他們。

其實在Create­DefaultBuilder方法中的幾個UseXXX的方法也是這樣通過ConfigureServices將對應的Action寫入了configureServicesDelegates, 等待唐僧的到來。

四、WebHostBuilder.Build()

創建並配置好的WebHostBuilder開始通過Build方法創建WebHost了, 首先是BuildCommonServices, 

 1 private IServiceCollection BuildCommonServices(out AggregateException hostingStartupErrors)
 2         {
 3             //...省略...
 4             var services = new ServiceCollection();
 5             services.AddSingleton(_hostingEnvironment);
 6             services.AddSingleton(_context);
 7             //....各種Add....
 9             foreach (var configureServices in _configureServicesDelegates)
10             {
11                 configureServices(_context, services);
12             }
14             return services;
15         }                

  在這個方法里創建了ServiceCollection services(以唐僧為首的取經小分隊), 然后通過各種Add方法注冊了好多內容進去(收了悟空),然后foreach 之前暫存在configureServicesDelegates中的各個Action,傳入services逐一執行, 將之前需要注冊的內容注冊到services中, 這里就包括Startup(八戒),注意這里僅是進行了注冊,而未執行Startup的方法。

  處理好的這個services被BuildCommonServices返回后賦值給 hostingServices,然后 hostingServices經過Clone()生成 applicationServices,再由這個 applicationServices進行 GetProviderFromFactory(hostingServices)生成一個 IServiceProvider hostingServiceProvider.經過一系列的處理后,可以創建WebHost了。

var host = new WebHost(
    applicationServices,
    hostingServiceProvider,
    _options,
    _config,
    hostingStartupErrors);

host.Initialize();

 將生成的applicationServices 和 hostingServiceProvider作為參數傳遞給新生成的WebHost。接下來就是這個WebHost的 Initialize()。

 五、WebHost.Initialize()

WebHost的 Initialize()的主要工作就是BuildApplication()。

EnsureApplicationServices(): 用來處理WebHost的 private IServiceProvider _applicationServices ,④Startup的ConfigureServices方法在這里被調用

_startup = _hostingServiceProvider.GetRequiredService<IStartup>();
_applicationServices = _startup.ConfigureServices(_applicationServiceCollection);

通過 GetRequiredService<IStartup>() 獲取到我們的_startup, 然后調用這個_startup的 ⑤ConfigureServices 方法,這就是我們用於依賴注入的startup類的ConfigureServices方法了。

所以,_applicationServices是根據_applicationServiceCollection 加上我們在_startup中注冊的內容之后重新生成的 IServiceProvider。

EnsureServer()通過 GetRequiredService<IServer>()獲取Server並配置監聽地址。

var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>();
var builder = builderFactory.CreateBuilder(Server.Features);
builder.ApplicationServices = _applicationServices;

獲取到 IApplicationBuilderFactory並通過它創建 IApplicationBuilder,並將上面創建的_applicationServices賦值給它的ApplicationServices,它還有個重要的集合_components

private readonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>();

 從_components的類型可以看出它其實是中間件的集合,是該調用我們的_startup的Configure方法的時候了。

先獲取定義的IStartupFilter, foreach這些IStartupFilter並與_startup的Configure方法一起將配置的中間件寫入_components,然后通過 Build()創建RequestDelegate _application,

在Build()中對_components進行處理生成請求處理管道,關於IStartupFilter和生成管道這部分將在下篇文章進行詳細說明。

六、WebHost.Run()

  WebHost創建完畢, 最后一步就是Run起來了,WebHost的Run()會調用它的方法StartAsync()

public virtual async Task StartAsync(CancellationToken cancellationToken = default(CancellationToken))
{
    //......var hostingApp = new HostingApplication(_application, _logger, diagnosticSource, httpContextFactory);
    await Server.StartAsync(hostingApp, cancellationToken).ConfigureAwait(false);
    _hostedServiceExecutor.StartAsync(cancellationToken).ConfigureAwait(false);
    //.....
}

  在之前的文章中我們知道,請求是經過 Server監聽=>處理成httpContext=>Application處理,所以這里首先傳入上面創建的_application和一個httpContextFactory來⑨生成一個HostingApplication,並將這個HostingApplication傳入Server的StartAsync(), 當Server監聽到請求之后, 后面的工作由HostingApplication來完成。

  ⑩hostedServiceExecutor.StartAsync()方法用來開啟一個后台運行的服務,一些需要后台運行的操作比如定期刷新緩存等可以放到這里來。

七、更新

  感謝dudu的留言,去github上看了一下WebHost的最新源碼,BuildApplication()不再包含EnsureApplicationServices()的調用,並且轉移到了WebHost.StartAsync() 中進行; WebHost.Initialize() 中由原本調用BuildApplication()改為調用原本放在BuildApplication()中調用的EnsureApplicationServices()。

 

  通過VS加載符號的方式調試獲取到的WebHost仍是原來的版本,即使刪除下載的文件后再次重新獲取也一樣, 應該是和新建項目默認引用的依賴版本有關。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM