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

第一步 基本操作
还是老样子,我们新建一个模块化接口类
新建接口 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 该类来存储 自身和引用的其他模块

/// <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
{
///
/// 启动模块
///
///
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<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 定义
///
/// 模块接口类型全名称
///
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

///
/// 模块排序
///
///
///
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 方法 筛选去重 依次进行注入, 并最终保存到全局对象中

    /// <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方法的模块调用

///
/// 模块服务扩展
///
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 接口, 模块和实现都自己手动都同步一下

/// <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);
}
 亚马逊测评 www.yisuping.com
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值