Util應(yīng)用框架核心(二) - 啟動(dòng)器
本節(jié)介紹 Util 項(xiàng)目啟動(dòng)初始化過程.
文章分為多個(gè)小節(jié),如果對(duì)設(shè)計(jì)原理不感興趣,只需閱讀基礎(chǔ)用法部分即可.
基礎(chǔ)用法
查看 Util 服務(wù)配置,范例:
var builder = WebApplication.CreateBuilder( args );
builder.AsBuild()
.AddAop()
.AddSerilog()
.AddUtil();
注意其中調(diào)用了 AddUtil 方法.
AddUtil 方法調(diào)用啟動(dòng)器進(jìn)行初始化.
設(shè)計(jì)動(dòng)機(jī)
有些服務(wù)需要配置,但并不需要傳遞配置參數(shù).
對(duì)于這類服務(wù),我們希望自動(dòng)完成配置,而不是手工調(diào)用 AddXXX() 方法.
Util項(xiàng)目需要一種自動(dòng)執(zhí)行特定初始化代碼的方法.
Util啟動(dòng)時(shí)掃描全部程序集,找出特定代碼塊,并執(zhí)行它們.
這些被自動(dòng)執(zhí)行的代碼塊,稱為服務(wù)注冊器.
Util 啟動(dòng)器的設(shè)計(jì)和代碼主要從 NopCommerce 吸收而來,并在項(xiàng)目實(shí)戰(zhàn)中不斷改進(jìn).
采用程序集掃描,是一種簡單輕量的啟動(dòng)方式,不需要進(jìn)行任何配置.
源碼解析
AddUtil 擴(kuò)展方法
在 IHostBuilder 和 IAppBuilder 接口上擴(kuò)展了 AddUtil 方法.
AddUtil 方法調(diào)用 Bootstrapper 啟動(dòng)器的 Start 方法,掃描程序集執(zhí)行服務(wù)注冊器.
通常你不需要調(diào)用 Bootstrapper 類啟動(dòng),使用 AddUtil 擴(kuò)展方法會(huì)更簡單.
/// <summary>
/// 主機(jī)生成器服務(wù)擴(kuò)展
/// </summary>
public static class IHostBuilderExtensions {
/// <summary>
/// 啟動(dòng)Util服務(wù)
/// </summary>
/// <param name="hostBuilder">主機(jī)生成器</param>
public static IHostBuilder AddUtil( this IHostBuilder hostBuilder ) {
hostBuilder.CheckNull( nameof( hostBuilder ) );
var bootstrapper = new Bootstrapper( hostBuilder );
bootstrapper.Start();
return hostBuilder;
}
/// <summary>
/// 啟動(dòng)Util服務(wù)
/// </summary>
/// <param name="appBuilder">應(yīng)用生成器</param>
public static IAppBuilder AddUtil( this IAppBuilder appBuilder ) {
appBuilder.CheckNull( nameof( appBuilder ) );
var bootstrapper = new Bootstrapper( appBuilder.Host );
bootstrapper.Start();
return appBuilder;
}
}
Bootstrapper 啟動(dòng)器
啟動(dòng)器使用類型查找器 ITypeFinder 找出所有啟用的服務(wù)注冊器 IServiceRegistrar,并根據(jù) OrderId 屬性排序.
使用反射創(chuàng)建服務(wù)注冊器實(shí)例,并將主機(jī)生成器 IHostBuilder 實(shí)例傳遞給它.
執(zhí)行服務(wù)注冊器實(shí)例的 Register 方法,完成服務(wù)初始化工作.
/// <summary>
/// 啟動(dòng)器
/// </summary>
public class Bootstrapper {
/// <summary>
/// 主機(jī)生成器
/// </summary>
private readonly IHostBuilder _hostBuilder;
/// <summary>
/// 程序集查找器
/// </summary>
private readonly IAssemblyFinder _assemblyFinder;
/// <summary>
/// 類型查找器
/// </summary>
private readonly ITypeFinder _typeFinder;
/// <summary>
/// 服務(wù)配置操作列表
/// </summary>
private readonly List<Action> _serviceActions;
/// <summary>
/// 初始化啟動(dòng)器
/// </summary>
/// <param name="hostBuilder">主機(jī)生成器</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>
/// 啟動(dòng)
/// </summary>
public virtual void Start() {
ConfigureServices();
ResolveServiceRegistrar();
ExecuteServiceActions();
}
/// <summary>
/// 配置服務(wù)
/// </summary>
protected virtual void ConfigureServices() {
_hostBuilder.ConfigureServices( ( context, services ) => {
Util.Helpers.Config.SetConfiguration( context.Configuration );
services.TryAddSingleton( _assemblyFinder );
services.TryAddSingleton( _typeFinder );
} );
}
/// <summary>
/// 解析服務(wù)注冊器
/// </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>
/// 執(zhí)行延遲服務(wù)注冊操作
/// </summary>
protected virtual void ExecuteServiceActions() {
_serviceActions.ForEach( action => action?.Invoke() );
}
}
ITypeFinder 類型查找器
應(yīng)用程序域類型查找器 AppDomainTypeFinder 使用程序集查找器 IAssemblyFinder 獲取程序集列表.
并從程序集中查找指定接口的實(shí)現(xiàn)類型.
/// <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>
/// 應(yīng)用程序域類型查找器
/// </summary>
public class AppDomainTypeFinder : ITypeFinder {
/// <summary>
/// 程序集查找器
/// </summary>
private readonly IAssemblyFinder _assemblyFinder;
/// <summary>
/// 初始化應(yīng)用程序域類型查找器
/// </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 程序集查找器
應(yīng)用程序域程序集查找器 AppDomainAssemblyFinder 掃描當(dāng)前應(yīng)用程序域,獲取全部程序集.
值得注意的是,如果在應(yīng)用程序域所有程序集中進(jìn)行查找,必定效率十分低下,啟動(dòng)將異常緩慢.
我們掃描程序集的目的,是希望從中獲得服務(wù)注冊器.
只有Util應(yīng)用框架和你的項(xiàng)目相關(guān)的程序集中,才有可能包含服務(wù)注冊器.
所以排除掉 .Net 和第三方類庫程序集,將能大大提升掃描查找效率.
/// <summary>
/// 程序集查找器
/// </summary>
public interface IAssemblyFinder {
/// <summary>
/// 程序集過濾模式
/// </summary>
public string AssemblySkipPattern { get; set; }
/// <summary>
/// 查找程序集列表
/// </summary>
List<Assembly> Find();
}
/// <summary>
/// 應(yīng)用程序域程序集查找器
/// </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>
/// 加載引用但尚未調(diào)用的程序集列表到當(dāng)前應(yīng)用程序域
/// </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>
/// 加載程序集到當(dāng)前應(yīng)用程序域
/// </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應(yīng)用框架已經(jīng)排除了引用的所有依賴庫程序集.
但你的項(xiàng)目可能引用其它第三方類庫,如果只引用了少量類庫,影響非常小,但引用大量類庫,則必須配置程序集過濾列表.
如果你不想在每個(gè)項(xiàng)目配置程序集過濾,可以讓Util應(yīng)用框架更新過濾列表,請把要過濾的程序集名稱告訴我們.
Util.Infrastructure.BootstrapperConfig 是啟動(dòng)器配置, AssemblySkipPattern 屬性提供了程序集過濾列表.
程序集過濾列表是一個(gè)正則表達(dá)式,使用 | 分隔程序集,使用 ^ 匹配起始名稱過濾.
范例1
如果你想排除名為 Demo 的程序集.
BootstrapperConfig.AssemblySkipPattern += "|Demo";
builder.AsBuild().AddUtil();
必須在 AddUtil 之前設(shè)置 BootstrapperConfig.AssemblySkipPattern 屬性.
范例2
排除 Demo 開頭的程序集,比如 Demo.A,Demo.B .
BootstrapperConfig.AssemblySkipPattern += "|^Demo";

浙公網(wǎng)安備 33010602011771號(hào)