赞
踩
Util应用框架的启动初始化过程
本节介绍 Util 项目启动初始化过程.
文章分为多个小节,如果对设计原理不感兴趣,只需阅读基础用法部分即可.
查看 Util 服务配置,范例:
var builder = WebApplication.CreateBuilder( args );
builder.AsBuild()
.AddAop()
.AddSerilog()
.AddUtil();
注意其中调用了 AddUtil 方法.
AddUtil 方法调用启动器进行初始化.
有些服务需要配置,但并不需要传递配置参数.
对于这类服务,我们希望自动完成配置,而不是手工调用 AddXXX() 方法.
Util项目需要一种自动执行特定初始化代码的方法.
Util启动时扫描全部程序集,找出特定代码块,并执行它们.
这些被自动执行的代码块,称为服务注册器.
Util 启动器的设计和代码主要从 NopCommerce 吸收而来,并在项目实战中不断改进.
采用程序集扫描,是一种简单轻量的启动方式,不需要进行任何配置.
在 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; } }
启动器使用类型查找器 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() ); } }
应用程序域类型查找器 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() ); } }
应用程序域程序集查找器 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";
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。