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

Util应用框架核心(二) - 启动器的更多相关文章

  1. 图解Disruptor框架(二):核心概念

    图解Disruptor框架(二):核心概念 概述 上一个章节简单的介绍了了下Disruptor,这节就是要好好的理清楚Disruptor中的核心的概念.并且会给出个HelloWorld的小例子. 在正 ...

  2. MVC系列——MVC源码学习:打造自己的MVC框架(二:附源码)

    前言:上篇介绍了下 MVC5 的核心原理,整篇文章比较偏理论,所以相对比较枯燥.今天就来根据上篇的理论一步一步进行实践,通过自己写的一个简易MVC框架逐步理解,相信通过这一篇的实践,你会对MVC有一个 ...

  3. 【原创】NIO框架入门(二):服务端基于MINA2的UDP双向通信Demo演示

    前言 NIO框架的流行,使得开发大并发.高性能的互联网服务端成为可能.这其中最流行的无非就是MINA和Netty了,MINA目前的主要版本是MINA2.而Netty的主要版本是Netty3和Netty ...

  4. 游戏UI框架设计(二) : 最简版本设计

    游戏UI框架设计(二) --最简版本设计 为降低难度决定先讲解一个最简版本,阐述UI框架的核心设计理念.这里先定义三个核心功能: 1:UI窗体的自动加载功能. 2:缓存UI窗体. 3:窗体生命周期(状 ...

  5. CI框架 -- 核心文件 之 Hooks.php

    钩子 - 扩展框架核心 CodeIgniter 的钩子特性提供了一种方法来修改框架的内部运作流程,而无需修改 核心文件.CodeIgniter 的运行遵循着一个特定的流程,你可以参考这个页面的 应用程 ...

  6. Robot Framework自动化测试框架核心指南-如何使用Java编写自定义的RobotFramework Lib

    如何使用Java编写自定义的RobotFramework Lib 本文包括2个章节 1. Robot Frdamwork中如何调用java Lib库 2.使用 java编写自定义的Lib 本文作者为: ...

  7. Robot Framework自动化测试框架核心指南-如何做好自动化测试平台框架的设计

    自动化测试如果需要能高效快速的支撑软件项目的测试,项目的快速迭代以及上线,除了以上我们介绍的需要许多的Lib来支持以及需要高效的去编写自动化测试案例外,还需要一个好的自动化测试框架平台来支撑我们的自动 ...

  8. JavaScript 框架设计(二)

    JavaScript 高级框架设计 (二) 上一篇,JavaScript高级框架设计(一)我们 实现了对tag标签的选择 下来我们实现对id的选择,即id选择器. 我们将上一篇的get命名为getTa ...

  9. “Zhuang.Data”轻型数据库访问框架(二)框架的入口DbAccessor对象

    目录: “Zhuang.Data”轻型数据库访问框架(一)开篇介绍 “Zhuang.Data”轻型数据库访问框架(二)框架的入口DbAccessor对象 先来看一段代码 DbAccessor dba ...

  10. Struts2框架学习(二) Action

    Struts2框架学习(二) Action Struts2框架中的Action类是一个单独的javabean对象.不像Struts1中还要去继承HttpServlet,耦合度减小了. 1,流程 拦截器 ...

随机推荐

  1. 【阅读笔记】超分之LANR-NLM算法

    论文信息 [Single Image Super-Resolution via Locally Regularized Anchored Neighborhood Regression and Non ...

  2. Matlab2014a 找不到 vs2015编译器解决方法

    准备工作 前提: 电脑已经安装 1.Matlab版本2014a 2.VS版本2015 目标: 结合Matlab和VS2015,实现Matlab的GUI文件和.m文件转化为.exe文件,然后可以单独运行 ...

  3. Elementary OS old version download 旧版本下载

    Elementary OS 号称是最漂亮的Linux发行版,没有之一.确实,他的整体风格看起来就是特别舒服,说不出哪里特别好,但也挑不出什么毛病.相比之下,其他Linux的界面总感觉不太和谐.比如特别 ...

  4. Linux 上 KVM 虚拟机网络问题

    通过控制台连接虚拟机,ping自己的ip,ping宿主机的ip,ping同网段的ip 1. 自己的ip也不通,先检查网络配置 2. 宿主机的ip不通,就要确认下虚拟机网卡的类型 对于macvlan网卡 ...

  5. openpyxl 设置某列单元格样式

    1 # 边框线 2 border_set = Border(left=Side(border_style='thin', color='000000'), 3 right=Side(border_st ...

  6. node.js中kafka的封装和高并发消费限流优雅降级以及egg-kafka的封装说明

    HI!,你好,我是zane,zanePerfor是一款我开发的一个前端性能监控平台,现在支持web浏览器端和微信小程序端. 我定义为一款完整,高性能,高可用的前端性能监控系统,这是未来会达到的目的,现 ...

  7. 【博客重构之路】webman-admin安装指南

    原文地址[博客重构之路]webman-admin安装指南 视频地址[bilibili] webman是什么 webman是一款基于workerman开发的高性能HTTP服务框架.webman用于替代传 ...

  8. Redis从入门到放弃(9):集群模式

    前面文章我们介绍了Redis的主从模式是一种在Redis中实现高可用性的方式,但也存在一些缺点. 1.主从模式缺点 写入单点故障:在主从模式中,写入操作只能在主节点进行,如果主节点宕机,写入将无法执行 ...

  9. 基于Linux的三种防火墙(IPtables、Firewall、UFW)

    学而不思则罔,思而不学则殆. 导航 IPtables Firewall UFW 对比总结 IPtables部分 1.IPtables 四表五链. 四表:filter.nat.raw.mangle. 五 ...

  10. 理解linux的CPU上下文切换

    前言 linux是一个多任务操作系统,它支持远大于CPU数量的任务同时运行.当然,这个同时运行不是真的同时运行,而是系统在很短的时间内轮流分配CPU资源,由于CPU的速度很快,所以给人一种同时运行的错 ...