基於DDD的.NET開發框架 - ABP模塊設計

返回ABP系列

ABP是「ASP.NET Boilerplate Project (ASP.NET樣板項目)」的簡稱。

ASP.NET Boilerplate是一個用最佳實踐和流行技術開發現代WEB應用程序的新起點,它旨在成爲一個通用的WEB應用程序框架和項目模板。

ABP的官方網站:http://www.aspnetboilerplate.com

ABP官方文檔:http://www.aspnetboilerplate.com/Pages/Documents

Github上的開源項目:https://github.com/aspnetboilerplate

一、摘要

研究過orchard和nopcommerce的都應該知道模塊概念,ABP的模塊也和他們是一回事。實現原理也都一樣:應用程序一般都是先定義模塊接口,然後把模塊編譯的dll放到固定的目錄中(ABP只能放到bin下),應用程序主程序通過加載那些實現了插件接口的dll來實現插件的使用。

ABP 框架提供了創建和組裝模塊的基礎,一個模塊能夠依賴於另一個模塊。在通常情況 下,一個程序集就可以看成是一個模塊。在 ABP 框架中,一個模塊通過一個類來定義,而這 個類要繼承自 AbpModule。

nopcommerce插件實現可以到:ASP.NET MVC5 插件化機制簡單實現瞭解下。

二、基本概念

下面的例子,我們開發一個可以在多個不同應用中被調用MybolgApplication模塊,代碼如下:

        public class MyBlogApplicationModule : AbpModule //定義
        {
            public override void Initialize() //初始化
            {
                IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
                //這行代碼的寫法基本上是不變的。它的作用是把當前程序集的特定類或接口註冊到依賴注入容器中。
            }
        }

ABP框架會掃描所有的程序集,並且發現AbpModule類中所有已經導入的所有類,如果你已經創建了包含多個程序集的應用,對於ABP,我們的建議是爲每一個程序集創建一個Module(模塊)。

生命週期:

在一個應用中,abp框架調用了Module模塊的一些指定的方法來進行啓動和關閉模塊的操作。我們可以重載這些方法來完成我們自己的任務。

ABP框架通過依賴關係的順序來調用這些方法,假如:模塊A依賴於模塊B,那麼模塊B要在模塊A之前初始化,模塊啓動的方法順序如下:


1、PreInitialize-B
2、PreInitialize-A
3、Initialize-B
4、Initialize-A
5、PostInitialize-B
6、PostInitialize-A

下面是具體方法的說明:

PreInitialize 預初始化:當應用啓動後,第一次會調用這個方法。在依賴注入註冊之前,你可以在這個方法中指定自己的特別代碼。舉個例子吧:假如你創建了一個傳統的登記類,那麼你要先註冊這個類(使用IocManager對登記類進行註冊),你可以註冊事件到IOC容器。

Initialize初始化:在這個方法中一般是來進行依賴注入的註冊,一般我們通過IocManager.RegisterAssemblyByConvention這個方法來實現。如果你想實現自定義的依賴注入,那麼請參考依賴注入的相關文檔。

PostInitialize提交初始化:最後一個方法,這個方法用來解析依賴關係。

Shutdown關閉:當應用關閉以後,這個方法被調用。

模塊依賴:

Abp框架會自動解析模塊之間的依賴關係,但是我們還是建議你通過重載GetDependencies方法來明確的聲明依賴關係。

[DependsOn(typeof(MyBlogCoreModule))]//通過註解來定義依賴關係
public class MyBlogApplicationModule : AbpModule
{
    public override void Initialize()
    {
        IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
    }
}

例如上面的代碼,我們就聲明瞭MyBlogApplicationModule和MyBlogCoreModule的依賴關係(通過屬性attribute),MyBlogApplicationModule這個應用模塊依賴於MyBlogCoreModule核心模塊,並且,MyBlogCoreModule核心模塊會在MyBlogApplicationModule模塊之前進行初始化。

自定義的模塊方法:

我們自己定義的模塊中可能有方法被其他依賴於當前模塊的模塊調用,下面的例子,假設模塊2依賴於模塊1,並且想在預初始化的時候調用模塊1的方法。

public class MyModule1 : AbpModule
{
    public override void Initialize() //初始化模塊
    {
        IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());//這裏,進行依賴注入的註冊。
    }

    public void MyModuleMethod1()
    {
        //這裏寫自定義的方法。
    }
}

[DependsOn(typeof(MyModule1))]
public class MyModule2 : AbpModule
{
    private readonly MyModule1 _myModule1;

    public MyModule2(MyModule1 myModule1)
    {
        _myModule1 = myModule1;
    }

