前言

  在上一篇我大致的介绍了这个系列所涉及到的知识点,在本篇我打算把IOC这一块单独提取出来讲,因为IOC容器在解除框架层与层之间的耦合有着不可磨灭的作用。当然在本系列前面的三篇中我也提供了一种基于反射的解耦方式,但是始终不是很优雅,运用到项目中显得别扭。目前,我所掌握的IOC容器主要有两个:一个是 unity,另一个则是spring.net,经过慎重的思考我还是决定选择unity 2.0做为本系列的IOC容器,原因主要有两个:第一,他是一个轻量级的容器且师出名门(微软),第二,它提供了简单的拦截机制,在它的基础上实现AOP显得非常的简单,下面开始我们今天的议题......

什么是IOC容器

  IOC容器是对控制反转与依赖注入的一种实现,关于什么是控制反转,什么是依赖注入,网上一搜一大把,我这里就不在多说了,我们需要关注的就是IOC容器到底能够为我们做些什么事情,其实说白了,IOC容器就是通过相应的配置,用来为我们创建实例,使我们摆脱了new的魔咒,这在层与层之间的解耦中有着重要的意义,至于层次间为什么要解耦请参见我的第一篇, 本文着重介绍unity 2.0,您需要在项目中添加对Microsoft.Practices.Unity.dll与Microsoft.Practices.Unity.Configuration.dll的引用,下面我通过简单doom来讲述它的运用,程序如图

IOC项目引用了IService项目,但并未引用service项目,IService项目中定义的是服务接口,Service项目引用了IService项目并实现了里面的服务接口。我们现在要做的事情就是在IOC中采用IService接口标识服务,在调用时采用unity容器读取配置文件帮助我们把接口实例化,其具体的服务来自Service项目(我们的IOC项目没有引用Service项目所以是无法new的),为了很好的运用Unity容器,我做了一下封装,代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;
using System.Configuration;
using System.Reflection; namespace IOC
{
public class ServiceLocator
{
/// <summary>
/// IOC容器
/// </summary>
private readonly IUnityContainer container;
private static readonly ServiceLocator instance = new ServiceLocator();
/// <summary>
/// 服务定位器单例
/// </summary>
public static ServiceLocator Instance
{
get { return instance; }
}
private ServiceLocator()
{
//读取容器配置文件
UnityConfigurationSection section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
//创建容器
container = new UnityContainer();
//配置容器
section.Configure(container);
}
#region
/// <summary>
/// 创建构造函数参数
/// </summary>
/// <param name="overridedArguments"></param>
/// <returns></returns>
private IEnumerable<ParameterOverride> GetParameterOverrides(object overridedArguments)
{
List<ParameterOverride> overrides = new List<ParameterOverride>();
Type argumentsType = overridedArguments.GetType();
argumentsType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.ToList()
.ForEach(property =>
{
var propertyValue = property.GetValue(overridedArguments, null);
var propertyName = property.Name;
overrides.Add(new ParameterOverride(propertyName, propertyValue));
});
return overrides;
}
#endregion #region 公共方法
/// <summary>
/// 创建指定类型的容器
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T GetService<T>()
{
return container.Resolve<T>();
}
/// <summary>
/// 根据指定名称的注册类型
/// 创建指定的类型
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="name">注册类型配置节点名称</param>
/// <returns></returns>
public T GetService<T>(string name)
{
return container.Resolve<T>(name);
}
/// <summary>
/// 用指定的构造函数参数
/// 创建实体
/// </summary>
/// <typeparam name="T">实体类型</typeparam>
/// <param name="overridedArguments">属性名对应参数名,属性值对应
/// 参数值得动态参数实体</param>
/// <returns></returns>
public T GetService<T>(object overridedArguments)
{
var overrides = GetParameterOverrides(overridedArguments);
return container.Resolve<T>(overrides.ToArray());
}
/// <summary>
///
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="name"></param>
/// <param name="overridedArguments"></param>
/// <returns></returns>
public T GetService<T>(string name,object overridedArguments)
{
var overrides = GetParameterOverrides(overridedArguments);
return container.Resolve<T>(name, overrides.ToArray());
}
#endregion
}
}

