Abp学习笔记---轻松搞懂模块

做.net开发的朋友或多或少都听说过这个框架,自己在差不多一年前也才开始听说,但是!!!之前也没太当回事,一来是工作项目上用不着,二来以为到时候需要用的时候再拿来用就好了。

现在看来却是大错特错!近段时间,自己身边发生很多事,自然而然的,听到abp 的次数多了了起来,开始被问及 abp 相关的问题。自己也只能知道这个框架做了很多集成,同时基于 ddd 思想设计(坦白,目前的自己还只能套用 ddd 代码层面的结构,精髓却是未能掌握)。所以也决定深入学习 abp ,看看它有多牛。

所以,这一篇是abp学习笔记的开篇,之后可能会不定期新增新内容,具体如何,就看学习的效率了。

其实网上有很多对abp这个框架的介绍,而博主在学习过程中主要参照了一下文章:

http://www.cnblogs.com/mienreal/p/4528470.html

https://www.jianshu.com/p/1e6efd9be629

http://www.cnblogs.com/1zhk/p/5281458.html

不得不说这些文章的内容都很优秀!

但是可能是自己水平太菜了或者是理解能力太差了,对于各位大牛讲 模块 内容不是很明白。

这个不明白不是这个模块从发现到注册的过程不明白(却是也有不少没理清楚,但这不是重点),而是“为什么要这样做?”,“具体是怎么做的呢?”。

之所以会有这样的疑问是因为,大牛们都会把模块的创建,到模块的生命周期,怎么加载的讲的很清晰,但具体这个的作用是什么,内部是怎么实现的,却模糊带过。亦或者有些是简略带过“使用模块是为了在任何地方都可以使用/复用这些模块的功能”,也没具体讲怎么实现。或者有些文章里边在讲模块部分的时候,拿abp 模块部分的源码过来 解析,这是相对挺好的了。但是对于我这个新手来说,还是不好理解,一开始愣是想了很久他们说的模块复用,随时随地使用,以及模块直接交互是怎么进行的。

扯多了。下面列下自己在学习中的疑问,然后再逐一回答,大牛路过勿笑:

1.模块是如何复用/使用的?

2.模块之间是怎么交互的?

3.这种模块编程跟引入dll程序集有什么区别?

4.模块的注册主要做了什么?

看起来很简单,但当初难到自己了(回答上了的也不用往下看了)

回答:

1.所谓的模块复用,其实也跟我们平时引用某个 dll 然后使用程序集提供的类或者方法的做法是一样的。像我们写一个http帮助类,它自己创建独立 dll,,然后哪个项目需要,我们就引入这个帮助类,这个复用是一样的。

使用上也是一样的,创建对象,然后调用。但是!它这个区别就是在创建上了,模块对象的创建是基于 ioc 容器(如果不清楚则需要先去了解一下了再往下看)自动创建的,这样,我们在使用模块中的服务时,不需要手动去new 一个对象,而是通过 依赖注入的方式进行自动注入。

2.模块之间的交互一般为两种:

一个是依赖,这个是单向,比如说模块A依赖模块B,那么可以在模块A的操作中根据需要通过 依赖注入使用B模块的对象。比如模块B是日志模块,那么在 A 模块直接使用模块B的日志对象,而模块B是不会也不能循环依赖 A 模块。

二个是并列,此时两个模块是被当作一个大模块向另一个模块提供服务。比如 模块C同时需要模块A和模块B的服务,这时候,对于AB之间,他们就是并列关系。

3.其实这个就是引用dll一样(既然一样,那还要个屁模块编程?,哈哈哈,看下面第四个问题)。

4.大牛们讲模块内容时,都会从头捋一遍模块的注册以及模块启动加载过程走一遍。但是这个模块注册具体是做什么,为什么要这样做呢?

实际上,模块的注册就是把模块(程序集,abp建议一个程序集只有一个模块)中的类型注册到 ioc 容器,没错就是这么简单,而这个ioc容器是全局的,这就是为什么 问题 1 中说的 能在任何地方调用,因为程序集中的公开类型都会被注册到容器中,需要某个类型的对象直接使用 ioc 容器创建。

那为什么这样做呢?如果有过在mvc Global.asax 中手动配置 ioc 容器,然后替换 mvc 默认控制器创建器的朋友就比较好理解了,我们在Global.asax 做配置时,不同的程序集我们都要 手动的 通过 Assembly 找到要注册的程序集,然后使用容器进行注册。平时简单的小注册没什么,但是,如果是动态使用某些模块,然后模块可能又依赖某一个模块呢?那样注册就会有问题了,一来要写很多重复的东西,二来万一哪天我换掉其中某一个模块,那改动的就麻烦了。

