依赖注入与Unity
关于控制反转(Inversion of Control)和依赖注入(Dependency Injection)大家网上可以找下相关概念,在《小菜学习设计模式(五)—控制反转(Ioc)》这篇文章中本人也有详细的解释,这边再说明下,有很多人把控制反转和依赖注入混为一谈,虽然在某种意义上来看他们是一体的,但好像又有些不同,就比如在上篇文章中所提到的示例。控制反转(Ioc)可以看成自来水厂,那自来水厂的运行就可以看作依赖注入(DI),Ioc是一个控制容器,DI就是这个容器的运行机制,有点像国家主席和总理的意思。
构造器注入
构造器注入(Constructor Injection):IoC容器会智能地选择选择和调用适合的构造函数以创建依赖的对象。如果被选择的构造函数具有相应的参数,IoC容器在调用构造函数之前解析注册的依赖关系并自行获得相应参数对象。
通过上面的定义看以看出,使用构造器注入需要在在构造函数中传递一个抽象参数,Ioc会自动解析具象所依赖的抽象并注册给具象,我们还是用上篇喝水作为示例:
/// <summary>
/// 人接口
/// </summary>
public interface IPeople
{
void DrinkWater();
}
/// <summary>
/// 村民
/// </summary>
public class VillagePeople : IPeople
{
IWaterTool _pw;
public VillagePeople(IWaterTool pw)
{
_pw = pw;
}
public void DrinkWater()
{
Console.WriteLine(_pw.returnWater());
}
}
/// <summary>
/// 压水井
/// </summary>
public class PressWater : IWaterTool
{
public string returnWater()
{
return "地下水好甜啊!!!";
}
}
/// <summary>
/// 获取水方式接口
/// </summary>
public interface IWaterTool
{
string returnWater();
}
代码很简单,PressWater
依赖于IWaterTool
,在VillagePeople
构造函数中传递一个IWaterTool
的抽象,我们看下调用代码:
static void Main(string[] args)
{
/*
IWaterTool waterTool = new PressWater();
IPeople people = new VillagePeople(waterTool);
people.DrinkWater(); //喝水
*/
UnityContainer container = new UnityContainer(); //创建容器
container.RegisterType<IWaterTool, PressWater>(); //注册依赖对象
IPeople people = container.Resolve<VillagePeople>(); //返回调用者
people.DrinkWater(); //喝水
}
上面主要用到Unity的RegisterType
和Resolve
的泛型方法,我们看下RegisterType的方法签名:
https://github.com/unitycontainer/abstractions/blob/master/src/Utility/UnityContainerExtensions.cs
/// <summary>
/// Register a type mapping with the container.
/// </summary>
/// <remarks>
/// <para>
/// This method is used to tell the container that when asked for type <typeparamref name="TFrom"/>,
/// actually return an instance of type <typeparamref name="TTo"/>. This is very useful for
/// getting instances of interfaces.
/// </para>
/// <para>
/// This overload registers a default mapping and transient lifetime.
/// </para>
/// </remarks>
/// <typeparam name="TFrom"><see cref="Type"/> that will be requested.</typeparam>
/// <typeparam name="TTo"><see cref="Type"/> that will actually be returned.</typeparam>
/// <param name="container">Container to configure.</param>
/// <param name="injectionMembers">Injection configuration objects.</param>
/// <returns>The <see cref="Unity.IUnityContainer"/> object that this method was called on (this in C#, Me in Visual Basic).</returns>
public static IUnityContainer RegisterType<TFrom, TTo>(this IUnityContainer container, params InjectionMember[] injectionMembers) where TTo : TFrom
{
return (container ?? throw new ArgumentNullException(nameof(container))).RegisterType(typeof(TFrom), typeof(TTo), null, null, injectionMembers);
}
我们可以看到RegisterType
的第一个参数是this IUnityContainer container,我们上面调用的时候并没有传递一个IUnityContainer 类型的参数, 为什么这里会有一个this关键字,做什么用?其实这就是扩展方法。这个扩展方法在静态类中声明,定义一个静态方法(UnityContainerExtensions类和RegisterType都是静态的), 其中第一个参数定义可它的扩展类型。RegisterType方法扩展了UnityContainerExtensions类,因为它的第一个参数定义了 IUnityContainer(UnityContainerExtensions的抽象接口) 类型,为了区分扩展方法和一般的静态方法,扩展方法还需要给第一个参数使用this关键字。
还有就是RegisterType
的泛型约束 where TTo : TFrom
; TTo
必须是TFrom
的派生类,就是说TTo
依赖于TFrom
。
我们再来看下Resolve
泛型方法的签名:
//
// 摘要:
// Resolve an instance of the default requested type from the container.
// 解决从容器的默认请求的类型的实例,就是获取调用者的对象。
//
// 参数:
// container:
// Container to resolve from.
//
// overrides:
// Any overrides for the resolve call.
//
// 类型参数:
// T:
// System.Type of object to get from the container.
//
// 返回结果:
// The retrieved object.
public static T Resolve<T>(this IUnityContainer container, params ResolverOverride[] overrides);
“Resolve an instance of the default requested type from the container”,这句话可以翻译为:解决从容器的默认请求的类型的实例,就是获取调用者的对象。
关于RegisterType
和Resolve
我们可以用自来水厂的例子来说明,请看下面:
RegisterType
:可以看做是自来水厂决定用什么作为水源,可以是水库或是地下水,我只要“注册”开关一下就行了。Resolve
:可以看做是自来水厂要输送水的对象,可以是农村或是城市,我只要“控制”输出就行了。
Dependency属性注入
属性注入(Property Injection):如果需要使用到被依赖对象的某个属性,在被依赖对象被创建之后,IoC容器会自动初始化该属性。
/// <summary>
/// 其他人
/// </summary>
public class OtherPeople : IPeople
{
[Unity.Attributes.Dependency]
public IWaterTool _pw { get; set; }
public void DrinkWater()
{
Console.WriteLine(_pw.returnWater());
}
}
调用方式和构造器注入一样,通过RegisterType<IWaterTool, PressWater>();注入就可以了,除了使用RegisterType
方法注册,我们还可以在配置文件中注册,[Dependency]和RegisterType方式其实都会产生耦合度,我们要添加一个属性或是修改一中注册都会去修改代码,我们要做的就是代码不去修改,只要修改配置文件了,这个在下面有讲解,这边就不多说,我们先看下使用UnityConfigurationSection的Configure方法加载配置文件注册:
**config\Unity.Config.xml**
```
-->
```
调用:
```
static void Main(string[] args)
{
ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
fileMap.ExeConfigFilename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory + "config\\Unity.Config.xml");
Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
UnityConfigurationSection section =
(UnityConfigurationSection)configuration.GetSection(UnityConfigurationSection.SectionName);
UnityContainer container = new UnityContainer();//创建容器
section.Configure(container, "defaultContainer");
IPeople people = container.Resolve<IPeople>();//返回调用者
people.DrinkWater();//喝水
}
# InjectionMethod方法注入 #
>方法注入(Method Injection):如果被依赖对象需要调用某个方法进行相应的初始化,在该对象创建之后,IoC容器会自动调用该方法
方法注入和属性方式使用一样,方法注入只需要在方法前加`[InjectionMethod]`标记就行了,从方法注入的定义上看,只是模糊的说对某个方法注入,并没有说明这个方法所依赖的对象注入,所依赖的对象无非就三种:参数、返回值和方法内部对象引用,我们做一个示例试下:
/// <summary>
/// 村民3
/// </summary>
public class VillagePeople3 : IPeople
{
public IWaterTool tool; //对象引用
public IWaterTool tool2; //参数
public IWaterTool tool3; //返回值
[InjectionMethod]
public void DrinkWater()
{
if (tool == null)
{}
}
[InjectionMethod]
public void DrinkWater2(IWaterTool tool2)
{
this.tool2 = tool2;
}
[InjectionMethod]
public IWaterTool DrinkWater3()
{
return tool3;
}
}
调用代码:
ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
fileMap.ExeConfigFilename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory + "config\\Unity.Config.xml");
Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
UnityConfigurationSection section =
(UnityConfigurationSection)configuration.GetSection(UnityConfigurationSection.SectionName);
UnityContainer container = new UnityContainer();//创建容器
section.Configure(container, "defaultContainer2");
VillagePeople3 people = container.Resolve<IPeople>() as VillagePeople3;//返回调用者
Console.WriteLine("people.tool == null(引用) ? {0}", people.tool == null ? "Yes" : "No");
Console.WriteLine("people.tool2 == null(参数) ? {0}", people.tool2 == null ? "Yes" : "No");
Console.WriteLine("people.tool3 == null(返回值) ? {0}", people.tool3 == null ? "Yes" : "No");
container.Resolve<IPeople>() as VillagePeople03;其实多此一举,因为已经在配置文件注册过了,不需要再进行转化,这边只是转化只是方便访问VillagePeople03对象的几个属性值,我们看下运行效果:
![](https://images2018.cnblogs.com/blog/196558/201803/196558-20180304152807090-161992613.png)
结果不言而喻,其实我们理解的方法注入就是对参数对象的注入,从`typeConfig`节点`-method`节点`-param`节点就可以看出来只有参数的配置,而并没有其他的配置,关于`typeConfig`下面会讲到。
# 非泛型注入 #
我们也可以使用非泛型注入,代码如下:
public static void FuTest04()
{
UnityContainer container = new UnityContainer();//创建容器
container.RegisterType(typeof(IWaterTool), typeof(PressWater));//注册依赖对象
IPeople people = (IPeople)container.Resolve(typeof(VillagePeople));//返回调用者
people.DrinkWater();//喝水
}
# 标识键 #
我们知道,Unity提供了对象的容器,那么这个容器是如何进行索引的呢?也就是说,容器内的单元是如何标识的呢?在Unity中,标识主要有两种方式, 一种是直接使用接口(或者基类)作为标识键,另一种是使用接口(或者基类)与名称的组合作为标识键,键对应的值就是具体类。
第一种使用接口(或者基类)作为标识键:
container.RegisterType<IWaterTool, PressWater>();
代码中的IWaterTool就是作为标识键,你可以使用`基类或是抽象类`作为标识,获取注册对象:container.Resolve<IWaterTool>(),如果一个Ioc容器容器里面注册了多个接口或是基类标示,我们再这样获取就不知道注册的是哪一个?怎么解决,就是用`接口或是基类与名称`作为标识键,示例代码如下:
public static void FuTest05()
{
UnityContainer container = new UnityContainer();//创建容器
//("WaterTool1")括号里面的name: <param name="name">将用于请求类型的名称.</param>
container.RegisterType<IWaterTool, PressWater>("WaterTool1");//注册依赖对象WaterTool1
container.RegisterType<IWaterTool, PressWater>("WaterTool2");//注册依赖对象WaterTool2
IWaterTool wt = container.Resolve<IWaterTool>("WaterTool1");//返回依赖对象WaterTool1
var list = container.ResolveAll<IWaterTool>();//返回所有注册类型为IWaterTool的对象
}
<img src="https://images0.cnblogs.com/i/435188/201404/181423367752431.jpg" alt="">
# ContainerControlledLifetimeManager单例 #
关于单例概念可以参考《[小菜学习设计模式(二)—单例(Singleton)模式](http://www.cnblogs.com/xishuai/p/3509346.html)》这篇文章,为了实现单例模式,我们通常的做法是,在类中定义一个方法如GetInstance,判断如果实例为null则新建一个实例,否则就返回已有实例。但是我觉得这种做法将对象的生命周期管理与类本身耦合在了一起。所以我觉得遇到需要使用单例的地方,应该将生命周期管理的职责转移到对象容器Ioc上,而我们的类依然是一个干净的类,使用Unity创建单例代码:
/// <summary>
/// ContainerControlledLifetimeManager 单例
/// </summary>
///
public static void FuTest07()
{
UnityContainer container = new UnityContainer();//创建容器
container.RegisterType<IWaterTool, PressWater>(new ContainerControlledLifetimeManager());//注册依赖对象
IPeople people = container.Resolve<VillagePeople>();//返回调用者
people.DrinkWater();//喝水
}
上面演示了将IWaterTool注册为PressWater,并声明为单例,`ContainerControlledLifetimeManager`字面意思上就是Ioc容器管理声明周期,我们也可以不使用类型映射,将某个类注册为单例:
container.RegisterType(new ContainerControlledLifetimeManager());
除了将类型注册为单例,我们也可以将已有对象注册为单例,使用`RegisterInstance`方法,示例代码:
PressWater pw = new PressWater();
container.RegisterInstance(pw);
上面的代码就表示将PressWater的pw对象注册到Ioc容器中,并声明为单例。
如果我们在注册类型的时候没有指定ContainerControlledLifetimeManager对象,Resolve获取的对象的生命周期是短暂的,Ioc容器并不会保存获取对象的引用,就是说我们再次Resolve获取对象的时候,获取的是一个全新的对象,如果我们指定ContainerControlledLifetimeManager,类型注册后,我们再次Resolve获取的对象就是上次创建的对象,而不是再重新创建对象,这也就是单例的意思。
# Unity的app.config节点配置 #
上面所说的三种注入方式,包括单例创建都是在代码中去配置的,当然只是演示用,这种配置都会产生耦合度,比如添加一个属性注入或是方法注入都要去属性或是方法前加`[Dependency]`和`[InjectionMethod]`标记,我们想要的依赖注入应该是去配置文件中配置,当系统发生变化,我们不应去修改代码,而是在配置文件中修改,这才是真正使用依赖注入解决耦合度所达到的效果,先看下Unity完整的配置节点:
![](https://images2018.cnblogs.com/blog/196558/201803/196558-20180312193938928-1759222448.png)
上面的图大家可能都见过,我再贴一下Unity示例节点配置,原文地址:msdn.microsoft.com/en-us/library/ff647848.aspx,稍微翻译了下。
<instances>
<add name="MyInstance1" type="System.String" value="Some value" />
<add name="MyInstance2" type="System.DateTime" value="2008-02-05T17:50:00" />
</instances>
<extensions>
<add type="MyApp.MyExtensions.SpecialOne" />
</extensions>
<extensionConfig>
<add name="MyExtensionConfigHandler" type="MyApp.MyExtensions.SpecialOne.ConfigHandler" />
</extensionConfig>
</container>
```
配置过unity的朋友看一下可能就清楚,这边我们再简单说下:
* Unity的配置节的名称为”Unity",节处理程序的类型为 `Microsoft.Practices.Unity.Configuration.UnityConfigurationSection`,它包含在程序集~~Microsoft.Practices.Unity.Configuration~~ `Unity.Configuration(5.1.3版本)`中,当前程序添加该程序集的引用。
* `typeAliases`管理生命周期类型,以及一些类型别名的设置,方便我们映射对象的编写,比如同一个类型注册多次,我们只要在`typeAlias`添加一个类型别名,这样我们再添加这个类型映射的时候只要写个别名就可以了。
* `containers`是容器container集合,我们可以配置多个容器类型,通过`Name`属性就可以访问,比如访问`defaultContainer`容器代码:`configuration.Configure(container, "defaultContainer")`;
* `container`为容器管理,下面包含多个类型映射,我们平常使用的构造器注册、属性注册和方法注册,就可以在constructor、property、method节点进行配置。
根据上面的配置,我做了一个简单的方法注入示例,配置注册信息都是在文件中,并不是使用RegisterType方法进行注册,但是报下面错误:
无法识别的元素“typeConfig”。示例是完全按照unity配置说明进行配置的,Google或是百度都找不到问题所在,也试了很多种方式,最后Google英文“Unrecognized element 'typeConfig'”,终于找到问题:http://unity.codeplex.com/discussions/209002。英文我大概看得不是很懂,好像是unity版本问题,unity2.0配置文件中并没有typeConfig节点,而我们使用的unity程序集是最新的,而并没有识别typeConfig节点,这个问题很严重,也花了我两天时间,就像那位提问者最后所说:“I can not tell you how grateful I am, I've spent the last 2 days trying to figure this one out.”,虽然问题原因找到了,但是是通过英文搜索找到了,就是说国外遇到过类似问题,难道我们国内没有遇到过,郁闷。
大家可能有些纳闷,为什么你上面几个示例使用的unity配置没有报错,我当时在写示例的时候只是使用简单的类型映射,并不是使用完整的unity配置文件,然后就网上找了下,就找到了http://msdn.microsoft.com/en-us/library/ff647848.aspx,当时没有注意版本问题,所以就出现了上面的问题,我们上面示例unity配置文件中Register节点就是unity2.0的配置,这也是为什么上面示例可以运行的原因。
unity2.0的配置文件配置说明找了好久,终于在MSDN找到了:http://msdn.microsoft.com/en-us/library/ff660914%28v%3Dpandp.20%29.aspx#config_constructor,没有中文版本,英文不好的朋友可以使用Google简单翻译下,注意这段话“Unity 2.0 uses a new streamlined configuration schema for configuring Unity.”,这才是我们要使用的unity2.0的配置文件,其实和1.2版本差不多,只不过是简化了一些东西,配置起来也更加方便,这边就不多说了,列一下配置目录:
- The <unity> Configuration Section
- The <container> Element
- The <register> Element
- The <lifetime> Element
- The <constructor> Element
- The <property> Element
- The <method> Element
- The <param> Element
- The <dependency> Element
- The <value> Element
- The <optional> Element
- The <array> Element
- The <extension> Element
- The <instance> Element
- The <namespace > Element
- The <alias> Element
- The <sectionExtension> Element
在unity1.2中我们使用构造器注入、属性注入和方法注入会有parameterType节点,就是说在constructor、property和method这些节点可以配置这些方式注入所依赖的类型,但是在unity2.0并不存在parameterType节点了,所有类型注册都是通过register节点进行配置的,相当于unity1.2中的type节点,虽然unity2.0存在constructor、property和method节点,但我感觉只是针对构造器、属性和方法本身进行注入。
另外在unity2.0配置中alias节点下的生命管理周期配置并不需要了,比如我们创建一个单例注册类型,只需要配置下面就可以了:
<containers>
<container name="defaultContainer">
<register type="UnityContainerDemo.IPeople, UnityContainerDemo" mapTo="UnityContainerDemo.VillagePeople01, UnityContainerDemo">
<lifetime type="singleton" />
</register>
<register type="UnityContainerDemo.IWaterTool, UnityContainerDemo" mapTo="UnityContainerDemo.PressWater, UnityContainerDemo"/>
</container>
</containers>
</unity>
并不需要再像unity1.2中创建下面节点:
<typeAlias alias="singleton" type="Microsoft.Practices.Unity.ContainerControlledLifetimeManager,Microsoft.Practices.Unity" />
转自:Unity依赖注入使用详解 - 田园里的蟋蟀 - 博客园 http://www.cnblogs.com/xishuai/p/3670292.html
依赖注入与Unity的更多相关文章
- 控制反转、依赖注入、Unity容器
控制反转原则 依赖注入 Install-Package Unity:https://www.nuget.org/packages/Unity/ Github:https://github.com/un ...
- 依赖注入与Unity(一) 介绍
在你学习依赖注入和Unity之前,你需要明白你为什么要使用它们.为了明白为什么要使用它们,你应该明白依赖注入和Unity能够帮助你解决什么类型的问题.作为介绍部分,这一章不会涉及太多关于Uni ...
- 【依赖注入】Unity和Autofac
全面理解ASP.NET Core依赖注入:https://www.cnblogs.com/jesse2013/p/di-in-aspnetcore.html MSDN:https://docs.mic ...
- IoC 依赖注入容器 Unity
原文:IoC 依赖注入容器 Unity IoC 是什么? 在软件工程领域,“控制反转(Inversion of Control,缩写为IoC)”是一种编程技术,表述在面向对象编程中,可描述为在编译时静 ...
- DI 依赖注入之unity的MVC版本使用Microsoft.Practices.Unity1.2与2.0版本对比
DI 依赖注入之unity的MVC版本使用Microsoft.Practices.Unity1.2与2.0版本对比 参考:https://www.cnblogs.com/xishuai/p/36702 ...
- DI 依赖注入之unity(mvc)
DI 依赖注入之unity(使用unity.mvc) 一.nuget下载安装: 使用Nuget安装Unity.MVC 安装完成后会在~/App_Start/目录下自动生成UnityMvcActivat ...
- 依赖注入之unity(winform方式)
依赖注入之unity(winform方式) 要讲unity就必须先了解DI和IOC及DIP,如下链接提供DI和IOC的基础:https://www.cnblogs.com/zlp520/p/12015 ...
- Unity文档阅读 第三章 依赖注入与Unity
Introduction 简介In previous chapters, you saw some of the reasons to use dependency injection and lea ...
- 关于微软企业库中依赖注入容器Unity两种生成对象的实现u
http://www.byywee.com/page/M0/S261/261037.html
随机推荐
- 【小白技术笔记】保存皮皮虾APP无水印视频到手机相册,只需要三步 [技术干货]
百万段友回皮皮虾,转身一变,都成了皮友.作为当年的资深段友,今天的皮友的我.看到好视频,经典视频,搞笑视频,就想保存一份到手机相册,然后皮皮虾啊皮皮虾,有个让人很讨厌的地方,保存视频的时候就有皮皮虾的 ...
- Linux系统加固
iptables 初始化 > iptables -F #清空所有的链 > iptables -X #清空所有自定义的链 关掉全部端口 > iptables -P INPUT DROP ...
- windows上使用mkdocs搭建静态博客
windows上使用mkdocs搭建静态博客 之前尝试过用HEXO搭建静态博客,最近发现有个叫mkdocs的开源项目也是搭建静态博客的好选择,而且它支持markdown格式,下面简要介绍一下mkdoc ...
- ssh 登录出现Are you sure you want to continue connecting (yes/no)?解决方法
ssh 登录出现Are you sure you want to continue connecting (yes/no)?解决方法 1,可以使用ssh -o 的参数进行设置例如: ssh -o St ...
- 滑动条QSlider
QSlider只提供整数范围 滑块接受Tab键的焦点,并同时提供了一个鼠标滚轮和键盘接口.键盘接口如下: Left/Right 移动水平滑块一个步长.Up/Down 移动垂直滑块一个步长.PageUp ...
- C. Trailing Loves (or L'oeufs?)
题目链接:http://codeforces.com/contest/1114/problem/C 题目大意:给你n和b,让你求n的阶乘,转换成b进制之后,有多少个后置零. 具体思路:首先看n和b,都 ...
- 列式数据库~clickhouse 数据同步使用
一 简介:进一步了解clickhouse二 数据操 1 单机建表 create TABLE aaa ( id UInt32, uid UInt32, amount Float64, ...
- Java获取资源路径——(八)
获取文件资源有两种方式: 第一种是: 获取Java项目根目录开始制定文件夹下指定文件,不用类加载器(目录开始要加/) // 获取工程路径 System.out.println(System.getP ...
- Linux文件系统1---概述
1.引言 本文所述关于文件管理的系列文章主要是对陈莉君老师所讲述的文件系统管理知识讲座的整理.Linux可以支持不同的文件系统,它源于unix文件系统,也是unix文件系统的一大特色. 本文主要先 ...
- eMMC基础技术6:eMMC data读写
1. 前言 data可以经data线从host发往device,也可以从device发往host 数据线以是1线(DATA0),4线(DATA0~DATA3),8线(DATA0~DATA7) 对每条数 ...