好了,下面开始我们的测试,我们首先在IService项目创建一个ISayHello服务接口代码如下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; namespace IService
{
public interface ISayHello
{
string hello();
}
}

下面我们在Service项目中创建一个ChineseSayHello服务实现ISayHello接口代码如下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using IService; namespace Service
{
public class ChineseSayHello : ISayHello
{
public string hello()
{
return "你好";
}
}
}

下面我们创建一个测试页面Test.aspx,后台代码如下

using IService;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls; namespace IOC
{
public partial class Test : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
ISayHello sayhello = ServiceLocator.Instance.GetService<ISayHello>();
showInfo.InnerText = sayhello.hello();
}
}
}

好,下面来看一看我们的配置文件

<?xml version="1.0" encoding="utf-8"?>

<!--
有关如何配置 ASP.NET 应用程序的详细消息,请访问
http://go.microsoft.com/fwlink/?LinkId=169433
-->
<configuration>
<configSections>
<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,
Microsoft.Practices.Unity.Configuration"/>
</configSections>
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<container>
<register type="IService.ISayHello,IService" mapTo="Service.ChineseSayHello,Service">
</register>
</container>
</unity>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
</configuration>

<register/>节点是告诉容器我要向容器中注册一个ISayHello接口类型,并且当每次要创建的ISayHello类型的时候都映射到ChineseSayHello实例。我们执行程序,得到的结果为:你好,这说明我们的容器正确的为我们创建ChineseSayHello实例。如果有一天我们觉得ChineseSayHello不好,我们想换一个服务来实现ISayHello,比如:EnglishSayHello,从而替代ChineseSayHello,我们仅需要创建一个EnglishSayHello类型,修改下配置文件,如下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using IService; namespace Service
{
public class EnglishSayHello : ISayHello
{
public string hello()
{
return "hello";
}
}
}
<?xml version="1.0" encoding="utf-8"?>

<!--
有关如何配置 ASP.NET 应用程序的详细消息,请访问
http://go.microsoft.com/fwlink/?LinkId=169433
-->
<configuration>
<configSections>
<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,
Microsoft.Practices.Unity.Configuration"/>
</configSections>
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<container>
<!--<register type="IService.ISayHello,IService" mapTo="Service.ChineseSayHello,Service">
</register>-->
<register type="IService.ISayHello,IService" mapTo="Service.EnglishSayHello,Service">
</register>
</container>
</unity>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
</configuration>

程序的运行结果为:hello,实例创建成功。简简单单的一个例证,我们看见了IOC容器在给我们带来的巨大好处,我们IOC层根本不再依赖于具体的服务,我们想要什么实例配置下文件即可,这样极大的增加了程序的灵活性与可扩张性.

下面,我们来讨论一下容器实例的生命周期,也就是实例在容器中的存活时间。举个例子,我们在同样的配置文件下连续创建多个ISayHello服务实例,很显然,这样的多个实例是来自同样的类型的,现在我们关心的是容器是每一次都会为我们创建该类型的实例,还是仅仅只为我们创建一个,以后所有的ISayHello都引用同一个实例呢?我们测试下,代码如下

using IService;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls; namespace IOC
{
public partial class Test : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
ISayHello sayhello = ServiceLocator.Instance.GetService<ISayHello>();
ISayHello sayhello1 = ServiceLocator.Instance.GetService<ISayHello>();
showInfo.InnerText = sayhello.GetHashCode().Equals(sayhello1.GetHashCode()).ToString();
}
}
}

我们得到的结果是False,很显然容器每次都为我们去创建了一个实例。事实上Unity容器创建实例的机制是这样的:首先去容器中查找有没有这样的实例还保持在容器中,如果有的话则直接拿出来,如果没有的话则重新去创建一个。现在关键的问题是容器采用什么用的机制去保存这些被创建出来的实例,也就是实例在容器中的生命周期,在默认的情况下,实例被创建出来,容器即不再保存该实例,故在下次创建的时候容器找不到这样的实例,从而重新创建该类型实例,事实上实例的生命周期是可以配置的,我们甚至可以自定义实例的生命周期,下面我们修改下配置文件,设置实例的lifetime类型为singleton,即让实例永远保持在容器中,如下

