做.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初始化时都自己进行了处理,各司其职。
效果:
至此也差不多了,以上仅作为学习参考。
--------源码参考--------