ASP.NET Core 2.0 : 七.一张图看透启动背后的秘密

为什么我们可以在Startup这个 “孤零零的” 类中配置依赖注入和管道?

  它是什么时候被实例化并且调用的?

  参数中的IServiceCollection services是怎么来的?

  处理管道是怎么构建起来的?

  启动过程中,系统“默默的”做了哪些准备工作?

  上一篇文章讲了ASP.NET Core中的依赖注入(系列目录), 而它的配置是在Startup这个文件中的 ConfigureServices(IServiceCollection services) 方法,而且Startup这个类也没有继承任何类或者接口。 深入的想一想,可能会冒出类似上面列出的好多问题,下面用一幅图来看透它。

一、整体流程图

先上图, 觉得看不清可以点击看大图或者下载后放大查看。

ASP.NET Core 2.0 : 七.一张图看透启动背后的秘密

图一  (点击放大

二、WebHostBuilder

  应用程序在Main方法之后通过调用Create­DefaultBuilder方法创建并配置WebHostBuilder, 

ASP.NET Core 2.0 : 七.一张图看透启动背后的秘密
 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     }
ASP.NET Core 2.0 : 七.一张图看透启动背后的秘密

WebHostBuilder存在一个重要的集合 private readonly List<Action<WebHostBuilderContext, IServiceCollection>> _configureServicesDelegates; , 通过 ConfigureServices 方法将需要的Action加入进来。

UseSetting是一个用于设置Key-Value的方法, 一些常用的配置均会通过此方法写入_config中。

三、UseStartup<Startup>()

  Create­DefaultBuilder之后调用UseStartup<Startup>(),指定Startup为启动类。

ASP.NET Core 2.0 : 七.一张图看透启动背后的秘密
        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));
                        });
                    }
                });
        }
ASP.NET Core 2.0 : 七.一张图看透启动背后的秘密

首先获取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, 

ASP.NET Core 2.0 : 七.一张图看透启动背后的秘密
 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         }                
ASP.NET Core 2.0 : 七.一张图看透启动背后的秘密

  在这个方法里创建了ServiceCollection services(以唐僧为首的取经小分队), 然后通过各种Add方法注册了好多内容进去(收了悟空),然后foreach 之前暂存在configureServicesDelegates中的各个Action,传入services逐一执行, 将之前需要注册的内容注册到services中, 这里就包括Startup(八戒),注意这里仅是进行了注册,而未执行Startup的方法。

  处理好的这个services被BuildCommonServices返回后赋值给 hostingServices,然后 hostingServices经过Clone()生成 applicationServices,再由这个 applicationServices进行GetProviderFromFactory(hostingServices)生成一个 IServiceProvider hostingServiceProvider.经过一系列的处理后,可以创建WebHost了。

ASP.NET Core 2.0 : 七.一张图看透启动背后的秘密
var host = new WebHost(
    applicationServices,
    hostingServiceProvider,
    _options,
    _config,
    hostingStartupErrors);

host.Initialize();
ASP.NET Core 2.0 : 七.一张图看透启动背后的秘密

 将生成的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()

ASP.NET Core 2.0 : 七.一张图看透启动背后的秘密
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);
    //.....
}
ASP.NET Core 2.0 : 七.一张图看透启动背后的秘密

  在之前的文章中我们知道,请求是经过 Server监听=>处理成httpContext=>Application处理,所以这里首先传入上面创建的_application和一个httpContextFactory来⑨生成一个HostingApplication,并将这个HostingApplication传入Server的StartAsync(), 当Server监听到请求之后, 后面的工作由HostingApplication来完成。

http://music.baidu.com/songlist/522765034
http://music.baidu.com/songlist/522765100
http://music.baidu.com/songlist/522765135
http://music.baidu.com/songlist/522765181
http://music.baidu.com/songlist/522765209

http://music.baidu.com/songlist/522

然扽手踭,聂惊风身形郁都唔郁,不过,佢却是可以感觉到喺嗰啲扽手踭中,咁化灵掌嘅力量,竟然化为咗黑白寒芒,直逼佢而嚟。
不过,对于呢黑白寒芒,聂惊风却是冇任何嘅计带,佢只系脚板底跺噉,就系生生系将黑白寒芒完完全全嘅扽碎而去。
根本唔费吹灰之力。
聂惊风面色一寒,灵气喺此刻尽数嘅呼啸而出,咁化灵掌之上,顿时有住好易裂咖爆裂开来,片刻之后,爆裂成咗漫天碎屑。
“点可能?”
化灵掌爆裂,柳狂面色都系一变,呢条化灵掌嘅强大之处,不特能够压制对方嘅攻势,甚至可以伤到对方,先前佢系真嘅察觉到化灵掌嘅力量,已经化为咗黑白寒芒,但系个仔,点都能够毫发无伤?
唔通聂惊风嘅实重自己之上唔到?
一想噉,柳狂就觉得生鬼,佢早就晋入咗化罡境,其他同修为嘅强者都会不堪一击,咁聂惊风又点可能超越佢。
“唰!”
喺其面色变化嘅时候,聂惊风却是身形一闪,以柳狂为中心嘅地面,就系寸寸断裂,而其身形,就系快如闪电下一霎,就系吊诡?嘅到咗柳狂之前,嗰幕,连柳狂都吓咗一大跳。
佢实在系有啲都理解,聂惊风点会悄无声息出现。
聂惊风眼中仿佛系有住冻厉之咸湿掠过,佢一拳轰出,呢一拳,全身嘅力量,都系爆发出嚟。
一拳势如破竹嘅轰咗出嚟,竟然系爆出咗一道道令人心惊嘅空间裂纹,噉系力量得人惊到极限嘅缘故。
唔少强者见到聂惊风呢一拳,顿时眼神都系凝重咗出嚟,噉系有啲觉得唔相信,呢一拳,就算系一般化罡境中期嘅强者,都会系都憾其锋芒。
可怕拳劲笼罩而嚟,柳狂面色当即大变,咁眼神终于系变得忌惮起,再都唔敢有一丝一毫嘅睇小佢而家方才彻底明,眼前呢个啱啱晋入化罡境嘅聂惊风,究竟系几咁嘅难缠。765246