    public override void PreInitialize()
    {
        _myModule1.MyModuleMethod1(); //調用MyModuleMethod1的方法。
    }

    public override void Initialize()
    {
        IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
    }
}

就這樣,就把模塊1注入到了模塊2,因此,模塊2就能調用模塊1的方法了。

三、基本原理

1、獲取bin下全部dll

            /// <summary>
            /// 獲取bin下全部dll
            /// </summary>
            public List<Assembly> GetAllAssemblies()
            {
                var allReferencedAssemblies = BuildManager.GetReferencedAssemblies().Cast<Assembly>().ToList();
                var dllFiles = Directory.GetFiles(HttpRuntime.AppDomainAppPath + "bin\\", "*.dll", SearchOption.TopDirectoryOnly).ToList();
                return dllFiles.Select(dllFile => allReferencedAssemblies.FirstOrDefault(asm => AssemblyName.ReferenceMatchesDefinition(asm.GetName(), AssemblyName.GetAssemblyName(dllFile)))).Where(locatedAssembly => locatedAssembly != null).ToList();
            }

2、判斷是否繼承了AbpModule接口

            /// <summary>
            /// 判斷與AbpModule的Types是否有關
            /// </summary>
            public static bool IsAbpModule(Type type)
            {
                return
                    type.IsClass &&
                    !type.IsAbstract &&
                    typeof(AbpModule).IsAssignableFrom(type);
            }

3、循環相關的依賴,把他們填在到modules集合中

#region 循環相關的依賴,把他們填在到modules集合中
            private static ICollection<Type> AddMissingDependedModules(ICollection<Type> allModules)
            {
                var initialModules = allModules.ToList();
                foreach (var module in initialModules)
                {
                    FillDependedModules(module, allModules);
                }

                return allModules;
            }

            private static void FillDependedModules(Type module, ICollection<Type> allModules)
            {
                foreach (var dependedModule in AbpModule.FindDependedModuleTypes(module))
                {
                    if (allModules.Contains(dependedModule)) continue;
                    allModules.Add(dependedModule);
                    FillDependedModules(dependedModule, allModules);
                }
            }
            public static List<Type> FindDependedModuleTypes(Type moduleType)
            {
                if (!IsAbpModule(moduleType))
                {
                    throw new AbpInitializationException("This type is not an ABP module: " + moduleType.AssemblyQualifiedName);
                }

                var list = new List<Type>();

                if (!moduleType.IsDefined(typeof (DependsOnAttribute), true)) return list;
                var dependsOnAttributes = moduleType.GetCustomAttributes(typeof(DependsOnAttribute), true).Cast<DependsOnAttribute>();
                list.AddRange(dependsOnAttributes.SelectMany(dependsOnAttribute => dependsOnAttribute.DependedModuleTypes));

                return list;
            }
            #endregion

 4、控制反轉

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Abp.Configuration.Startup;
using Abp.Dependency;
using Castle.Core.Logging;

namespace Abp.Modules
{
    /// <summary>
    /// This class is used to manage modules.
    /// </summary>
    internal class AbpModuleManager : IAbpModuleManager
    {
        public ILogger Logger { get; set; }

        private readonly AbpModuleCollection _modules;

        private readonly IIocManager _iocManager;
        private readonly IModuleFinder _moduleFinder;

        public AbpModuleManager(IIocManager iocManager, IModuleFinder moduleFinder)
        {
            _modules = new AbpModuleCollection();
            _iocManager = iocManager;
            _moduleFinder = moduleFinder;
            Logger = NullLogger.Instance;
        }

        /// <summary>
        /// 初始化模塊
        /// </summary>
        public virtual void InitializeModules()
        {
            LoadAll(); //加載所有

            var sortedModules = _modules.GetSortedModuleListByDependency();
            //初始化Modules的事件
            sortedModules.ForEach(module => module.Instance.PreInitialize());
            sortedModules.ForEach(module => module.Instance.Initialize());
            sortedModules.ForEach(module => module.Instance.PostInitialize());
        }
        /// <summary>
        /// 關閉模塊
        /// </summary>
        public virtual void ShutdownModules()
        {
            var sortedModules = _modules.GetSortedModuleListByDependency();
            sortedModules.Reverse();
            sortedModules.ForEach(sm => sm.Instance.Shutdown());
        }

