知识全聚集 .Net Core 技术突破 | 如何实现一个模块化方案二

dotNET知音

共 15076字,需浏览 31分钟

 · 2020-09-23

https://www.cnblogs.com/MrChuJiu/p/13708035.html

作者:初久的私房菜


教程

往期推荐

知识全聚集 .Net Core 技术突破 | 如何实现一个模块化方案一


02 | 模块化方案二

其他教程预览

分库分表项目实战教程

Git地址:https://github.com/MrChuJiu/EasyLogger

01 | 前言

02 | 简单的分库分表设计

03 | 控制反转搭配简单业务

04 | 强化设计方案

05 | 完善业务自动创建数据库

06 | 最终篇-通过AOP自动连接数据库-完成日志业务

简介

开讲第二篇,本篇代码并非Copy的ABP,只是参考ABP的功能,进行的实现方案,让代码更加通俗易懂。代码的讲解思路和上一篇一样,但是不引用上篇的写法。

开始

第一步 基本操作

还是老样子,我们新建一个模块化接口类
新建接口 IAppModule (ps:项目中起的类名和方法名尽量对标ABP)

   /// 
/// 应用模块接口定义
///

public interface IAppModule
{
///
/// 配置服务前
///

///
void OnPreConfigureServices();
///
/// 配置服务
///

/// 配置上下文
void OnConfigureServices();
///
/// 配置服务后
///

///
void OnPostConfigureServices();
///
/// 应用启动前
///

///
void OnPreApplicationInitialization();
///
/// 应用启动
///

///
void OnApplicationInitialization();
///
/// 应用启动后
///

///
void OnPostApplicationInitialization();
///
/// 应用停止
///

///
void OnApplicationShutdown();
}

新建类 AppModule 继承 IAppModule

   public abstract class AppModule : IAppModule
{
public virtual void OnPreConfigureServices()
{

}

public virtual void OnConfigureServices()
{

}

public virtual void OnPostConfigureServices()
{

}

public virtual void OnPreApplicationInitialization()
{

}

public virtual void OnApplicationInitialization()
{

}

public virtual void OnPostApplicationInitialization()
{

}
public virtual void OnApplicationShutdown()
{

}
}

第二步 预准备

这一步来完成ABP的DependsOnAttribute,通过特性进行引入模块,
这里参数 params Type[] 因为一个模块会依赖多个模块
新建类 DependsOnAttribute 继承 Attribute

/// 
/// 模块依赖的模块
///

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class DependsOnAttribute : Attribute
{
///
/// 依赖的模块类型
///

public Type[] DependModuleTypes { get; private set; }

public DependsOnAttribute(params Type[] dependModuleTypes)
{
DependModuleTypes = dependModuleTypes ?? new Type[0];
}
}

既然一个模块会包含多个模块的引用,那么就应该有一个存储的方式
新建类 ModuleDescriptor 该类来存储 自身和引用的其他模块

    /// 
/// 模块描述
///

public class ModuleDescriptor
{
private object _instance;

///
/// 模块类型
///

public Type ModuleType { get; private set; }

///
/// 依赖项
///

public ModuleDescriptor[] Dependencies { get; private set; }

///
/// 实例
///

public object Instance
{
get
{
if (this._instance == null)
{
this._instance = Activator.CreateInstance(this.ModuleType);
}
return this._instance;
}
}

public ModuleDescriptor(Type moduleType, params ModuleDescriptor[] dependencies)
{
this.ModuleType = moduleType;
// 如果模块依赖 为空给一个空数组
this.Dependencies = dependencies ?? new ModuleDescriptor[0];
}
}

第三步 模块管理器

来到核心步骤,这里我们写模块管理器,白话就是存储模块和模块操作方法的一个类(同上一篇的StartupModulesOptions)
第一步肯定是模块的启动
我们新建 IModuleManager接口

 public interface IModuleManager : IDisposable
{
///
/// 启动模块
///

///
void StartModule(IServiceCollection services)
where TModule : IAppModule;
}

紧跟新建类 ModuleManager 继承 IModuleManager, StartModule 先放在一边
这里的思路是:模块是从一个入口的根模块开始的慢慢的形成一个树状的引用关系,我们首先需要拿到所有的模块引用,并把他们从树叶为起点排列起来,依次注入。
(理解为A=>B=>C 那么注入的顺序应该是 C=>B=>A)

1.先来实现根绝入口递归获取所有的引用关系 我已经在方法中将每一步的注释都写上了

/// 
/// 获取模块依赖树
///