<?xml version="1.0" encoding="utf-8"?>

<!--
有关如何配置 ASP.NET 应用程序的详细消息,请访问
http://go.microsoft.com/fwlink/?LinkId=169433
-->
<configuration>
<configSections>
<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,
Microsoft.Practices.Unity.Configuration"/>
</configSections>
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<container>
<!--<register type="IService.ISayHello,IService" mapTo="Service.ChineseSayHello,Service">
</register>-->
<register type="IService.ISayHello,IService" mapTo="Service.EnglishSayHello,Service">
<lifetime type="singleton"/>
</register>
</container>
</unity>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
</configuration>

我们在运行程序,得到的结果是:True,说明我们每次都引用了同一个实例,容器很好的帮我们实现了单例模式,除了singleton外,容器还默认了其他的几种实例生命周期,这里就不在多说了。注:我们所说的实例生命周期不是指实例的创建到销毁,而是指实例在容器中创建,受容器管辖的时间范围。

Unity容器支持为一个接口或者基类注册多个映射节点,但是每个节点需要采用不同的名称标识,在创建实例的时候,也通过该节点名称来创建指定的映射实例,例如

<?xml version="1.0" encoding="utf-8"?>

<!--
有关如何配置 ASP.NET 应用程序的详细消息,请访问
http://go.microsoft.com/fwlink/?LinkId=169433
-->
<configuration>
<configSections>
<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,
Microsoft.Practices.Unity.Configuration"/>
</configSections>
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<container>
<register type="IService.ISayHello,IService" mapTo="Service.ChineseSayHello,Service">
</register>
<register name="english" type="IService.ISayHello,IService" mapTo="Service.EnglishSayHello,Service">
</register>
</container>
</unity>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
</configuration>
using IService;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls; namespace IOC
{
public partial class Test : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
ISayHello sayhello = ServiceLocator.Instance.GetService<ISayHello>();
ISayHello sayhello1 = ServiceLocator.Instance.GetService<ISayHello>("english");
showInfo.InnerText = string.Format("{0}+{1}", sayhello1.hello(), sayhello.hello());
//showInfo.InnerText = sayhello.GetHashCode().Equals(sayhello1.GetHashCode()).ToString();
}
}
}

结果为:hello+你好,我们成功的通过了配置文件中的注册节点名称来创建我们的具体服务实例。我们知道创建实例是需要调用实例的构造函数的,很显然容器默认的为我们调用了构造函数,倘若构造函数带有参数,则容器则会创建相应的参数实例。现在问题来了,假如我的参数是一个接口或者抽象类型怎么办? 很显然要能创建这样的参数我们就必须知道参数的映射类型, 看如下例子,我们在IService项目中重新创建一个接口

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; namespace IService
{
public interface ISay
{
string Say();
}
}

我们写一个服务实现该接口

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using IService; namespace Service
{
public class ComSayHello_V2 : ISay
{
public ISayHello Chinese { get; set; } public ComSayHello_V2(ISayHello chinese)
{
this.Chinese = chinese;
} public string Say()
{
return this.Chinese.hello();
}
}
}

配置文件如下

<?xml version="1.0" encoding="utf-8"?>

<!--
有关如何配置 ASP.NET 应用程序的详细消息,请访问
http://go.microsoft.com/fwlink/?LinkId=169433
-->
<configuration>
<configSections>
<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,
Microsoft.Practices.Unity.Configuration"/>
</configSections>
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<container>
<register type="IService.ISayHello,IService" mapTo="Service.ChineseSayHello,Service">
</register>
<register name="english" type="IService.ISayHello,IService" mapTo="Service.EnglishSayHello,Service">
</register>
<register type="IService.ISay,IService" mapTo="Service.ComSayHello_V2,Service">
</register>
</container>
</unity>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
</configuration>
using IService;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls; namespace IOC
{
public partial class Test : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
//ISayHello sayhello = ServiceLocator.Instance.GetService<ISayHello>();
//ISayHello sayhello1 = ServiceLocator.Instance.GetService<ISayHello>("english");
//showInfo.InnerText = string.Format("{0}+{1}", sayhello1.hello(), sayhello.hello());
//showInfo.InnerText = sayhello.GetHashCode().Equals(sayhello1.GetHashCode()).ToString();
ISay say = ServiceLocator.Instance.GetService<ISay>();
showInfo.InnerText=say.Say();
}
}
}

