教程
其他教程預覽
分庫分表項目實戰教程
Git地址: https://github.com/MrChuJiu/EasyLogger
簡介
開講第二篇,本篇代碼並非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直接下載源碼看吧,麻煩老板們給點個星星!!!