在註冊Silky微服務應用一節中,我們瞭解到在ConfigureServices
階段,通過IServiceCollection
的擴充套件方法AddSilkyServices<T>()
除了註冊必要的服務之外,更主要的是構建了服務引擎(IEngine
)。
下面,我們學習在IServiceCollection
的擴充套件方法AddSilkyServices<T>()
中完成了什麼樣的工作。如下所示的程式碼為在包 Silky.Core 的 ServiceCollectionExtensions.cs中提供的擴充套件方法AddSilkyServices<T>()
。
public static IEngine AddSilkyServices<T>(this IServiceCollection services, IConfiguration configuration,
IHostEnvironment hostEnvironment) where T : StartUpModule
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; // 指定通訊管道的加密傳輸協定
CommonSilkyHelpers.DefaultFileProvider = new SilkyFileProvider(hostEnvironment); // 構建檔案服務提供者
services.TryAddSingleton(CommonSilkyHelpers.DefaultFileProvider); // 向services註冊單例的檔案服務提供者
var engine = EngineContext.Create(); // 建立單例的服務引擎
services.AddOptions<AppSettingsOptions>()
.Bind(configuration.GetSection(AppSettingsOptions.AppSettings)); // 新增AppSettingsOptions設定
var moduleLoader = new ModuleLoader(); // 建立模組載入器
engine.LoadModules<T>(services, moduleLoader); // 載入所有模組
services.TryAddSingleton<IModuleLoader>(moduleLoader); // 註冊單例的模組載入器
services.AddHostedService<InitSilkyHostedService>(); // 註冊 InitSilkyHostedService 後臺任務服務,該服務用於初始化各個模組的任務或是在應用停止時釋放模組資源
services.AddSingleton<ICancellationTokenProvider>(NullCancellationTokenProvider.Instance); //註冊預設的CancellationTokenProvider
engine.ConfigureServices(services, configuration, hostEnvironment); // 通過服務引擎掃描所有IConfigureService介面的類,其實現類可以通過IServiceCollection對服務進行註冊;以及通過各個模組的ConfigureServices方法對服務進行註冊
return engine; // 返回服務引擎物件
}
建立服務引擎的物件方法如下所示,我們可以看出,服務引擎在整個應用的生命週期是全域性單例的。
internal static IEngine Create()
{
return Singleton<IEngine>.Instance ?? (Singleton<IEngine>.Instance = new SilkyEngine()); // 服務引擎在應用的整個生命週期是單例的
}
通過我們對上述程式碼註釋可以看出,在AddSilkyServices<T>()
方法中,在該方法中做了如下關鍵性的工作:
構建了一個關鍵性的物件 檔案服務提供者(SilkyFileProvider
) ,該物件主要用於掃描或是獲取指定的檔案(例如應用程式集等)以及提供資料夾等幫助方法;
使用EngineContext
建立了服務引擎物件SilkyEngine
物件;
使用IServiceCollection
註冊了必要的核心的物件,如:SilkyFileProvider
、ModuleLoader
、NullCancellationTokenProvider
等;
建立模組載入器ModuleLoader
物件,並通過服務引擎解析、載入silky模組,需要指出的是,在這裡我們需要指定啟動模組,系統會根據啟動模組指定的依賴關係進行排序;
註冊後臺任務服務InitSilkyHostedService
,該服務用於初始化各個模組的任務或是在應用停止時釋放模組資源;在各個模組的初始化工作中完成了很多核心的工作,例如:對應用服務以及服務條目的解析、服務後設資料的註冊、服務範例的註冊與更新、Rpc訊息監聽者的啟動等等;
在呼叫服務引擎的ConfigureServices()
方法時,通過服務引擎掃描所有IConfigureService
介面的類,通過反射建立實現類的物件,通過IServiceCollection
對服務進行註冊;以及通過遍歷所有的Silky模組範例,通過模組的提供的ConfigureServices()
的方法通過IServiceCollection
對服務進行註冊。
提示
如果熟悉 nopCommerce 框架的小夥伴們應該注意到,
SilkyEngine
服務引擎的作用與構建與該框架的設計基本是一致的。
服務引擎的SilkyEngine
的作用主要由如下幾點:
通過模組載入器ModuleLoader
解析和載入模組,關於模組如何解析和載入,請檢視下一節模組內容;
實現服務的依賴注入,本質上來說要麼通過IServiceCollection
服務實現服務註冊,要麼通過Autufac提供的ContainerBuilder
實現服務註冊;
服務引擎實現服務的依賴注入主要由如下幾種方式實現:
2.1 通過掃描所有IConfigureService
介面的類,並通過反射的方式構建實現類的物件,然後可以通過IServiceCollection
對服務進行註冊;以及通過遍歷所有的Silky模組範例,通過模組的提供的ConfigureServices()
的方法通過IServiceCollection
對服務進行註冊。
如下程式碼為服務引擎提供的ConfigureServices()
方法原始碼:
// SilkyEngine 實現的ConfigureServices註冊服務的方法
public void ConfigureServices(IServiceCollection services, IConfiguration configuration,
IHostEnvironment hostEnvironment)
{
_typeFinder = new SilkyAppTypeFinder(); // 建立型別查詢器
ServiceProvider = services.BuildServiceProvider();
Configuration = configuration;
HostEnvironment = hostEnvironment;
HostName = Assembly.GetEntryAssembly()?.GetName().Name; // 解析應用服務主機名稱
var configureServices = _typeFinder.FindClassesOfType<IConfigureService>(); // 通過查詢器查詢所有的`IConfigureService`實現類
var instances = configureServices
.Select(configureService => (IConfigureService)Activator.CreateInstance(configureService)); // 通過反射的方式建立`IConfigureService`實現類的範例
foreach (var instance in instances) // 遍歷`IConfigureService`的實現類的範例,並通過其範例實現通過IServiceCollection對服務的註冊
instance.ConfigureServices(services, configuration);
// configure modules
foreach (var module in Modules) // 遍歷各個模組,通過各個模組提供`ConfigureServices`實現服務的註冊
module.Instance.ConfigureServices(services, configuration);
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
}
在上述程式碼中,我們可以看到在該方法體內主要完成如下工作:
A) 建立型別查詢器、構建服務提供者以及為設定器、主機環境變數、主機名等賦值;
B) 使用型別查詢器查詢到所有IConfigureService
實現類,並通過反射的方式建立其範例,遍歷其範例,其範例通過IServiceCollection
實現對服務的註冊;
C) 遍歷所有的模組,通過模組的範例提供的ConfigureServices()
方法,通過IServiceCollection
實現對服務的註冊;
2.2 在上一章註冊silky微服務應用中有指出, 執行ContainerBuilder
方法時,主要通過Autofac
的ContainerBuilder
實現服務的依賴註冊。
public static IHostBuilder RegisterSilkyServices<T>(this IHostBuilder builder)
where T : StartUpModule
{
// 其他程式碼略...
builder
.UseServiceProviderFactory(new AutofacServiceProviderFactory()) // 替換服務提供者工作類
.ConfigureContainer<ContainerBuilder>(builder => // 通過ContainerBuilder實現服務依賴註冊
{
engine.RegisterModules(services, builder);
engine.RegisterDependencies(builder);
})
}
我們看到,如何通過ContainerBuilder
實現服務註冊,也是通過服務引擎巧妙的實現:一種方式是通過模組,另外一種方式是通過約定的依賴方式。
2.2.1 通過模組註冊服務
在SilkyModule
的定義中,我們看到模組的基礎類別是Autofac.Module
,我們在遍歷所有的模組範例的過程中,通過ContainerBuilder
提供的RegisterModule()
方法實現模組指定的服務的註冊。換句話說,就是在在執行RegisterModule()
的方法過程中,Autofac會呼叫模組的提供的RegisterServices(ContainerBuilder builder)
實現具體的服務註冊。
public void RegisterModules(IServiceCollection services, ContainerBuilder containerBuilder)
{
containerBuilder.RegisterInstance(this).As<IModuleContainer>().SingleInstance();
var assemblyNames = ((AppDomainTypeFinder)_typeFinder).AssemblyNames;
foreach (var module in Modules)
{
if (!assemblyNames.Contains(module.Assembly.FullName))
{
((AppDomainTypeFinder)_typeFinder).AssemblyNames.Add(module.Assembly.FullName);
}
containerBuilder.RegisterModule((SilkyModule)module.Instance);
}
}
所以在Silky模組的定義SilkyModule中,提供瞭如下虛方法(RegisterServices
),實際上是Autofac的基礎類別Autofac.Module
的一個基礎方法,在呼叫containerBuilder.RegisterModule((SilkyModule)module.Instance)
時,底層會通過呼叫模組的Load()
實現模組的具體服務的註冊。在Load()
方法中,每個模組會呼叫RegisterServices(builder)
實現通過ContainerBuilder
對服務進行註冊。
protected override void Load([NotNull] ContainerBuilder builder)
{
base.Load(builder);
RegisterServices(builder);
}
所以,Silky具體的模組可以通過重寫RegisterServices([NotNull] ContainerBuilder builder)
實現該模組使用ContainerBuilder
實現服務的依賴註冊。
protected virtual void RegisterServices([NotNull] ContainerBuilder builder)
{
}
提示
使用ContainerBuilder
實現服務的註冊和通過IServiceCollection
實現服務的註冊的效果是一致的;使用ContainerBuilder
實現服務的註冊的優勢在於支援命名服務的註冊。也就是在服務註冊的過程中,可以給服務起個名字,在服務解析的過程中,通過名稱去解析到指定名稱的介面的實現的物件。
2.2.2 通過約定註冊服務
服務引擎SilkyEngine
通過呼叫RegisterDependencies()
方法,使用ContainerBuilder
實現對約定的規範的服務進行註冊。
public void RegisterDependencies(ContainerBuilder containerBuilder)
{
containerBuilder.RegisterInstance(this).As<IEngine>().SingleInstance();
containerBuilder.RegisterInstance(_typeFinder).As<ITypeFinder>().SingleInstance();
var dependencyRegistrars = _typeFinder.FindClassesOfType<IDependencyRegistrar>();
var instances = dependencyRegistrars
.Select(dependencyRegistrar => (IDependencyRegistrar)Activator.CreateInstance(dependencyRegistrar))
.OrderBy(dependencyRegistrar => dependencyRegistrar.Order);
foreach (var dependencyRegistrar in instances)
dependencyRegistrar.Register(containerBuilder, _typeFinder);
}
在上面的程式碼中,我們看到通過構建約定註冊器(IDependencyRegistrar
)的範例,通過約定註冊器實現指定服務的註冊。系統存在兩個預設的約定註冊器:
(1) DefaultDependencyRegistrar,該服務註冊器可以實現對標識介面的服務註冊;
A) 對繼承ISingletonDependency
的類註冊為單例;
B) 對繼承ITransientDependency
的類註冊為瞬態;
C) 對繼承IScopedDependency
的類註冊為範圍;
(2) NamedServiceDependencyRegistrar 實現了對命名服務的註冊;在某個類繼承上述標識介面時,如果通過InjectNamedAttribute
特性對服務進行命名,那麼該服務的將會被命名為該名稱的服務,在解析該服務的時候,可以通過名稱進行解析。
例如:
// 該服務將會被註冊為範圍的,並被命名為:DemoService,在服務解析過程中可以通過服務名 DemoService 解析到
[InjectNamed("DemoService")]
public class DemoService : IScopedDependency
{
}
服務引擎提供了多種判斷服務是否註冊以及服務解析方法;
服務引擎提供了獲取指定的設定項的方法;
可以通過服務引擎獲取型別查詢器(TypeFinder
)、服務設定器(Configuration
)、主機環境變數提供者(IHostEnvironment
)、以及主機名(HostName
)等資訊。
在開發過程中,可以通過EngineContext.Current
獲取服務引擎,並使用服務引擎提供的各個方法,例如:判斷服務是否註冊、解析服務、獲取設定類、獲取當前原因的主機名稱、或是使用型別查詢器(TypeFinder
)、服務設定器(Configuration
)、主機環境變數提供者(IHostEnvironment
)等。
提示
在開發過程中,使用服務引擎的大部分場景是,在不方便實現對某個服務進行構造注入的場景下,通過服務引擎實現對某個服務解析,從而得到該服務的範例。