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 的程序集.

BootstrapperConfig.AssemblySkipPattern += "|Demo";

builder.AsBuild().AddUtil();

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

范例2

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

BootstrapperConfig.AssemblySkipPattern += "|^Demo";