Asp.net Core 啟動流程分析


新建的.net core 程序啟動本質上是一個控制台應用程序,所以它的入口在Main方法中,所以啟動的開始時從Main方法開始。
 1     public class Program
 2     {
 3         public static void Main(string[] args)
 4         {
 5             BuildWebHost(args).Run();
 6         }
 7 
 8         public static IWebHost BuildWebHost(string[] args)=>
 9             WebHost.CreateDefaultBuilder(args).UseStartup<Startup>().Build();
10 
11     }

WebHost的CreateDefaultBuilder方法負責創建WebHostBuilder,最后調用WebHostBuilder的build方法創建一個WebHost,這個流程是現在Core里面流行的創建方式,類似讀取Config的流程。

IConfiguration configuration = new ConfigurationBuilder().Add(new MemoryConfigurationSource { InitialData = source }).Build(); configuration.GetSection("Format");//調用Config的獲得數據的方法。

我們看到的第一個方法:WebHost.CreateDefaultBuilder(args)。

 1  public static IWebHostBuilder CreateDefaultBuilder(string[] args)
 2         {
 3             var builder = new WebHostBuilder()
 4                 .UseKestrel()//用Kestreal作為服務器
 5                 .UseContentRoot(Directory.GetCurrentDirectory())//設置程序的根目錄
 6                 .ConfigureAppConfiguration((hostingContext, config) =>
 7                 {
 8                     //根據環境把對應的appsetting配置文件加入到config容器中
 9                     var env = hostingContext.HostingEnvironment;
10 
11                     config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
12                           .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
13 
14                     if (env.IsDevelopment())
15                     {
16                         var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
17                         if (appAssembly != null)
18                         {
19                             //添加用戶機密數據
20                             config.AddUserSecrets(appAssembly, optional: true);
21                         }
22                     }
23                     //添加環境變量
24                     config.AddEnvironmentVariables();
25                     //這句加參數不知道有什么用
26                     if (args != null)
27                     {
28                         config.AddCommandLine(args);
29                     }
30                 })
31                 .ConfigureLogging((hostingContext, logging) =>
32                 {
33                     logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
34                     logging.AddConsole();
35                     logging.AddDebug();
36                 })
37                 .UseIISIntegration()
38                 .UseDefaultServiceProvider((context, options) =>
39                 {
40                     options.ValidateScopes = context.HostingEnvironment.IsDevelopment();
41                 });
42 
43             return builder;
44         }

添加用戶機密是干什么的可以詳細看ASP.NET Core 優雅的在開發環境保存機密(User Secrets),具體就是用來保存一些機密數據。

WebHostBuilder類

 1         //環境變量的參數
 2         private readonly IHostingEnvironment _hostingEnvironment;
 3         //存儲添加的Service委托的一個集合
 4         private readonly List<Action<WebHostBuilderContext, IServiceCollection>> _configureServicesDelegates;
 5         //訪問配置文件
 6         private IConfiguration _config;
 7         //WebHost的一些配置選項,它里面其實都是存在config里面的
 8         private WebHostOptions _options;
 9         //webHostBuilder上下文,里面兩個屬性,一個是IHostingEnvironment,一個是IConfiguration
10         private WebHostBuilderContext _context;
11         //判斷是否調用了Build方法,調用以后為true,再次調用Build方法時會報錯
12         private bool _webHostBuilt;
13         //存儲添加配置的一個委托集合
14         private List<Action<WebHostBuilderContext, IConfigurationBuilder>> _configureAppConfigurationBuilderDelegates;
15 
16         /// <summary>
17         /// Initializes a new instance of the <see cref="WebHostBuilder"/> class.
18         /// </summary>
19         public WebHostBuilder()
20         {
21             _hostingEnvironment = new HostingEnvironment();
22             _configureServicesDelegates = new List<Action<WebHostBuilderContext, IServiceCollection>>();
23             _configureAppConfigurationBuilderDelegates = new List<Action<WebHostBuilderContext, IConfigurationBuilder>>();
24 
25             _config = new ConfigurationBuilder()
26                 .AddEnvironmentVariables(prefix: "ASPNETCORE_")
27                 .Build();
28 
29             if (string.IsNullOrEmpty(GetSetting(WebHostDefaults.EnvironmentKey)))
30             {
31                 // Try adding legacy environment keys, never remove these.
32                 UseSetting(WebHostDefaults.EnvironmentKey, Environment.GetEnvironmentVariable("Hosting:Environment")
33                     ?? Environment.GetEnvironmentVariable("ASPNET_ENV"));
34             }
35 
36             if (string.IsNullOrEmpty(GetSetting(WebHostDefaults.ServerUrlsKey)))
37             {
38                 // Try adding legacy url key, never remove this.
39                 UseSetting(WebHostDefaults.ServerUrlsKey, Environment.GetEnvironmentVariable("ASPNETCORE_SERVER.URLS"));
40             }
41 
42             _context = new WebHostBuilderContext
43             {
44                 Configuration = _config
45             };
46         }