所以,就有个模块自己注册的做法,程序在启动时会去算模块的依赖顺序,然后去分别调用初始化,这样如果我的模块拿去人家那里,人家也只管引用,而不需要再启动文件注册,而且这种做法还能为每个模块做自己的个性化注册,因为每个模块可能有自己的约束或者规范,自己注册比交给人家注册清楚多了。

其实说了这么多,但是看文字的话,可能有人理解有人还没理解,写这个,其实主要是为了方便需要的朋友,不过最好是看了大牛的文章就能理解不要看这个了。

为了写这篇文章,自己还准备了一个 模块载入和使用的 demo,做的很粗糙,但是能用,如果有兴趣的话,且往下看。

注意,一下实现仅为自己理解的基础上实现的简单的过程,实际会复杂的多,自己也还在学习中

项目结构如下

其中:

InjectContainer 是 ioc 容器

ModuleCore 是这个模块编程的核心

其它的都是一个个的 模块module

 

特别说一下  ModuleCore:

Contaier 提供容器基本注册方法,具体实现是基于 InjectContaier

using System;
using System.Reflection;
using MyContainer = InjectContainer.Container;

namespace ModuleCore.Container
{
    public class IocContainer : IIocContainer
    {
        private static IIocContainer instance;
        private static MyContainer container;
        public IocContainer() { }

        static IocContainer()
        {
            container = MyContainer.GetContainer();
            container.Register(typeof(IocContainer));
            instance = new IocContainer();
        }

        public static IIocContainer Getinstance()
        {
            return instance;
        }


        public object GetInstance(Type toType)
        {
            return container.GetInstance<object>(toType);
        }

        public TTo GetInstance<TTo>()
        {
            return container.GetInstance<TTo>();
        }

        public void Register(Type toType)
        {
            container.Register(toType);
        }

        public void Register<TTo>()
        {
            container.Register(typeof(TTo));
        }

        public void Register(Assembly assembly)
        {
            var types = assembly.GetTypes();//.Where(t=>t.GetInterface(typeof(IDependService).FullName)!=null);
            foreach (var type in types)
            {
                container.Register(type);
            }
        }
    }
}

Module下的 BaseModule类似与 AbpModule,是一个抽象类,这里只有一个方法 Register,每个模块都要有一个继承它的类

using ModuleCore.Container;

namespace ModuleCore.Module
{
    public abstract class BaseModule
    {
        public IIocContainer Container { get; internal set; }
        public abstract void Register();
    }
}

ModuleManager 是加载和调用module 进行注册的管理类,这里提供 Initialize 方法,基本思路是传进启动的 模块,然后查看这个module 的依赖模块,有的话依次往下获取,然后进行排序,先注册最上层的模块。而这个注册是将类型跟起实现的接口做映射(自己写的ioc容器目前只是注册接口类型)

using ModuleCore.Container;
using System;
using System.Collections.Generic;
using System.Linq;

namespace ModuleCore.Module
{
    public class ModuleManager : IModuleManager
    {
        private static Dictionary<Type, int> moduleOrder = new Dictionary<Type, int>();
        private static int orderIndex = 0;
        public void Initialize(Type startupModule)
        {
            LoadModule(startupModule);
            moduleOrder = moduleOrder.OrderByDescending(item => item.Value).ToDictionary(item => item.Key, item => item.Value);
            foreach (var item in moduleOrder)
            {
                var module = Activator.CreateInstance(item.Key) as BaseModule;
                module.Container = IocContainer.Getinstance();
                module.Register();
            }
        }

        private void LoadModule(Type currentModule)
        {
            if(moduleOrder.ContainsKey(currentModule))
            {
                moduleOrder[currentModule] = orderIndex++;
            }
            else
            {
                moduleOrder.Add(currentModule, orderIndex++);
            }
            var depands = currentModule.GetCustomAttributes(typeof(DependOnAttribute), false);
            if (depands != null && depands.Count()>0)
            {
                foreach (var depand in depands)
                {
                    var attr = depand as DependOnAttribute;
                    foreach (var module in attr.modules)
                    {
                        LoadModule(module);
                    }
                    
                }
            }
        }

    }
}

再一个是 ModuleStarter,不管是什么程序,总要有个入口的,而不管是怎么解耦,总会有地方要耦合的。而这个启动入口就是 ModuleStarter 了,这个有点类似 AbpBootstrapper,这个类提供开始初始化的静态方法 Start 以及一个获取容器的方法 GetContainer。

using ModuleCore.Container;
using ModuleCore.Module;
using System;

namespace ModuleCore
{
    public class ModuleStarter
    {
       

        public static void Start<TStartModule>()where TStartModule:BaseModule
        {
            var manager = new ModuleManager();
            manager.Initialize(typeof(TStartModule));
        }

        public static IIocContainer GetContainer()
        {
            return IocContainer.Getinstance();
        }
    }
}