///
///
protected virtual List VisitModule(Type moduleType) {

var moduleDescriptors = new List();
// 是否必须被重写|是否是接口|是否为泛型类型|是否是一个类或委托
if (moduleType.IsAbstract || moduleType.IsInterface || moduleType.IsGenericType || !moduleType.IsClass) {
return moduleDescriptors;
}

// 过滤没有实现IRModule接口的类
var baseInterfaceType = moduleType.GetInterface(_moduleInterfaceTypeFullName, false);
if (baseInterfaceType == null)
{
return moduleDescriptors;
}

// 得到当前模块依赖了那些模块
var dependModulesAttribute = moduleType.GetCustomAttribute();
// 依赖属性为空
if (dependModulesAttribute == null)
{
moduleDescriptors.Add(new ModuleDescriptor(moduleType));
}
else {
// 依赖属性不为空,递归获取依赖
var dependModuleDescriptors = new List();
foreach (var dependModuleType in dependModulesAttribute.DependModuleTypes)
{
dependModuleDescriptors.AddRange(
VisitModule(dependModuleType)
);
}
// 创建模块描述信息,内容为模块类型和依赖类型
moduleDescriptors.Add(new ModuleDescriptor(moduleType, dependModuleDescriptors.ToArray()));
}

return moduleDescriptors;
}
补:_moduleInterfaceTypeFullName 定义
        /// 
/// 模块接口类型全名称
///

