Util应用框架核心(二) - 启动器
本节介绍 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";
Util应用框架核心(二) - 启动器的更多相关文章
- 图解Disruptor框架(二):核心概念
图解Disruptor框架(二):核心概念 概述 上一个章节简单的介绍了了下Disruptor,这节就是要好好的理清楚Disruptor中的核心的概念.并且会给出个HelloWorld的小例子. 在正 ...
- MVC系列——MVC源码学习:打造自己的MVC框架(二:附源码)
前言:上篇介绍了下 MVC5 的核心原理,整篇文章比较偏理论,所以相对比较枯燥.今天就来根据上篇的理论一步一步进行实践,通过自己写的一个简易MVC框架逐步理解,相信通过这一篇的实践,你会对MVC有一个 ...
- 【原创】NIO框架入门(二):服务端基于MINA2的UDP双向通信Demo演示
前言 NIO框架的流行,使得开发大并发.高性能的互联网服务端成为可能.这其中最流行的无非就是MINA和Netty了,MINA目前的主要版本是MINA2.而Netty的主要版本是Netty3和Netty ...
- 游戏UI框架设计(二) : 最简版本设计
游戏UI框架设计(二) --最简版本设计 为降低难度决定先讲解一个最简版本,阐述UI框架的核心设计理念.这里先定义三个核心功能: 1:UI窗体的自动加载功能. 2:缓存UI窗体. 3:窗体生命周期(状 ...
- CI框架 -- 核心文件 之 Hooks.php
钩子 - 扩展框架核心 CodeIgniter 的钩子特性提供了一种方法来修改框架的内部运作流程,而无需修改 核心文件.CodeIgniter 的运行遵循着一个特定的流程,你可以参考这个页面的 应用程 ...
- Robot Framework自动化测试框架核心指南-如何使用Java编写自定义的RobotFramework Lib
如何使用Java编写自定义的RobotFramework Lib 本文包括2个章节 1. Robot Frdamwork中如何调用java Lib库 2.使用 java编写自定义的Lib 本文作者为: ...
- Robot Framework自动化测试框架核心指南-如何做好自动化测试平台框架的设计
自动化测试如果需要能高效快速的支撑软件项目的测试,项目的快速迭代以及上线,除了以上我们介绍的需要许多的Lib来支持以及需要高效的去编写自动化测试案例外,还需要一个好的自动化测试框架平台来支撑我们的自动 ...
- JavaScript 框架设计(二)
JavaScript 高级框架设计 (二) 上一篇,JavaScript高级框架设计(一)我们 实现了对tag标签的选择 下来我们实现对id的选择,即id选择器. 我们将上一篇的get命名为getTa ...
- “Zhuang.Data”轻型数据库访问框架(二)框架的入口DbAccessor对象
目录: “Zhuang.Data”轻型数据库访问框架(一)开篇介绍 “Zhuang.Data”轻型数据库访问框架(二)框架的入口DbAccessor对象 先来看一段代码 DbAccessor dba ...
- Struts2框架学习(二) Action
Struts2框架学习(二) Action Struts2框架中的Action类是一个单独的javabean对象.不像Struts1中还要去继承HttpServlet,耦合度减小了. 1,流程 拦截器 ...
随机推荐
- 【Python】数据可视化利器PyCharts在测试工作中的应用
PyCharts 简介 PyCharts 是一个基于 Python 的数据可视化库,它支持多种图表类型,如折线图.柱状图.饼图等.PyCharts 提供了简洁的 API,使得用户能够轻松地创建各种图表 ...
- 2023年陕西彬州第八届半程马拉松赛153pb完赛
1.赛事背景 2023年6月3日,我参加了2023陕西彬州第八届半程马拉松赛,最终153完赛,PB了5分钟.起跑时间早上7点30分,毕竟6月天气也开始热了.天气预报显示当天还是小到中雨,上次铜川宜君半 ...
- 将Dubbo注册到Nacos,与DubboAdmin的部署
王有志,一个分享硬核Java技术的互金摸鱼侠加入Java人的提桶跑路群:共同富裕的Java人 本文是<从 0 开始学 Dubbo>系列文章中应用篇的番外篇. 在这篇文章中我会和大家一起部署 ...
- 2021-7-29 MySql进阶
Alter的使用: 列的增加和删减 alter table users add user_name VARCHAR(100);#添加一列在末尾 SELECT * from users; alter t ...
- Java8 函数式编程stream流
1.初始环境准备 场景:现在有一个公司,公司部门有一级部门,二级部门甲和二级部门乙(其中二级部门甲和二级部门乙是一级部门的子部门), 一级部门下面有有001号员工小明,二级部门甲下面有002号员工 ...
- java读取txt文件解决乱码问题
说明:由于txt文件有bom和不同的编码方式,导致导入数据时产生乱码,以下代码完美解决乱码问题. 参考他人代码,结合自己的业务加工完成,费了大半天功夫完成,希望对大家有点用处. 废话不多说,直接上代码 ...
- pandas:字典转dataframe的注意事项
推荐写法 参考链接 https://blog.csdn.net/u013061183/article/details/79497254
- 王道oj/problem10
地址:http://oj.lgwenda.com/problem/10 思路:首先创建字符串赋初值,其次用gets()输入字符串[fgets()相对于gets()会多识别"\n", ...
- SpringSecurity1: spring boot web 样例快速体验
本文只讲操作实践,不讲原理,这样对于想快速搭建起一个基于SpringSecurity的Web项目的朋友们而言,比较友好.文章主要由两部分构成: 快速演示样例 所有账户和授权数据均基于内存,能在极短的时 ...
- 8.0 Python 使用进程与线程
python 进程与线程是并发编程的两种常见方式.进程是操作系统中的一个基本概念,表示程序在操作系统中的一次执行过程,拥有独立的地址空间.资源.优先级等属性.线程是进程中的一条执行路径,可以看做是轻量 ...