然后其它模块大概过一下,都是demo,就随便写写:

Encrypt:这里 的 IMylogger  在创建 EncryptHelper时, ioc 容器会自动注入,所以不需要 new 则直接使用

public interface IEncryptHelper:IDependService
    {
        string EncryptData(string source);
    }

public class EncryptHelper : IEncryptHelper
    {
        private IMyLogger myLogger;
        public EncryptHelper(IMyLogger myLogger)
        {
            this.myLogger = myLogger;
        }
        public string EncryptData(string source)
        {
            myLogger.Write(source);
            return string.Format("<-{0}->", source);
        }
    }
[DependOn(typeof(LoggerModule))]
    public class EncryptModule : BaseModule
    {
        public override void Register()
        {
            Container.Register(Assembly.GetExecutingAssembly());
        }
    }

Logger:

public interface IMyLogger:IDependService
    {
        void Write(string log);
    }
public class MyLogger : IMyLogger
    {
        public void Write(string log)
        {
            Console.WriteLine(log);
        }
    }
public class LoggerModule : BaseModule
    {
        public override void Register()
        {
            Container.Register(Assembly.GetExecutingAssembly());
        }
    }

Web ,这个模块主要是 继承 HttpApplication 实现自己的 Application_Start ,这样我们可以在实际的web应用中指定启动module方法,以便在程序启动时,开启模块初始化。像这个模块注册虽然注册了,但也没有什么服务能别用到的,习惯当作一个模块。

public class MyWebApplication<StartModule> :System.Web.HttpApplication where StartModule:BaseModule
    {
        protected virtual void Application_Start()
        {
            ModuleStarter.Start<StartModule>();
            
        }
    }

    public class WebModule : BaseModule
    {
        public override void Register()
        {
            Container.Register(Assembly.GetExecutingAssembly());
        }
    } 

Web.Mvc 这个模块主要是在 Web模块的基础上,实现对 controller创建器做更改,更改为通过自己的容器创建。

public class MyControllerFactory: DefaultControllerFactory
    {
        private IIocContainer iocContainer;
        public MyControllerFactory(IIocContainer iocContainer)
        {
            this.iocContainer = iocContainer;
        }

        protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
        {
            try
            {
                return iocContainer.GetInstance(controllerType) as IController;
            }
            catch (Exception)
            {
                return base.GetControllerInstance(requestContext, controllerType);
            }
            
        }
    }

 public class MyWebMvcApplication<StartModule>: MyWebApplication<StartModule> where StartModule:BaseModule
    {
        protected override void Application_Start()
        {
            base.Application_Start();
            ControllerBuilder.Current.SetControllerFactory(new MyControllerFactory(ModuleStarter.GetContainer()));
            AreaRegistration.RegisterAllAreas();
        }
    }

[DependOn(typeof(WebModule))]
    public class WebMvcModule : BaseModule
    {
        public override void Register()
        {
            Container.Register(Assembly.GetExecutingAssembly());
        }
    }

最后一个,就是我们的应用层面:WebApplication1,在这里,一个是注册controller,一个是 更改 Global.asax 中 继承 的 application,同时传入启动module为自身。

[DependOn(typeof(WebMvcModule),typeof(EncryptModule),typeof(LoggerModule))]
    public class ApplicationModule : BaseModule
    {
        public override void Register()
        {
            var assembly = Assembly.GetExecutingAssembly();
            Container.Register(assembly);
            //注册控制器
            var controllerTypes = assembly.GetTypes().Where(t => t.Name.EndsWith("Controller"));
            foreach (var item in controllerTypes)
            {
                Container.Register(item);
            }
        }
    }


 public class MvcApplication : MyWebMvcApplication<ApplicationModule>
    {
        protected override void Application_Start()
        {
            base.Application_Start();
            RouteConfig.RegisterRoutes(RouteTable.Routes);
        }
    }

//然后 控制器直接这么用
 public class HomeController : Controller
    {
        private IEncryptHelper encrypt;
        private IMyLogger myLogger;
        public HomeController(IEncryptHelper encrypt, IIocContainer iocContainer)
        {
            myLogger = iocContainer.GetInstance<IMyLogger>();
            this.encrypt = encrypt;
        }
        // GET: Home
        public ActionResult Index()
        {
            ViewBag.Logger = myLogger?.ToString();
            ViewBag.Encrypt = encrypt?.ToString();
            var dd = encrypt.EncryptData("ssss");
            ViewBag.Data = dd;
            return View();
        }

我们注意到,我们的应用层不需要去管类型注册,直接在应用中使用 IMyLogger IEncryptHelper 这两个其它模块的服务 ,类型的注册在各自module初始化时都自己进行了处理,各司其职。

效果:

 

至此也差不多了,以上仅作为学习参考。

--------源码参考--------

 

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值