05、NetCore2.0依赖注入(DI)之Web应用启动流程管理
在一个Asp.net core 2.0 Web应用程序中,启动过程都做了些什么?NetCore2.0的依赖注入(DI)框架是如何管理启动过程的?WebServer和Startup是如何注册的?
------------------------------------------------------------------------------------------------------------
写在前面:这是一个系列的文章,总目录请移步:NetCore2.0技术文章目录
------------------------------------------------------------------------------------------------------------
一、我们先看看依赖注入框架是如何使用的
NetCore2.0的依赖注入(DI)框架是要解决对象创建的问题,把创建对象与使用对象进行解耦。调用者不需要关心对象是单例的还是多实例的;服务的扩展和调用也更容易。
首先使用VS2017新建一个控制台程序,要使用依赖注入(DI)框架,我们需要引入微软的依赖注入包:
install-package Microsoft.Extensions.DependencyInjection
我们声明一个自己的接口,并实现一个类
// 接口 interface IRun { void Run(); } // 实现类 class Run : IRun { void IRun.Run() { Console.WriteLine("跑起来,兄弟"); } }
使用DI框架来注册接口和类的实例;并通过服务提供者来访问接口
using Microsoft.Extensions.DependencyInjection; using System; namespace MyServiceBus { class Program { static void Main(string[] args) { // 实例化DI框架 IServiceCollection services = new ServiceCollection(); // 在DI框架中加入接口的一个实例(它是单例的) services.AddSingleton<IRun, Run>(); // 服务的提供者 IServiceProvider serviceProvider = services.BuildServiceProvider(); // ============上下两部分代码一般不会同时出现在一个类中======== // 从服务提供者获取接口的实例(不用关心是如何创建的) serviceProvider.GetService<IRun>().Run(); Console.ReadLine(); } } }
看看运行效果吧!可以看出,IRun业务的调用方,不需要关心是如何实例化的。
二、DI框架如何管理Asp.NetCore2.0 Web应用的启动过程
一个极简的Web应用程序一般是这样的:
using Microsoft.AspNetCore.Hosting; namespace MyWebApi { class Program { static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .UseStartup<StartUp>() .Build(); host.Run(); } } }
从上面的代码中判断,DI框架的初始化和接口注册应该是在WebHostBuild.Build()方法中完成的,从命名就能看出,这是一个建造者模式,把内部复杂的构建方式隐藏了。我们去看一下这个方法的开源代码:
// 为了说明问题,代码略作调整,保留核心代码
public IWebHost Build() { // 初始化DI框架:估计里面预制了一些服务 IServiceCollection hostingServices = BuildCommonServices(out var hostingStartupErrors); IServiceCollection applicationServices = hostingServices.Clone();
// 服务的提供者 ServiceProvider hostingServiceProvider = hostingServices.BuildServiceProvider(); AddApplicationServices(applicationServices, hostingServiceProvider); var host = new WebHost( applicationServices, hostingServiceProvider, _options, _config, hostingStartupErrors); host.Initialize(); return host; }
我们可以看到在WebHostBuild.Build()方法中,显示的初始化了DI框架,我们看一下DI框架初始化方法的源码,可以发现确实预制了一些服务:
// 为了说明问题,代码略作调整,保留核心代码 private IServiceCollection BuildCommonServices(out AggregateException hostingStartupErrors) { // 配置选项 _options = new WebHostOptions(_config); var contentRootPath = ResolveContentRootPath(_options.ContentRootPath, AppContext.BaseDirectory); var applicationName = _options.ApplicationName; // Initialize the hosting environment _hostingEnvironment.Initialize(applicationName, contentRootPath, _options); _context.HostingEnvironment = _hostingEnvironment; // 实例化DI框架 var services = new ServiceCollection(); // 预制环境参数服务到框架中 services.AddSingleton(_hostingEnvironment); // 预制上下文插件到框架中 services.AddSingleton(_context); // 预制配置管理服务到框架中 var builder = new ConfigurationBuilder() .SetBasePath(_hostingEnvironment.ContentRootPath) .AddInMemoryCollection(_config.AsEnumerable()); var configuration = builder.Build(); services.AddSingleton<IConfiguration>(configuration); _context.Configuration = configuration; // 预制诊断服务到框架中 var listener = new DiagnosticListener("Microsoft.AspNetCore"); services.AddSingleton<DiagnosticListener>(listener); services.AddSingleton<DiagnosticSource>(listener); // 预制其他服务到框架中 services.AddTransient<IApplicationBuilderFactory, ApplicationBuilderFactory>(); services.AddTransient<IHttpContextFactory, HttpContextFactory>(); services.AddScoped<IMiddlewareFactory, MiddlewareFactory>(); services.AddOptions(); services.AddLogging(); // Conjure up a RequestServices services.AddTransient<IStartupFilter, AutoRequestServicesStartupFilter>(); services.AddTransient<IServiceProviderFactory<IServiceCollection>, DefaultServiceProviderFactory>(); // Ensure object pooling is available everywhere. services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>(); return services; }
从代码分析看WebHostBuilder做的事如下:
1. 定义了一些WebHost的配置项
2. 创建依赖注入的容器, 并预制一些service
三、其中WebServer就是作为服务进行注入的
回头再看WebServer的注册,使用的是UseKestrel:
using Microsoft.AspNetCore.Hosting;
namespace MyWebApi
{
class Program
{
static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseStartup<StartUp>()
.Build();
host.Run();
}
}
}
猜测也是作为服务进行注入的,我们来看Kestrel的开源代码:
public static IWebHostBuilder UseKestrel(this IWebHostBuilder hostBuilder) { hostBuilder.UseLibuv(); return hostBuilder.ConfigureServices(services => { services.AddTransient<IConfigureOptions<KestrelServerOptions>, KestrelServerOptionsSetup>(); services.AddSingleton<IServer, KestrelServer>(); }); }
从中能够看出WebServer是作为IServer接口进行注入的,而且是单例模式。
四、不难推断Startup也是作为服务进行注入的
极简的Web应用程序一般是这样的:
using Microsoft.AspNetCore.Hosting;
namespace MyWebApi
{
class Program
{
static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseStartup<StartUp>()
.Build();
host.Run();
}
}
}
我们来看开源代码:
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是作为IStartup接口进行注入的,而且是单例模式。
至此一个简单网站的初始化过程我们就基本清楚了!