本节介绍 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";

标签: none

添加新评论