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仍是原来的版本,即使删除下载的文件后再次重新获取也一样, 应该是和新建项目默认引用的依赖版本有关。

 

本文链接:https://2i3i.com/aspnetcore2_7.html ,转载请注明来源地址。
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