public static string _moduleInterfaceTypeFullName = typeof(IAppModule).FullName;
2.拿到依赖关系通过拓扑排序进行顺序处理 (ps:拓扑排序地址 https://www.cnblogs.com/myzony/p/9201768.html)

新建类 Topological 这块没啥特别要讲的根据链接去看下就好了

    /// 
/// 拓扑排序工具类
///

public static class Topological
{
public static List Sort(IEnumerable source, Func> getDependencies) {

var sorted = new List();
var visited = new Dictionarybool>();

foreach (var item in source)
{
Visit(item, getDependencies, sorted, visited);
}

return sorted;
}

static void Visit(T item, Func> getDependencies, List sorted, Dictionarybool> visited)
{
bool inProcess;
var alreadyVisited = visited.TryGetValue(item, out inProcess);

// 如果已经访问该顶点,则直接返回
if (alreadyVisited)
{
// 如果处理的为当前节点,则说明存在循环引用
if (inProcess)
{
throw new ArgumentException("模块出现循环依赖.");
}
}
else
{
// 正在处理当前顶点
visited[item] = true;

// 获得所有依赖项
var dependencies = getDependencies(item);
// 如果依赖项集合不为空,遍历访问其依赖节点
if (dependencies != null)
{
foreach (var dependency in dependencies)
{
// 递归遍历访问
Visit(dependency, getDependencies, sorted, visited);
}
}

// 处理完成置为 false
visited[item] = false;
sorted.Add(item);
}
}

}

回到 ModuleManager 新建方法 ModuleSort

 /// 
/// 模块排序
///

///
///
public virtual List ModuleSort() where TModule : IAppModule
{
// 得到模块树依赖
var moduleDescriptors = VisitModule(typeof(TModule));
// 因为现在得到的数据是从树根开始到树叶 - 实际的注入顺序应该是从树叶开始 所以这里需要对模块进行排序
return Topological.Sort(moduleDescriptors, o => o.Dependencies);
}
补:ModuleSort本来是个私有方法 后为了让模块使用者可以实现重写,请在 IModuleManager 加入
        /// 
/// 模块排序
///

/// 启动模块类型
/// 排序结果
List ModuleSort()
where TModule : IAppModule;

3.模块已经可以通过方法拿到了就来实现 StartModule 方法 筛选去重 依次进行注入, 并最终保存到全局对象中

        /// 
/// 模块明细和实例
///

public virtual IReadOnlyList ModuleDescriptors { get; protected set; }

///
/// 入口 StartModule
/// 我们通过传递泛型进来的 TModule 为起点
/// 查找他的依赖树
///

///
///
public void StartModule(IServiceCollection services) where TModule : IAppModule
{

var moduleDescriptors = new List();

var moduleDescriptorList = this.ModuleSort();
// 去除重复的引用 进行注入
foreach (var item in moduleDescriptorList)
{
if (moduleDescriptors.Any(o => o.ModuleType.FullName == item.ModuleType.FullName))
{
continue;
}
moduleDescriptors.Add(item);
services.AddSingleton(item.ModuleType, item.Instance);
}
ModuleDescriptors = moduleDescriptors.AsReadOnly();
}
4.ModuleDescriptors既然存储着我们的所有模块,那么我们怎么执行模块的方法呢

入口通过调用下面的方法进行模块的方法调用

        /// 
/// 进行模块的 ConfigurationService 方法调用
///

///
///
///
public IServiceCollection ConfigurationService(IServiceCollection services, IConfiguration configuration) {

foreach (var module in ModuleDescriptors)
{
(module.Instance as IAppModule)?.OnPreConfigureServices();
}

foreach (var module in ModuleDescriptors)
{
(module.Instance as IAppModule)?.OnConfigureServices();
}

foreach (var module in ModuleDescriptors)
{
(module.Instance as IAppModule)?.OnPostConfigureServices();
}

return services;
}
///
/// 进行模块的 Configure 方法调用
///

///
///
public IServiceProvider ApplicationInitialization(IServiceProvider serviceProvider)
{
foreach (var module in ModuleDescriptors)
{
(module.Instance as IAppModule)?.OnPreApplicationInitialization();
}

foreach (var module in ModuleDescriptors)
{
(module.Instance as IAppModule)?.OnApplicationInitialization();
}

foreach (var module in ModuleDescriptors)
{
(module.Instance as IAppModule)?.OnPostApplicationInitialization();
}

return serviceProvider;
}
///
/// 模块销毁
///

public void ApplicationShutdown()
{
// todo我觉得这里有点问题问 易大师
//var modules = ModuleDescriptors.Reverse().ToList();

foreach (var module in ModuleDescriptors)
{
(module.Instance as IAppModule)?.OnApplicationShutdown();
}
}

当然还漏了一个模块销毁,该方法在主模块被销毁的时候调用(ps: 我个人思路应该是从树叶开始进行,但是ABP对模块顺序进行了反转从根开始进行销毁,所以这里同上)

        /// 
/// 主模块销毁的时候 销毁子模块
///

public void Dispose()
{
this.Dispose(true);
}

protected virtual void Dispose(bool state)
{
this.ApplicationShutdown();

}

第四步 Extensions

模块管理器写完了,那么这个方法如何调用呢来写我们的 Extensions
新建 RivenModuleServiceCollectionExtensions 类,让其完成ConfigurationService方法的模块调用

 /// 
/// 模块服务扩展
///

public static class RivenModuleServiceCollectionExtensions
{
///
/// 添加Riven模块服务
///

///
///
///
///
public static IServiceCollection AddRivenModule(this IServiceCollection services, IConfiguration configuration)
where TModule : IAppModule
{
var moduleManager = new ModuleManager();
// 将模块都查询排序好
moduleManager.StartModule(services);
// 调用模块 和 子模块的ConfigurationService方法
moduleManager.ConfigurationService(services, configuration);
// 注入全局的 IModuleManager
services.TryAddSingleton(moduleManager);
return services;
}
}

新建 RivenModuleIApplicationBuilderExtensions 类 ,让其完成Configuration方法的模块调用

 public static class RivenModuleIApplicationBuilderExtensions
{
///
/// 使用RivenModule
///

///
///
public static IServiceProvider UseRivenModule(this IServiceProvider serviceProvider)
{
var moduleManager = serviceProvider.GetService();

return moduleManager.ApplicationInitialization(serviceProvider);
}
}

第五步 测试

新建一个测试项目,引入写好的模块化类库,在 ConfigureServices 中调用

services.AddRivenModule(Configuration);

Configure 中调用

 app.ApplicationServices.UseRivenModule();

模块销毁演示(ps:这个是演示效果、实际是在项目停止的时候进行。)

 app.Map("/ApplicationShutdown", _ =>
{
_.Run((context) =>
{
var moduleManager = app.ApplicationServices.GetService();
moduleManager.ApplicationShutdown();
return Task.FromResult(0);
});
});

补:

新建 MyAppStartupModule、TestModuleA、TestModuleB 继承AppModule。
MyAppStartupModule作为入口模块 引用 A => B 然后在模块方法中打印 Console.WriteLine 看效果

补充 给模块传递参数

新建 ApplicationInitializationContext 类

public class ApplicationInitializationContext
{
public IServiceProvider ServiceProvider { get; }

public IConfiguration Configuration { get; }

public ApplicationInitializationContext([NotNull] IServiceProvider serviceProvider, [NotNull] IConfiguration configuration)
{
ServiceProvider = serviceProvider;
Configuration = configuration;
}
}

新建 ApplicationShutdownContext 类

 public class ApplicationShutdownContext
{
public IServiceProvider ServiceProvider { get; }

public ApplicationShutdownContext([NotNull] IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
}
}

新建 ServiceConfigurationContext 类

 public class ServiceConfigurationContext
{
public IServiceCollection Services { get; protected set; }

public IConfiguration Configuration { get; protected set; }


public ServiceConfigurationContext(IServiceCollection services, IConfiguration configuration)
{
Services = services;
Configuration = configuration;
}

}

修改 IAppModule 接口, 模块和实现都自己手动都同步一下

    /// 
/// 应用模块接口定义
///

public interface IAppModule
{
///
/// 配置服务前
///

///
void OnPreConfigureServices(ServiceConfigurationContext context);

///
/// 配置服务
///

/// 配置上下文
void OnConfigureServices(ServiceConfigurationContext context);

///
/// 配置服务后
///

///
void OnPostConfigureServices(ServiceConfigurationContext context);

///
/// 应用启动前
///

///
void OnPreApplicationInitialization(ApplicationInitializationContext context);

///
/// 应用启动
///

///
void OnApplicationInitialization(ApplicationInitializationContext context);

///
/// 应用启动后
///

///
void OnPostApplicationInitialization(ApplicationInitializationContext context);

///
/// 应用停止
///

///
void OnApplicationShutdown(ApplicationShutdownContext context);
}

修改 ModuleManager的 ConfigurationService、ApplicationInitialization、ApplicationShutdown 方法给调用传递对应参数
这部分代码我就不贴了,会的大佬都能自己写,想看的去我的github直接下载源码看吧,麻烦老板们给点个星星!!!

项目地址

知识全聚集,逐个击破:https://github.com/MrChuJiu/Easy.Core.Flow

鸣谢

玩双截棍的熊猫

源地址:https://github.com/rivenfx/Modular


浏览 16
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报