我们得到结果:你好。在配置文件中我们添加了两个注册节点,从结果中我们看见,容器默认选择了未命名的节点,倘若我们注释该节点程序将报错,程序没办法自动识别带名称的节点,要想让程序识别带名称的节点我们需要配置构造函数参数,配置如下

<?xml version="1.0" encoding="utf-8"?>

<!--
有关如何配置 ASP.NET 应用程序的详细消息,请访问
http://go.microsoft.com/fwlink/?LinkId=169433
-->
<configuration>
<configSections>
<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,
Microsoft.Practices.Unity.Configuration"/>
</configSections>
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<container>
<!--<register type="IService.ISayHello,IService" mapTo="Service.ChineseSayHello,Service">
</register>-->
<register name="english" type="IService.ISayHello,IService" mapTo="Service.EnglishSayHello,Service">
<!--<lifetime type="singleton"/>-->
</register>
<register type="IService.ISay,IService" mapTo="Service.ComSayHello_V2,Service">
<constructor>
<param name="say" dependencyName="english"></param>
</constructor>
</register>
</container>
</unity>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
</configuration>

结果正确的显示为:hello,在这里顺便提一下如果我们取消english注册节点lifetime的注释,我们会发现每次创建ComSayHello_V2的参数将来自同一个实例的引用,原因请参见,上文的实例生命周期。
    当然我们也可以直接在配置文件的构造函数中指定,参数类型而避免注册其他类型节点,配置文件代码如下,结果一样

<?xml version="1.0" encoding="utf-8"?>

<!--
有关如何配置 ASP.NET 应用程序的详细消息,请访问
http://go.microsoft.com/fwlink/?LinkId=169433
-->
<configuration>
<configSections>
<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,
Microsoft.Practices.Unity.Configuration"/>
</configSections>
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<container>
<register type="IService.ISay,IService" mapTo="Service.ComSayHello_V2,Service">
<constructor>
<param name="say" dependencyType="Service.EnglishSayHello,Service"></param>
</constructor>
</register>
</container>
</unity>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
</configuration>

其实,我们还能够在配置文件中给参数赋值,但是如果参数是一个复杂类型,比如类的时候,我们就需要一个转换器,把字符串类型的值转换为指定的赋值类型,因为在配置文件中我们赋值的类型只能是string。转换器在平时实践中用的少,所以我不打算多说。需要注意的是,如果我们的类中有多个构造函数的话,那么容器默认总会选择参数最多的那个构造函数。
   以上所介绍的归根到底也只是一种构造函数注入。其实Unity还提供能属性注入与方法注入,即在创建实例的时候动态为某个属性赋值或者调用某个方法,其实这个要做到也蛮简单的,我们只需要在相应的属性上面打上[Dependency]特性,在方法上打上[InjectionMethod]特性即可,但是这两种方式对类的侵入性太强,不推荐使用

总结

本文简单的演示了Unity IOC的一些使用方法,因为在我的框架中,Unity在层次解耦中充当了重要的作用,除此之外Unity其实还能实现的AOP拦截,但是由于篇幅的原因不再多讲,在这里要提醒大家务必理解实体的生命周期,因为这对实现单元工作模式有着重要的意义。在我的系列前三篇中,我都是采用了是反射来解耦,有兴趣的朋友可以尝试下用Unity取代它。我目前写的案例与前面系列的版本框架有很大的差异,所以有些知识点必须和大家说明,相信在接下来的一到两篇中,就能与大伙见面,祝大伙周末愉快。本篇测试源码请点击这里

