01 | 模塊化方案一html
02 | 模塊化方案二git
01 | 前言github
04 | 強化設計方案app
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)
/// <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; }
/// <summary> /// 模塊接口類型全名稱 /// </summary> public static string _moduleInterfaceTypeFullName = typeof(IAppModule).FullName;
新建類 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); }
/// <summary> /// 模塊排序 /// </summary> /// <typeparam name="TModule">啓動模塊類型</typeparam> /// <returns>排序結果</returns> List<ModuleDescriptor> ModuleSort<TModule>() where TModule : IAppModule;
/// <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(); }
入口經過調用下面的方法進行模塊的方法調用
/// <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
新建 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直接下載源碼看吧,麻煩老闆們給點個星星!!!