ConfigureAppConfiguration方法就是把委托加到_configureAppConfigurationBuilderDelegates的集合當中去。

AddLogging就是把Service添加到_configureServicesDelegates中去。

UseStartUp方法

        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));
                        });
                    }
                });
        }

設置一下webhostBuilder的程序集名稱,把StartUp注冊到DI容器中去。目前還沒有注入到di容器中去,只是跟之前一樣,把這個委托加到了_configureServicesDelegates中去了,在循環執行委托的時候,才真正的注入到di容器中去,這個委托的用處就是把StartUp注入到DI去。

此時有兩個判斷,如果泛型傳進來的StartUp類是繼承了IStartup的話就直接注入到DI去的,如果不是的話,就裝載默認的StartUp類,也就是我們Core項目的新建時候的那個StartUp類。

看一下StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName)方法的代碼:

 1         public static StartupMethods LoadMethods(IServiceProvider hostingServiceProvider, Type startupType, string environmentName)
 2         {
 3             //獲取StartUp類的Config方法(注:這個是可以根據環境來獲取不同的配置方法:方法名帶有環境變量類似於:Configure{0},占位符就是環境變量的值test,stage等)
 4             var configureMethod = FindConfigureDelegate(startupType, environmentName);
 5             //獲取StartUp類的ConfigService方法(跟Config獲取一樣可以分環境獲取方法,Configure{0}Service)
 6             var servicesMethod = FindConfigureServicesDelegate(startupType, environmentName);
 7 
 8             //獲取StartUp類的ConfigContainer方法(跟Config獲取一樣可以分環境獲取方法,Configure{0}Container)
 9             var configureContainerMethod = FindConfigureContainerDelegate(startupType, environmentName);
10 
11             object instance = null;
12             if (!configureMethod.MethodInfo.IsStatic || (servicesMethod != null && !servicesMethod.MethodInfo.IsStatic))
13             {
14                 instance = ActivatorUtilities.GetServiceOrCreateInstance(hostingServiceProvider, startupType);
15             }
16             //構建一個執行ConfigService的委托
17             var configureServicesCallback = servicesMethod.Build(instance);
18             var configureContainerCallback = configureContainerMethod.Build(instance);
19 
20             Func<IServiceCollection, IServiceProvider> configureServices = services =>
21             {
22                 // Call ConfigureServices, if that returned an IServiceProvider, we're done,執行ConfigService,如果執行ConfigService方法返回的是一個IServiceProvider,則就直接返回,不執行下面的替換ConfigContainer的方法
23                 IServiceProvider applicationServiceProvider = configureServicesCallback.Invoke(services);
24 
25                 if (applicationServiceProvider != null)
26                 {
27                     return applicationServiceProvider;
28                 }
29 
30                 // If there's a ConfigureContainer method
31                 if (configureContainerMethod.MethodInfo != null)
32                 {
33                     // We have a ConfigureContainer method, get the IServiceProviderFactory<TContainerBuilder>
34                     var serviceProviderFactoryType = typeof(IServiceProviderFactory<>).MakeGenericType(configureContainerMethod.GetContainerType());
35                     var serviceProviderFactory = hostingServiceProvider.GetRequiredService(serviceProviderFactoryType);
36                     // var builder = serviceProviderFactory.CreateBuilder(services);
37                     var builder = serviceProviderFactoryType.GetMethod(nameof(DefaultServiceProviderFactory.CreateBuilder)).Invoke(serviceProviderFactory, new object[] { services });
38                     configureContainerCallback.Invoke(builder);
39                     // applicationServiceProvider = serviceProviderFactory.CreateServiceProvider(builder);
40                     applicationServiceProvider = (IServiceProvider)serviceProviderFactoryType.GetMethod(nameof(DefaultServiceProviderFactory.CreateServiceProvider)).Invoke(serviceProviderFactory, new object[] { builder });
41                 }
42                 else
43                 {
44                     // Get the default factory
45                     var serviceProviderFactory = hostingServiceProvider.GetRequiredService<IServiceProviderFactory<IServiceCollection>>();
46 
47                     // Don't bother calling CreateBuilder since it just returns the default service collection
48                     applicationServiceProvider = serviceProviderFactory.CreateServiceProvider(services);
49                 }
50 
51                 return applicationServiceProvider ?? services.BuildServiceProvider();
52             };
53 
54             return new StartupMethods(instance, configureMethod.Build(instance), configureServices);
55         }
ConventionBasedStartup這個類就是一個類似於StartUp的類,New得到時候,就是把StartupMethods傳進去,里面的話,就是有兩個方法Config指向StartUp的Config方法,ConfigService方法執行StartUp的ConfigService方法。

 

