Util应用框架核心(二)- 启动器

启动器

Util应用框架的启动初始化过程

本节介绍 Util 项目启动初始化过程.

文章分为多个小节,如果对设计原理不感兴趣,只需阅读基础用法部分即可.

基础用法

查看 Util 服务配置,范例:

c# 复制代码
var builder = WebApplication.CreateBuilder( args );
builder.AsBuild()
  .AddAop()
  .AddSerilog()
  .AddUtil();

注意其中调用了 AddUtil 方法.

AddUtil 方法调用启动器进行初始化.

设计动机

有些服务需要配置,但并不需要传递配置参数.

对于这类服务,我们希望自动完成配置,而不是手工调用 AddXXX() 方法.

Util项目需要一种自动执行特定初始化代码的方法.

Util启动时扫描全部程序集,找出特定代码块,并执行它们.

这些被自动执行的代码块,称为服务注册器.

Util 启动器的设计和代码主要从 NopCommerce 吸收而来,并在项目实战中不断改进.

采用程序集扫描,是一种简单轻量的启动方式,不需要进行任何配置.

源码解析

AddUtil 扩展方法

IHostBuilderIAppBuilder 接口上扩展了 AddUtil 方法.

AddUtil 方法调用 Bootstrapper 启动器的 Start 方法,扫描程序集执行服务注册器.

通常你不需要调用 Bootstrapper 类启动,使用 AddUtil 扩展方法会更简单.

c# 复制代码
/// <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 方法,完成服务初始化工作.

c# 复制代码
/// <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 获取程序集列表.

并从程序集中查找指定接口的实现类型.

c# 复制代码
/// <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 和第三方类库程序集,将能大大提升扫描查找效率.

c# 复制代码
/// <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 的程序集.

ini 复制代码
BootstrapperConfig.AssemblySkipPattern += "|Demo";

builder.AsBuild().AddUtil();

必须在 AddUtil 之前设置 BootstrapperConfig.AssemblySkipPattern 属性.

范例2

排除 Demo 开头的程序集,比如 Demo.A,Demo.B .

arduino 复制代码
BootstrapperConfig.AssemblySkipPattern += "|^Demo";
相关推荐
二等饼干~za8986681 天前
从零搭建 Geo 开源项目源码开发环境——以 GeoServer 为例
开源
极客BIM工作室1 天前
IfcOpenShell:开源BIM的核心引擎技术解析
开源
lularible1 天前
从沙子到车辙(3.3):数据通路与控制器的“双人舞“
开源·嵌入式·汽车电子
数据法师1 天前
MotrixNext:接棒经典 Motrix,用 Tauri 2+Rust 重构的下一代开源下载神器
重构·rust·开源
码途漫谈1 天前
让 AI 编程不断线:9Router 的本地模型路由与 Token 节流术
人工智能·ai·开源·ai编程
幽络源小助理2 天前
全新UI 阅后即焚V2正式版系统源码_全开源_安全加密传输
安全·ui·开源·php源码
lularible2 天前
从沙子到车辙(3.5):存储层次
开源·嵌入式·汽车电子
lularible2 天前
从沙子到车辙(3.4):流水线——指令级并行的艺术
开源·嵌入式·汽车电子
2601_955781982 天前
整合Kimi 大模型 OpenClaw 自动化能力再度升级
开源·github·kimi·open claw安装·open claw部署