        /// <summary>
        /// 
        /// </summary>
        private void LoadAll()
        {
            Logger.Debug("Loading Abp modules...");

            var moduleTypes = AddMissingDependedModules(_moduleFinder.FindAll());
            Logger.Debug("Found " + moduleTypes.Count + " ABP modules in total.");

            //註冊到IOC容器
            foreach (var moduleType in moduleTypes)
            {
                if (!AbpModule.IsAbpModule(moduleType))
                {
                    throw new AbpInitializationException("This type is not an ABP module: " + moduleType.AssemblyQualifiedName);
                }

                if (!_iocManager.IsRegistered(moduleType))
                {
                    _iocManager.Register(moduleType);
                }
            }

            //模塊添加到_modules中
            foreach (var moduleType in moduleTypes)
            {
                var moduleObject = (AbpModule)_iocManager.Resolve(moduleType);

                moduleObject.IocManager = _iocManager;
                moduleObject.Configuration = _iocManager.Resolve<IAbpStartupConfiguration>();

                _modules.Add(new AbpModuleInfo(moduleObject));

                Logger.DebugFormat("Loaded module: " + moduleType.AssemblyQualifiedName);
            }

            EnsureKernelModuleToBeFirst();

            SetDependencies();

            Logger.DebugFormat("{0} modules loaded.", _modules.Count);
        }

        /// <summary>
        /// AbpKernelModule must be the first module
        /// </summary>
        private void EnsureKernelModuleToBeFirst()
        {
            var kernelModuleIndex = _modules.FindIndex(m => m.Type == typeof (AbpKernelModule));
            if (kernelModuleIndex > 0)
            {
                var kernelModule = _modules[kernelModuleIndex];
                _modules.RemoveAt(kernelModuleIndex);
                _modules.Insert(0, kernelModule);
            }
        }

        private void SetDependencies()
        {
            foreach (var moduleInfo in _modules)
            {
                //Set dependencies according to assembly dependency
                foreach (var referencedAssemblyName in moduleInfo.Assembly.GetReferencedAssemblies())
                {
                    var referencedAssembly = Assembly.Load(referencedAssemblyName);
                    var dependedModuleList = _modules.Where(m => m.Assembly == referencedAssembly).ToList();
                    if (dependedModuleList.Count > 0)
                    {
                        moduleInfo.Dependencies.AddRange(dependedModuleList);
                    }
                }

                //Set dependencies for defined DependsOnAttribute attribute(s).
                foreach (var dependedModuleType in AbpModule.FindDependedModuleTypes(moduleInfo.Type))
                {
                    var dependedModuleInfo = _modules.FirstOrDefault(m => m.Type == dependedModuleType);
                    if (dependedModuleInfo == null)
                    {
                        throw new AbpInitializationException("Could not find a depended module " + dependedModuleType.AssemblyQualifiedName + " for " + moduleInfo.Type.AssemblyQualifiedName);
                    }

                    if ((moduleInfo.Dependencies.FirstOrDefault(dm => dm.Type == dependedModuleType) == null))
                    {
                        moduleInfo.Dependencies.Add(dependedModuleInfo);
                    }
                }
            }
        }

        private static ICollection<Type> AddMissingDependedModules(ICollection<Type> allModules)
        {
            var initialModules = allModules.ToList();
            foreach (var module in initialModules)
            {
                FillDependedModules(module, allModules);
            }

            return allModules;
        }

        private static void FillDependedModules(Type module, ICollection<Type> allModules)
        {
            foreach (var dependedModule in AbpModule.FindDependedModuleTypes(module))
            {
                if (!allModules.Contains(dependedModule))
                {
                    allModules.Add(dependedModule);
                    FillDependedModules(dependedModule, allModules);
                }
            }
        }
    }
}

 

四、ABP底層如何描述Module

AbpModule:模塊抽象類

AbpModuleInfo:模塊信息

AbpModuleCollection:AbpModuleInfo的集合

IAbpModuleManager:模塊管理接口

AbpModuleManager:模塊管理類實現模塊管理接口

IModuleFinder:負責找所有模塊的接口

DefaultModuleFinder:實現IModuleFinder接口

DependsOnAttribute:用來定義ABP模塊依賴其他模塊

AbpModuleInfo用於封裝AbpModule的基本信息。 AbpModuleCollection則是AbpModuleInfo的集合。

五、Module註冊到ABP底層流程

Abp底層框架發現Module是從AbpBootstrapper在執行Initialize方法的時候開始的,該方法會調用IAbpModuleManager實例的InitializeModules方法,這個方法接着調用DefaultModuleFinder的FindAll方法(該方法用於過濾出AbpModule的assembly),而FindAll方法調用TypeFinder得到所有的assembly. 所以只要你定義的assembly中有一個繼承至AbpModule的類,並且該assembly被引用到你的項目中,那麼這個Module就可以說會被Abp底層框架集成了。