本節介紹 Util 專案啟動初始化過程.
文章分為多個小節,如果對設計原理不感興趣,只需閱讀基礎用法部分即可.
基礎用法
檢視 Util 服務配置,範例:
var builder = WebApplication.CreateBuilder( args );
builder.AsBuild()
.AddAop()
.AddSerilog()
.AddUtil();
注意其中呼叫了 AddUtil 方法.
AddUtil 方法呼叫啟動器進行初始化.
設計動機
有些服務需要配置,但並不需要傳遞配置引數.
對於這類服務,我們希望自動完成配置,而不是手工呼叫 AddXXX() 方法.
Util專案需要一種自動執行特定初始化程式碼的方法.
Util啟動時掃描全部程式集,找出特定程式碼塊,並執行它們.
這些被自動執行的程式碼塊,稱為服務註冊器.
Util 啟動器的設計和程式碼主要從 NopCommerce 吸收而來,並在專案實戰中不斷改進.
採用程式集掃描,是一種簡單輕量的啟動方式,不需要進行任何配置.
原始碼解析
AddUtil 擴充套件方法
在 IHostBuilder 和 IAppBuilder 介面上擴充套件了 AddUtil 方法.
AddUtil 方法呼叫 Bootstrapper 啟動器的 Start 方法,掃描程式集執行服務註冊器.
通常你不需要呼叫 Bootstrapper 類啟動,使用 AddUtil 擴充套件方法會更簡單.
/// <summary>
/// 主機生成器服務擴充套件
/// </summary>
public static class IHostBuilderExtensions {
/// <summary>
/// 啟動Util服務
/// </summary>
/// <param name="hostBuilder">主機生成器</param>
public static IHostBuilder AddUtil( this IHostBuilder hostBuilder ) {
hostBuilder.CheckNull( nameof( hostBuilder ) );
var bootstrapper = new Bootstrapper( hostBuilder );
bootstrapper.Start();
return hostBuilder;
}
/// <summary>
/// 啟動Util服務
/// </summary>
/// <param name="appBuilder">應用生成器</param>
public static IAppBuilder AddUtil( this IAppBuilder appBuilder ) {
appBuilder.CheckNull( nameof( appBuilder ) );
var bootstrapper = new Bootstrapper( appBuilder.Host );
bootstrapper.Start();
return appBuilder;
}
}
Bootstrapper 啟動器
啟動器使用型別查詢器 ITypeFinder 找出所有啟用的服務註冊器 IServiceRegistrar,並根據 OrderId 屬性排序.
使用反射建立服務註冊器例項,並將主機生成器 IHostBuilder 例項傳遞給它.
執行服務註冊器例項的 Register 方法,完成服務初始化工作.
/// <summary>
/// 啟動器
/// </summary>
public class Bootstrapper {
/// <summary>
/// 主機生成器
/// </summary>
private readonly IHostBuilder _hostBuilder;
/// <summary>
/// 程式集查詢器
/// </summary>
private readonly IAssemblyFinder _assemblyFinder;
/// <summary>
/// 型別查詢器
/// </summary>
private readonly ITypeFinder _typeFinder;
/// <summary>
/// 服務配置操作列表
/// </summary>
private readonly List<Action> _serviceActions;
/// <summary>
/// 初始化啟動器
/// </summary>
/// <param name="hostBuilder">主機生成器</param>
public Bootstrapper( IHostBuilder hostBuilder ) {
_hostBuilder = hostBuilder ?? throw new ArgumentNullException( nameof( hostBuilder ) );
_assemblyFinder = new AppDomainAssemblyFinder { AssemblySkipPattern = BootstrapperConfig.AssemblySkipPattern };
_typeFinder = new AppDomainTypeFinder( _assemblyFinder );
_serviceActions = new List<Action>();
}
/// <summary>
/// 啟動
/// </summary>
public virtual void Start() {
ConfigureServices();
ResolveServiceRegistrar();
ExecuteServiceActions();
}
/// <summary>
/// 配置服務
/// </summary>
protected virtual void ConfigureServices() {
_hostBuilder.ConfigureServices( ( context, services ) => {
Util.Helpers.Config.SetConfiguration( context.Configuration );
services.TryAddSingleton( _assemblyFinder );
services.TryAddSingleton( _typeFinder );
} );
}
/// <summary>
/// 解析服務註冊器
/// </summary>
protected virtual void ResolveServiceRegistrar() {
var types = _typeFinder.Find<IServiceRegistrar>();
var instances = types.Select( type => Reflection.CreateInstance<IServiceRegistrar>( type ) ).Where( t => t.Enabled ).OrderBy( t => t.OrderId ).ToList();
var context = new ServiceContext( _hostBuilder, _assemblyFinder, _typeFinder );
instances.ForEach( t => _serviceActions.Add( t.Register( context ) ) );
}
/// <summary>
/// 執行延遲服務註冊操作
/// </summary>
protected virtual void ExecuteServiceActions() {
_serviceActions.ForEach( action => action?.Invoke() );
}
}
ITypeFinder 型別查詢器
應用程式域型別查詢器 AppDomainTypeFinder 使用程式集查詢器 IAssemblyFinder 獲取程式集列表.
並從程式集中查詢指定介面的實現型別.
/// <summary>
/// 型別查詢器
/// </summary>
public interface ITypeFinder {
/// <summary>
/// 查詢型別列表
/// </summary>
/// <typeparam name="T">查詢型別</typeparam>
List<Type> Find<T>();
/// <summary>
/// 查詢型別列表
/// </summary>
/// <param name="findType">查詢型別</param>
List<Type> Find( Type findType );
}
/// <summary>
/// 應用程式域型別查詢器
/// </summary>
public class AppDomainTypeFinder : ITypeFinder {
/// <summary>
/// 程式集查詢器
/// </summary>
private readonly IAssemblyFinder _assemblyFinder;
/// <summary>
/// 初始化應用程式域型別查詢器
/// </summary>
/// <param name="assemblyFinder">程式集查詢器</param>
public AppDomainTypeFinder( IAssemblyFinder assemblyFinder ) {
_assemblyFinder = assemblyFinder ?? throw new ArgumentNullException( nameof( assemblyFinder ) );
}
/// <summary>
/// 查詢型別列表
/// </summary>
/// <typeparam name="T">查詢型別</typeparam>
public List<Type> Find<T>() {
return Find( typeof( T ) );
}
/// <summary>
/// 獲取程式集列表
/// </summary>
public List<Assembly> GetAssemblies() {
return _assemblyFinder.Find();
}
/// <summary>
/// 查詢型別列表
/// </summary>
/// <param name="findType">查詢型別</param>
public List<Type> Find( Type findType ) {
return Reflection.FindImplementTypes( findType, GetAssemblies()?.ToArray() );
}
}
IAssemblyFinder 程式集查詢器
應用程式域程式集查詢器 AppDomainAssemblyFinder 掃描當前應用程式域,獲取全部程式集.
值得注意的是,如果在應用程式域所有程式集中進行查詢,必定效率十分低下,啟動將異常緩慢.
我們掃描程式集的目的,是希望從中獲得服務註冊器.
只有Util應用框架和你的專案相關的程式集中,才有可能包含服務註冊器.
所以排除掉 .Net 和第三方類庫程式集,將能大大提升掃描查詢效率.
/// <summary>
/// 程式集查詢器
/// </summary>
public interface IAssemblyFinder {
/// <summary>
/// 程式集過濾模式
/// </summary>
public string AssemblySkipPattern { get; set; }
/// <summary>
/// 查詢程式集列表
/// </summary>
List<Assembly> Find();
}
/// <summary>
/// 應用程式域程式集查詢器
/// </summary>
public class AppDomainAssemblyFinder : IAssemblyFinder {
/// <summary>
/// 程式集過濾模式
/// </summary>
public string AssemblySkipPattern { get; set; }
/// <summary>
/// 程式集列表
/// </summary>
private List<Assembly> _assemblies;
/// <summary>
/// 獲取程式集列表
/// </summary>
public List<Assembly> Find() {
if ( _assemblies != null )
return _assemblies;
_assemblies = new List<Assembly>();
LoadAssemblies();
foreach( var assembly in AppDomain.CurrentDomain.GetAssemblies() ) {
if( IsSkip( assembly ) )
continue;
_assemblies.Add( assembly );
}
return _assemblies;
}
/// <summary>
/// 載入引用但尚未呼叫的程式集列表到當前應用程式域
/// </summary>
protected virtual void LoadAssemblies() {
var currentDomainAssemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach( string file in GetLoadAssemblyFiles() )
LoadAssembly( file, currentDomainAssemblies );
}
/// <summary>
/// 獲取需要載入的程式集檔案列表
/// </summary>
protected virtual string[] GetLoadAssemblyFiles() {
return Directory.GetFiles( AppContext.BaseDirectory, "*.dll" );
}
/// <summary>
/// 載入程式集到當前應用程式域
/// </summary>
protected void LoadAssembly( string file, Assembly[] currentDomainAssemblies ) {
try {
var assemblyName = AssemblyName.GetAssemblyName( file );
if( IsSkip( assemblyName.Name ) )
return;
if( currentDomainAssemblies.Any( t => t.FullName == assemblyName.FullName ) )
return;
AppDomain.CurrentDomain.Load( assemblyName );
}
catch( BadImageFormatException ) {
}
}
/// <summary>
/// 是否過濾程式集
/// </summary>
protected bool IsSkip( string assemblyName ) {
var applicationName = Assembly.GetEntryAssembly()?.GetName().Name;
if ( assemblyName.StartsWith( $"{applicationName}.Views" ) )
return true;
if( assemblyName.StartsWith( $"{applicationName}.PrecompiledViews" ) )
return true;
if ( string.IsNullOrWhiteSpace( AssemblySkipPattern ) )
return false;
return Regex.IsMatch( assemblyName, AssemblySkipPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled );
}
/// <summary>
/// 是否過濾程式集
/// </summary>
private bool IsSkip( Assembly assembly ) {
return IsSkip( assembly.FullName );
}
}
配置程式集過濾列表
Util應用框架已經排除了引用的所有依賴庫程式集.
但你的專案可能引用其它第三方類庫,如果只引用了少量類庫,影響非常小,但引用大量類庫,則必須配置程式集過濾列表.
如果你不想在每個專案配置程式集過濾,可以讓Util應用框架更新過濾列表,請把要過濾的程式集名稱告訴我們.
Util.Infrastructure.BootstrapperConfig 是啟動器配置, AssemblySkipPattern 屬性提供了程式集過濾列表.
程式集過濾列表是一個正規表示式,使用 | 分隔程式集,使用 ^ 匹配起始名稱過濾.
範例1
如果你想排除名為 Demo 的程式集.
BootstrapperConfig.AssemblySkipPattern += "|Demo";
builder.AsBuild().AddUtil();
必須在 AddUtil 之前設定 BootstrapperConfig.AssemblySkipPattern 屬性.
範例2
排除 Demo 開頭的程式集,比如 Demo.A,Demo.B .
BootstrapperConfig.AssemblySkipPattern += "|^Demo";