企业级应用框架(五)IOC容器在框架中的应用的更多相关文章

  1. IOC容器在框架中的应用

    IOC容器在框架中的应用 前言 在上一篇我大致的介绍了这个系列所涉及到的知识点,在本篇我打算把IOC这一块单独提取出来讲,因为IOC容器在解除框架层与层之间的耦合有着不可磨灭的作用.当然在本系列前面的 ...

  2. Spring框架学习[IoC容器高级特性]

    1.通过前面4篇文章对Spring IoC容器的源码分析,我们已经基本上了解了Spring IoC容器对Bean定义资源的定位.读入和解析过程,同时也清楚了当用户通过getBean方法向IoC容器获取 ...

  3. Spring框架 之IOC容器 和AOP详解

    主要分析点: 一.Spring开源框架的简介  二.Spring下IOC容器和DI(依赖注入Dependency injection) 三.Spring下面向切面编程(AOP)和事务管理配置  一.S ...

  4. Spring框架及IOC容器

    Spring是一个非常活跃的开源框架, 它是一个基于IOC和AOP来构架多层JavaEE系统的框架,它的主要目地是简化企业开发.Spring以一种非侵入式的方式来管理你的代码, Spring提倡”最少 ...

  5. dreamvc框架(一)ioc容器的集成

    我的dreamvc框架最终写得差点儿相同了,借鉴了非常多开源框架,SpringMVC.Struts2等,眼下放在github上面.地址请猛戳我 写得差点儿相同了,是要写一个总结,把自己当时的思路记录下 ...

  6. 【.NET6+WPF】WPF使用prism框架+Unity IOC容器实现MVVM双向绑定和依赖注入

    前言:在C/S架构上,WPF无疑已经是"桌面一霸"了.在.NET生态环境中,很多小伙伴还在使用Winform开发C/S架构的桌面应用.但是WPF也有很多年的历史了,并且基于MVVM ...

  7. 基于nutz框架理解Ioc容器

    同样我们从问题入手去验证以及去理解Ioc容器都做了哪些事情: 1.nutz是有几种方式获取需要容器管理bean的信息? 第一种是使用json格式的文件进行配置,如: 第二种:使用注解@IocBean ...

  8. ASP.NET Web API 框架研究 IoC容器 DependencyResolver

    一.概念 1.IoC(Inversion of Control),控制反转 即将依赖对象的创建和维护交给一个外部容器来负责,而不是应用本身.如,在类型A中需要使用类型B的实例,而B的实例的创建不是由A ...

  9. 《精通Spring 4.X企业应用开发实战》读书笔记1-1(IoC容器和Bean)

    很长一段时间关注在Java Web开发的方向上,提及到Jave Web开发就绕不开Spring全家桶系列,使用面向百度,谷歌的编程方法能够完成大部分的工作.但是这种不系统的了解总觉得自己的知识有所欠缺 ...

随机推荐

  1. content:attr()

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  2. DataGrid能否动态合并一笔订单下面的多个交易

    /** * author ____′↘夏悸 * create date 2012-11-5 **/ $.extend($.fn.datagrid.methods, { autoMergeCells : ...

  3. 清理Xcode中无用的 Provisioning Profile.

    Xcode中如果添加过多个开发者账号, 慢慢就会发现 Provisioning Profile 有很多, 无用的 Provisioning Profile Xcode也不会自动删除, 想要清理的话, ...

  4. bootstrap基本标签总结[转]

    文件头: DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <title> ...

  5. boost库的使用(一)

    参考http://www.cnblogs.com/lexus/archive/2012/07/15/2592250.html bjam stage --without-python --toolset ...

  6. Android 不能勾选 Project Build Target

    再勾选完project bulid target,从新返回这个页面,发现还是没有被勾选上. 从新刷新一下项目,原因是project.properties配置文件没有加载上. 下回导入的项目第一件事就是 ...

  7. Linux查看系统版本信息的

    // 显示电脑以及操作系统的相关信息 uname -a // 正在运行的内核版本 cat /proc/version // 显示的是发行版本信息 cat /etc/issue // 适用于所有的lin ...

  8. Tsinsen A1516. fx 数位dp

    题目: http://www.tsinsen.com/A1516 A1516. fx 时间限制:2.0s   内存限制:256.0MB    总提交次数:164   AC次数:72   平均分:51. ...

  9. HW5.12

    public class Solution { public static void main(String[] args) { printChars('1', 'Z', 10); } public ...

  10. [转]2-SAT问题及其算法

    转自:http://www.cppblog.com/MatoNo1/archive/2011/07/13/150766.html [2-SAT问题]现有一个由N个布尔值组成的序列A,给出一些限制关系, ...