最后就是WebHostBuilder.Build()方法

 

 1 public IWebHost Build()
 2         {
 3             if (_webHostBuilt)
 4             {
 5                 throw new InvalidOperationException(Resources.WebHostBuilder_SingleInstance);
 6             }
 7             _webHostBuilt = true;
 8 
 9             // Warn about deprecated environment variables
10             if (Environment.GetEnvironmentVariable("Hosting:Environment") != null)
11             {
12                 Console.WriteLine("The environment variable 'Hosting:Environment' is obsolete and has been replaced with 'ASPNETCORE_ENVIRONMENT'");
13             }
14 
15             if (Environment.GetEnvironmentVariable("ASPNET_ENV") != null)
16             {
17                 Console.WriteLine("The environment variable 'ASPNET_ENV' is obsolete and has been replaced with 'ASPNETCORE_ENVIRONMENT'");
18             }
19 
20             if (Environment.GetEnvironmentVariable("ASPNETCORE_SERVER.URLS") != null)
21             {
22                 Console.WriteLine("The environment variable 'ASPNETCORE_SERVER.URLS' is obsolete and has been replaced with 'ASPNETCORE_URLS'");
23             }
24 
25             var hostingServices = BuildCommonServices(out var hostingStartupErrors);
26             var applicationServices = hostingServices.Clone();
27             var hostingServiceProvider = hostingServices.BuildServiceProvider();
28 
29             AddApplicationServices(applicationServices, hostingServiceProvider);
30 
31             var host = new WebHost(
32                 applicationServices,
33                 hostingServiceProvider,
34                 _options,
35                 _config,
36                 hostingStartupErrors);
37 
38             host.Initialize();
39 
40             return host;
41         }

 

