您的位置:首页 > 其它

MVC+IOC框架下基于构造器注入的原理分析

2016-07-13 18:08 309 查看
有不少人可能会认为,如果抽象工厂模式再加上反射的话,跟IOC容器非常类似。但实际不是这样,IOC有两个特性,控制反转和依赖注入,两者其相辅相成,密不可分,这两点工厂模式都没有做到,我们依然要通过手动调用工厂的方法生成实例,控制方依然没有改变,就是说创建对象的工作依然是在原来的调用者内部完成。

IOC容器有三种注入途径:接口,构造器,setter。由于构造器注入使用起来简单清晰,所以实际项目中使用比较广泛。

为了让描述能够更直观,我们定义好三个主要角色:调用者(Caller),被依赖类的接口(IUser),被依赖的类(User);

简要的来说,如果Caller当中需要调用User的话,IOC容器会通过反射机制生成一个User的实例instance,当调用者Caller被请求时,将调用者Caller实例化,并将实例instance以参数形式传入调用者Caller的构造器中,从而创建一个调用者对象。

其中构造器的参数是接口IUser,因为接口具备多态性,对具体实现的依赖。

从这句话可以分析出1个事:IOC不仅负责创建被依赖类User的实例,而且也负责创建调用者Caller的实例,这一步发生在调用者被请求的时候,同时也是依赖注入的时候。

IOC执行流程:

1. 通过XML配置接口和实现类的映射关系,IOC容器初始化时读取配置,先反射遍历所有的程序集是否物理存在,如果不存在,抛出异常,流程终止;

2. IOC容器将遍历的结果以k-v的方式缓存在公共对象中,key是接口,value是实现类,假设变量名为kv1;

3. 将需要IOC容器管控的调用者类型注册到IOC中,一旦调用者被请求,将被IOC拦截。

4. IOC容器遍历所有注册类的构造器,将构造器的参数也以k-v的方式缓存,假设变量名为kv2;

5. 调用者被请求创建时,IOC容器获取到调用者类型,并且获取构造器函数的参数,通过索引key在kv2中找到对应的参数类型(接口),再通过参数类型在kv1中找到对应的实现,然后通过反射生成实例,注入到调用者的构造器中,最后创建调用者对象。

整个过程大概就是这样,主要涉及到几点内容:

1. XML文档遍历;

2. 反射机制;

3. 哈希表;

4. 拦截调用者请求;

5. 对调用者构造器进行依赖注入。

其中第4点需要特别注意,因为通常在B/S模式下,客户端都是以http的方式请求服务器,服务器再根据请求创建调用者实例,那么如何在默认的创建之前拦截请求,并且自行创建调用者对象,就成了构建IOC容器的关键。虽然IOC并不是MVC的专属搭档,但是由于MVC在自定义控制器方面的便捷性让IOC的拦截工作变得简单。

那么,早期的WebForm又该如何实现呢?其实也很简单。但本文重点阐述的是IOC+MVC的搭配,打字太累,就不细说了。

在MVC框架中,我们在控制器中会显示的实现一个带接口参数的构造函数,IOC就是注入到这个函数里的,但控制器是一个类,这个类的实例肯定是MVC自己创建的,如果我们要赶在MVC之前自己创建控制器实例,应该怎么做呢?

我们只要搞清楚MVC是如何做的就可以了,MVC创建控制器是用的工厂模式,通过路由传递的url解析出控制器名称,MVC根据控制器名再通过工厂生产出对应的实例,大概过程就是这样。这个工厂的名字就叫DefaultControllerFactory,看名字就知道CreateController这个虚方法就是创建的地方了,微软这么设计明摆着就是等我们来继承和重写的。但实际上CreateController里面还有业务逻辑的封装,源码如下:

public virtual IController CreateController(RequestContext requestContext, string controllerName) {
Type controllerType = GetControllerType(requestContext, controllerName);
IController controller = GetControllerInstance(requestContext, controllerType);
return controller;
}


代码段中的GetControllerInstance虚方法才是创建的最后环节,所以我们可以重写CreateController也可以重写GetControllerInstance,都没问题。但是前者还要自己获取控制器类型,不如后者方便,直接传回的Type,所以我们肯定优先选择重写GetControllerInstance。

先创建一个自定义工厂类,继承DefaultControllerFactory,然后重写GetControllerInstance,将对应接口的实例传入构造器创建控制器实例。由于IOC里面有大量的机制比较复杂,这里我们简化代码,假设直接获取到了要注入的User对象:

//自定义控制器工厂类
public class MyControllerFactory : DefaultControllerFactory
{
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
Assembly ass = Assembly.Load("Demo");
Type type = ass.GetType("Demo.User");
IUser user = (IUser)Activator.CreateInstance(type);
object[] args = { user };
IController controller = (IController)Activator.CreateInstance(controllerType, args);
return controller;
}
}


//在应用程序启动时,将自定义的工厂设置为MVC默认的
ControllerBuilder.Current.SetControllerFactory(new MyControllerFactory());


//控制器代码
public class HomeController : Controller
{
private IUser _user;
public HomeController(IUser user)
{
this._user = user;
}

public ActionResult Index()
{
ViewBag.User = _user.Get();//简单的输出字符串
return View();
}
}


视图部分就不写了。启动程序,就能看到_user.Get()的结果。至此,我们已经根据IOC工作原理自己实现了一个简单的demo,文章仅供学习,Over!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: