Util應用框架核心(二) - 啓動器

本節介紹 Util 項目啓動初始化過程.

文章分爲多個小節,如果對設計原理不感興趣,只需閱讀基礎用法部分即可.

基礎用法

查看 Util 服務配置,範例:

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 擴展方法會更簡單.

/// <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";
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章