里面的重點是在BuildCommonServices中,里面默認添加了一系列的Service。如下面的代碼:

  1         private IServiceCollection BuildCommonServices(out AggregateException hostingStartupErrors)
  2         {
  3             hostingStartupErrors = null;
  4 
  5             _options = new WebHostOptions(_config);
  6 
  7             if (!_options.PreventHostingStartup)
  8             {
  9                 var exceptions = new List<Exception>();
 10 
 11                 // Execute the hosting startup assemblies
 12                 // HostingStartupAssemblies = $"{ApplicationName};{configuration[WebHostDefaults.HostingStartupAssembliesKey]}".Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) ?? new string[0];這就是HostingStartupAssembly查找,當我們有類繼承與IHostingStartup是,他必須得在build的config里面添加一個key為WebHostDefaults.HostingStartupAssembliesKey的程序集名稱,多個以分號隔開。
 13                 foreach (var assemblyName in _options.HostingStartupAssemblies)
 14                 {
 15                     try
 16                     {
 17                         var assembly = Assembly.Load(new AssemblyName(assemblyName));
 18 
 19                         //程序集上必須打上HostingStartupAttribute標簽
 20                         foreach (var attribute in assembly.GetCustomAttributes<HostingStartupAttribute>())
 21                         {
 22                             var hostingStartup = (IHostingStartup)Activator.CreateInstance(attribute.HostingStartupType);
 23                             //執行hostingStartup的config方法。
 24                             hostingStartup.Configure(this);
 25                         }
 26                     }
 27                     catch (Exception ex)
 28                     {
 29                         // Capture any errors that happen during startup
 30                         exceptions.Add(new InvalidOperationException($"Startup assembly {assemblyName} failed to execute. See the inner exception for more details.", ex));
 31                     }
 32                 }
 33 
 34                 if (exceptions.Count > 0)
 35                 {
 36                     hostingStartupErrors = new AggregateException(exceptions);
 37                 }
 38             }
 39 
 40             var contentRootPath = ResolveContentRootPath(_options.ContentRootPath, AppContext.BaseDirectory);
 41             var applicationName = _options.ApplicationName;
 42 
 43             // Initialize the hosting environment
 44             _hostingEnvironment.Initialize(applicationName, contentRootPath, _options);
 45             _context.HostingEnvironment = _hostingEnvironment;
 46 
 47             var services = new ServiceCollection();
 48             services.AddSingleton(_hostingEnvironment);
 49             services.AddSingleton(_context);
 50 
 51             var builder = new ConfigurationBuilder()
 52                 .SetBasePath(_hostingEnvironment.ContentRootPath)
 53                 .AddInMemoryCollection(_config.AsEnumerable());
 54 
 55             //執行之前添加的配置文件的委托
 56             foreach (var configureAppConfiguration in _configureAppConfigurationBuilderDelegates)
 57             {
 58                 configureAppConfiguration(_context, builder);
 59             }
 60 
 61             var configuration = builder.Build();
 62             services.AddSingleton<IConfiguration>(configuration);
 63             _context.Configuration = configuration;
 64 
 65             var listener = new DiagnosticListener("Microsoft.AspNetCore");
 66             services.AddSingleton<DiagnosticListener>(listener);
 67             services.AddSingleton<DiagnosticSource>(listener);
 68 
 69             services.AddTransient<IApplicationBuilderFactory, ApplicationBuilderFactory>();
 70             services.AddTransient<IHttpContextFactory, HttpContextFactory>();
 71             services.AddScoped<IMiddlewareFactory, MiddlewareFactory>();
 72             services.AddOptions();
 73             services.AddLogging();
 74 
 75             // Conjure up a RequestServices
 76             services.AddTransient<IStartupFilter, AutoRequestServicesStartupFilter>();
 77             services.AddTransient<IServiceProviderFactory<IServiceCollection>, DefaultServiceProviderFactory>();
 78 
 79             // Ensure object pooling is available everywhere.
 80             services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
 81             //之前再UseStartUp(string assemblyName)設置的startup類的程序集,在這之前也執行了 一段這個代碼,在UseStartUp里面
 82             //UseStartUp<T>(),這個重載的方法里面是沒有加入這個程序集名稱的,只是設置一下UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName)
 83             if (!string.IsNullOrEmpty(_options.StartupAssembly))
 84             {
 85                 try
 86                 {
 87                     var startupType = StartupLoader.FindStartupType(_options.StartupAssembly, _hostingEnvironment.EnvironmentName);
 88 
 89                     if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo()))
 90                     {
 91                         services.AddSingleton(typeof(IStartup), startupType);
 92                     }
 93                     else
 94                     {
 95                         services.AddSingleton(typeof(IStartup), sp =>
 96                         {
 97                             var hostingEnvironment = sp.GetRequiredService<IHostingEnvironment>();
 98                             var methods = StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName);
 99                             return new ConventionBasedStartup(methods);
100                         });
101                     }
102                 }
103                 catch (Exception ex)
104                 {
105                     var capture = ExceptionDispatchInfo.Capture(ex);
106                     services.AddSingleton<IStartup>(_ =>
107                     {
108                         capture.Throw();
109                         return null;
110                     });
111                 }
112             }
113             //到這里就已經把說有的Service加到ServiceCollection
114             foreach (var configureServices in _configureServicesDelegates)
115             {
116                 configureServices(_context, services);
117             }
118 
119             return services;
120         }

 

 

