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


    教程

    01 | 模块化方案一

    02 | 模块化方案二

    其他教程预览

    分库分表项目实战教程

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

    01 | 前言

    02 | 简单的分库分表设计

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

    04 | 强化设计方案

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

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

    简介

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

    开始

    第一步 基本操作

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

       /// <summary>
        /// 应用模块接口定义
        /// </summary>
        public interface IAppModule
        {
            /// <summary>
            /// 配置服务前
            /// </summary>
            /// <param name="context"></param>
            void OnPreConfigureServices();
            /// <summary>
            /// 配置服务
            /// </summary>
            /// <param name="context">配置上下文</param>
            void OnConfigureServices();
            /// <summary>
            /// 配置服务后
            /// </summary>
            /// <param name="context"></param>
            void OnPostConfigureServices();
            /// <summary>
            /// 应用启动前
            /// </summary>
            /// <param name="context"></param>
            void OnPreApplicationInitialization();
            /// <summary>
            /// 应用启动
            /// </summary>
            /// <param name="context"></param>
            void OnApplicationInitialization();
            /// <summary>
            /// 应用启动后
            /// </summary>
            /// <param name="context"></param>
            void OnPostApplicationInitialization();
            /// <summary>
            /// 应用停止
            /// </summary>
            /// <param name="context"></param>
            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

    /// <summary>
        /// 模块依赖的模块
        /// </summary>
        [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
        public class DependsOnAttribute : Attribute
        {
            /// <summary>
            /// 依赖的模块类型
            /// </summary>
            public Type[] DependModuleTypes { get; private set; }
    
            public DependsOnAttribute(params Type[] dependModuleTypes)
            {
                DependModuleTypes = dependModuleTypes ?? new Type[0];
            }
        }
    

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

        /// <summary>
        /// 模块描述
        /// </summary>
        public class ModuleDescriptor
        {
            private object _instance;
    
            /// <summary>
            /// 模块类型
            /// </summary>
            public Type ModuleType { get; private set; }
    
            /// <summary>
            /// 依赖项
            /// </summary>
            public ModuleDescriptor[] Dependencies { get; private set; }
    
            /// <summary>
            /// 实例
            /// </summary>
            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
        {
            /// <summary>
            /// 启动模块
            /// </summary>
            /// <typeparam name="TModule"></typeparam>
            void StartModule<TModule>(IServiceCollection services)
                where TModule : IAppModule;
        }
    

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

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

    /// <summary>
            /// 获取模块依赖树
            /// </summary>
            /// <param name="moduleType"></param>
            /// <returns></returns>
            protected virtual List<ModuleDescriptor> VisitModule(Type moduleType) {
    
                var moduleDescriptors = new List<ModuleDescriptor>();
                // 是否必须被重写|是否是接口|是否为泛型类型|是否是一个类或委托
                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<DependsOnAttribute>();
                // 依赖属性为空
                if (dependModulesAttribute == null)
                {
                    moduleDescriptors.Add(new ModuleDescriptor(moduleType));
                }
                else {
                    // 依赖属性不为空,递归获取依赖
                    var dependModuleDescriptors = new List<ModuleDescriptor>();
                    foreach (var dependModuleType in dependModulesAttribute.DependModuleTypes)
                    {
                        dependModuleDescriptors.AddRange(
                            VisitModule(dependModuleType)
                        );
                    }
                    // 创建模块描述信息,内容为模块类型和依赖类型
                    moduleDescriptors.Add(new ModuleDescriptor(moduleType, dependModuleDescriptors.ToArray()));
                }
    
                return moduleDescriptors;
            }
    
    补: _moduleInterfaceTypeFullName 定义
            /// <summary>
            /// 模块接口类型全名称
            /// </summary>
            public static string _moduleInterfaceTypeFullName = typeof(IAppModule).FullName;
    
    2.拿到依赖关系通过拓扑排序进行顺序处理 (ps:拓扑排序地址 https://www.cnblogs.com/myzony/p/9201768.html)

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

        /// <summary>
        /// 拓扑排序工具类
        /// </summary>
        public static class Topological
        {
            public static List<T> Sort<T>(IEnumerable<T> source, Func<T, IEnumerable<T>> getDependencies) {
    
                var sorted = new List<T>();
                var visited = new Dictionary<T, bool>();
    
                foreach (var item in source)
                {
                    Visit(item, getDependencies, sorted, visited);
                }
    
                return sorted;
            }
    
            static void Visit<T>(T item, Func<T, IEnumerable<T>> getDependencies, List<T> sorted, Dictionary<T, bool> 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

     /// <summary>
            /// 模块排序
            /// </summary>
            /// <typeparam name="TModule"></typeparam>
            /// <returns></returns>
            public virtual List<ModuleDescriptor> ModuleSort<TModule>() where TModule : IAppModule
            {
                // 得到模块树依赖
                var moduleDescriptors = VisitModule(typeof(TModule));
                // 因为现在得到的数据是从树根开始到树叶 - 实际的注入顺序应该是从树叶开始 所以这里需要对模块进行排序
                return Topological.Sort(moduleDescriptors, o => o.Dependencies);
            }
    
    补:ModuleSort本来是个私有方法 后为了让模块使用者可以实现重写,请在 IModuleManager 加入
            /// <summary>
            /// 模块排序
            /// </summary>
            /// <typeparam name="TModule">启动模块类型</typeparam>
            /// <returns>排序结果</returns>
            List<ModuleDescriptor> ModuleSort<TModule>()
                where TModule : IAppModule;
    
    
    3.模块已经可以通过方法拿到了就来实现 StartModule 方法 筛选去重 依次进行注入, 并最终保存到全局对象中

            /// <summary>
            /// 模块明细和实例
            /// </summary>
            public virtual IReadOnlyList<ModuleDescriptor> ModuleDescriptors { get; protected set; }
    
            /// <summary>
            /// 入口 StartModule 
            /// 我们通过传递泛型进来的 TModule 为起点
            /// 查找他的依赖树
            /// </summary>
            /// <typeparam name="TModule"></typeparam>
            /// <param name="services"></param>
            public void StartModule<TModule>(IServiceCollection services) where TModule : IAppModule
            {
    
                var moduleDescriptors = new List<ModuleDescriptor>();
    
                var moduleDescriptorList = this.ModuleSort<TModule>();
                // 去除重复的引用 进行注入
                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既然存储着我们的所有模块,那么我们怎么执行模块的方法呢

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

            /// <summary>
            /// 进行模块的  ConfigurationService 方法调用
            /// </summary>
            /// <param name="services"></param>
            /// <param name="configuration"></param>
            /// <returns></returns>
            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;
            }
            /// <summary>
            /// 进行模块的  Configure 方法调用
            /// </summary>
            /// <param name="serviceProvider"></param>
            /// <returns></returns>
            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;
            }
            /// <summary>
            /// 模块销毁
            /// </summary>
            public void ApplicationShutdown()
            {
                // todo我觉得这里有点问题问 易大师
                //var modules = ModuleDescriptors.Reverse().ToList();
    
                foreach (var module in ModuleDescriptors)
                {
                    (module.Instance as IAppModule)?.OnApplicationShutdown();
                }
            }
    

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

            /// <summary>
            /// 主模块销毁的时候 销毁子模块
            /// </summary>
            public void Dispose()
            {
                this.Dispose(true);
            }
    
            protected virtual void Dispose(bool state)
            {
                this.ApplicationShutdown();
    
            }
    

    第四步 Extensions

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

     /// <summary>
        /// 模块服务扩展
        /// </summary>
        public static class RivenModuleServiceCollectionExtensions
        {
            /// <summary>
            /// 添加Riven模块服务
            /// </summary>
            /// <typeparam name="TModule"></typeparam>
            /// <param name="services"></param>
            /// <param name="configuration"></param>
            /// <returns></returns>
            public static IServiceCollection AddRivenModule<TModule>(this IServiceCollection services, IConfiguration configuration)
                where TModule : IAppModule
            {
                var moduleManager = new ModuleManager();
                // 将模块都查询排序好
                moduleManager.StartModule<TModule>(services);
                // 调用模块 和 子模块的ConfigurationService方法
                moduleManager.ConfigurationService(services, configuration);
                // 注入全局的  IModuleManager
                services.TryAddSingleton<IModuleManager>(moduleManager);
                return services;
            }
        }
    

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

     public static class RivenModuleIApplicationBuilderExtensions
        {
            /// <summary>
            /// 使用RivenModule
            /// </summary>
            /// <param name="serviceProvider"></param>
            /// <returns></returns>
            public static IServiceProvider UseRivenModule(this IServiceProvider serviceProvider)
            {
                var moduleManager = serviceProvider.GetService<IModuleManager>();
    
                return moduleManager.ApplicationInitialization(serviceProvider);
            }
        }
    

    第五步 测试

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

    services.AddRivenModule<MyAppStartupModule>(Configuration);
    

    Configure 中调用

     app.ApplicationServices.UseRivenModule();
    

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

     app.Map("/ApplicationShutdown", _ =>
                {
                    _.Run((context) =>
                    {
                        var moduleManager = app.ApplicationServices.GetService<IModuleManager>();
                        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 接口, 模块和实现都自己手动都同步一下

        /// <summary>
        /// 应用模块接口定义
        /// </summary>
        public interface IAppModule
        {
            /// <summary>
            /// 配置服务前
            /// </summary>
            /// <param name="context"></param>
            void OnPreConfigureServices(ServiceConfigurationContext context);
    
            /// <summary>
            /// 配置服务
            /// </summary>
            /// <param name="context">配置上下文</param>
            void OnConfigureServices(ServiceConfigurationContext context);
    
            /// <summary>
            /// 配置服务后
            /// </summary>
            /// <param name="context"></param>
            void OnPostConfigureServices(ServiceConfigurationContext context);
    
            /// <summary>
            /// 应用启动前
            /// </summary>
            /// <param name="context"></param>
            void OnPreApplicationInitialization(ApplicationInitializationContext context);
    
            /// <summary>
            /// 应用启动
            /// </summary>
            /// <param name="context"></param>
            void OnApplicationInitialization(ApplicationInitializationContext context);
    
            /// <summary>
            /// 应用启动后
            /// </summary>
            /// <param name="context"></param>
            void OnPostApplicationInitialization(ApplicationInitializationContext context);
    
            /// <summary>
            /// 应用停止
            /// </summary>
            /// <param name="context"></param>
            void OnApplicationShutdown(ApplicationShutdownContext context);
        }
    

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

    项目地址

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

    鸣谢

    玩双截棍的熊猫

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

  • 相关阅读:
    将js进行到底:node学习1
    mui开发app前言(一)
    用kotlin方式打开《第一行代码:Android》之开发酷欧天气(最终版)
    用kotlin方式打开《第一行代码:Android》之开发酷欧天气(2)
    用kotlin方式打开《第一行代码:Android》之开发酷欧天气(1)
    062、如何使用flannel host-gw backend(2019-04-02 周二)
    061、flannel的连通与隔离(2019-04-01 周一)
    060、在docker中使用flannel(2019-03-29 周五)
    059、安装配置flannel(2019-03-28 周四)
    058、flannel概述(2019-03-27 周三)
  • 原文地址:https://www.cnblogs.com/MrChuJiu/p/13708035.html
Copyright © 2020-2023  润新知