var applicationServices = hostingServices.Clone();
var hostingServiceProvider = hostingServices.BuildServiceProvider();

AddApplicationServices(applicationServices, hostingServiceProvider)

這里就是新建一個IServiceCollection,把hostingServices里面的service復制一份到applicationService里面去,同時里面有一個

            var listener = new DiagnosticListener("Microsoft.AspNetCore");
            services.AddSingleton<DiagnosticListener>(listener);
            services.AddSingleton<DiagnosticSource>(listener);

 

這個listener,要保證在兩個container里面都是同一個。
 1         private void AddApplicationServices(IServiceCollection services, IServiceProvider hostingServiceProvider)
 2         {
 3             // We are forwarding services from hosting contrainer so hosting container
 4             // can still manage their lifetime (disposal) shared instances with application services.
 5             // NOTE: This code overrides original services lifetime. Instances would always be singleton in
 6             // application container.
 7             var listener = hostingServiceProvider.GetService<DiagnosticListener>();
 8             services.Replace(ServiceDescriptor.Singleton(typeof(DiagnosticListener), listener));
 9             services.Replace(ServiceDescriptor.Singleton(typeof(DiagnosticSource), listener));
10         }

 

最后是新建一個WebHost,然后是調用Initialize方法進行初始化的一些工作,我們最重要的就是看里面的初始化干了什么東西?

 
                //確保是否_applicationService是不是空的,空的話就調用IStartUp的ConfigServices方法獲得
                EnsureApplicationServices();
                //調用了UseUrls,則這里面把Server.Feature.Address的值賦值上去
                EnsureServer();

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

                var startupFilters = _applicationServices.GetService<IEnumerable<IStartupFilter>>();
                Action<IApplicationBuilder> configure = _startup.Configure;
                //查找是否 注冊了StartUpFilter,如果注冊了,則先調用過濾器
                foreach (var filter in startupFilters.Reverse())
                {
                    configure = filter.Configure(configure);
                }
                //調用第一個注冊的config的方法然后接連調用鏈條
                configure(builder);

                return builder.Build();

EnsureApplicationServices()的方法是最終調用StartUp類里面的ConfigService方法,把Service注入進去。

private void EnsureApplicationServices()
        {
            if (_applicationServices == null)
            {
                EnsureStartup();
//調用ConfigureServices方法把服務注冊進去(實際調用的是
ConventionBasedStartup這個類的ConfigService方法。
_applicationServices
= _startup.ConfigureServices(_applicationServiceCollection);
}
}

 

 

 

至此,build方法弄完了。

最后是Run方法:

                //這里時開始的方法。
await host.StartAsync(token); var hostingEnvironment = host.Services.GetService<IHostingEnvironment>(); var applicationLifetime = host.Services.GetService<IApplicationLifetime>(); Console.WriteLine($"Hosting environment: {hostingEnvironment.EnvironmentName}"); Console.WriteLine($"Content root path: {hostingEnvironment.ContentRootPath}");
//UseUrls時,傳入的url,這里回監聽這些地址。
var serverAddresses = host.ServerFeatures.Get<IServerAddressesFeature>()?.Addresses; if (serverAddresses != null) { foreach (var address in serverAddresses) { Console.WriteLine($"Now listening on: {address}"); } } if (!string.IsNullOrEmpty(shutdownMessage)) { Console.WriteLine(shutdownMessage); } await host.WaitForTokenShutdownAsync(token); }

 

StartAsync里面最終調用的就是IServer的Start方法。

 

最終的啟動流程就結束了!!!!。

 

最后感覺,這個啟動流程為了配合DI容器寫的好繞,里面很多的委托進行傳進傳出,看的人很暈!!

但是它帶來的靈活性也是巨大的,可以自定義很多地方,真正體現了萬物皆DI的思想! 


免責聲明!

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



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