前言
什么是依赖注入?
依赖注入(Dependency Injection)和控制反转(Inversion of Control)是同一个概念。具体含义是:当某个角色(可能是一个C#实例,调用者)需要另一个角色(另一个C#实例,被调用者)的协助时,在 传统的程序设计过程中,通常由调用者来创建被调用者的实例。但在Spring里,创建被调用者的工作不再由调用者来完成,因此称为控制反转;创建被调用者 实例的工作通常由Spring容器来完成,然后注入调用者,因此也称为依赖注入。
autofac
相信大家对IOC和DI都耳熟能详,它们在项目里面带来的便利大家也都知道,微软新出的.NetCore也大量采用了这种手法。
如今.NetCore也是大势所趋了,基本上以.Net为技术主导的公司都在向.NetCore转型了,我也一直在想抽时间写几篇.NetCore的文章,可无奈最近的项目实在太赶,也没时间写什么文章。
但今天这篇文章不是专门讲.NetCore的。
算了,废话不多说,开始今天的主题吧。
本篇文章主要讲解Autofac的基本使用和高级用法,以及可能会踩到的一些坑。
2.基本概念
相信大家都知道IOC、DIP和DI是什么,用倒是网上抄点代码过来,项目就能跑起来了,但真要你讲出个花样,估计还是有点悬吧,这也是找工作面试时候经常被问起的。
控制反转
谁控制谁?
IoC/DI容器控制应用主程序。
控制什么?
IoC/DI容器控制对象本身的创建、实例化;控制对象之间的依赖关系。
何谓反转(对应于正向)?
因为现在应用程序不能主动去创建对象了,而是被动等待对象容器给它注入它所需要的资源,所以称之为反转。
哪些方面反转了?
1.创建对象
2.程序获取资源的方式反了
为何需要反转?
1.引入IoC/DI容器过后,体系更为松散,而且管理和维护以及项目升级更有序;
2.类之间真正实现了解耦
依赖
什么是依赖(按名称理解、按动词理解)?
依赖(按名称理解):依赖关系;依赖(按动词理解):依赖的动作
谁依赖于谁?
应用程序依赖于IoC/DI容器
为什么需要依赖?
因为发生了反转,应用程序依赖的资源都是IoC/DI容器里面
依赖什么东西?
应用程序依赖于IoC/DI容器为它注入所需要的资源。(比如:依赖关系)
注入
谁注入于谁?
IoC/DI容器注入于应用程序。
注入什么东西?
注入应用程序需要的对象,比如依赖关系。
为何要注入?
因为程序要正常运行需要访问这些对象。
IOC(控制反转Inversion of Control)
控制反转(Inversion of Control)就是使用对象容器反过来控制应用程序所需要的外部资源,这样的一种程序开发思想,调用者不再创建被调用者的实例,由IOC框架实现(容器创建)所以称为控制反转;创建对象和对象非托管资源的释放都由外部容器去完成,实现项目层与层之间的解耦的一种设计思想。
DI(依赖注入)和DIP(依赖倒置原则)
相信很多人还分不清楚DI和DIP这两个词,甚至认为它们就是同一个词。
依赖倒置原则(Dependency Inversion Principle)为我们提供了降低模块间耦合度的一种思路,而依赖注入(Dependency Injection)是一种具体的实施方法,容器创建好实例后再注入调用者称为依赖注入,就是应用程序依赖IOC容器来注入所需要的外部资源,这样一种程序的开发思想。
能做什么(What)?松散耦合对象,解耦项目架构层。
怎么做(How)?使用Autofac/Unity/Spring等框架类库,里面有实现好了的IoC/DI容器。
用在什么地方(Where)?凡是程序里面需要使用外部资源的情况,比如创建对象,都可以考虑使用IoC/DI容器。
DI和IOC是同一概念吗?
肯定不是同一概念啊,但它们两个描述的是同一件事件,从不同的角度来说:IOC是从对象容器的角度;DI是从应用程序的角度。
控制反转的描述:对象容器反过来控制应用程序,控制应用程序锁所需要的一些对象,比如DbContext。
依赖注入的描述:应用程序依赖对象容器,依赖它注入所需要的外部资源。
对IoC的理解:
a. 应用程序无需主动new对象,而是描述对象应该如何被创建(构造方法、属性、方法参数等)。
b. 应用程序不需要主动装配对象之间的依赖关系,而是描述需要哪个服务,IoC容器会帮你装配,被动接受装配。
c. 主动变被动,是一种让服务消费者不直接依赖于服务提供者的组件设计方式,是一种减少类与类之间依赖的设计原则。
3.Autofac/Unity简介
Autofac是.NET领域最为流行的IOC框架之一,传说是速度最快的一个,而今微软也很青睐的一个轻量高效的IOC框架,简单易上手且让人着迷;
Unity是微软官方出品的IOC框架,用法和Autofac大致差不多。
4.基本使用
通过nuget引入autofac;
准备几个实例对象:
public class Doge{ public void SayHello(){ Console.WriteLine("我是小狗,汪汪汪~"); } } public class Person{ public string Name { get; set; } public int Age { get; set; } } |
我们传统的做法当然是直接new啦,但现在有了IOC容器,怎么还能那样做呢!
接下来准备IOC容器,通过IOC容器来实例化对象
var builder = new ContainerBuilder();//准备容器 builder.RegisterType<Doge>();//注册对象 var container = builder.Build();//创建容器完毕 var dog = container.Resolve<Doge>();//通过IOC容器创建对象 dog.SayHello();
还可以直接实例注入
builder.RegisterInstance(new Doge());//实例注入
单例托管:
builder.RegisterInstance(Singleton.GetInstance()).ExternallyOwned();//将单例对象托管到IOC容器
还可以Lambda表达式注入:
builder.Register(c => new Person() { Name = "张三", Age = 20 });//Lambda表达式创建 Person p = container.Resolve<Person>();
还可以注入泛型类:
builder.RegisterGeneric(typeof(List<>)); List<string> list = container.Resolve<List<string>>();
你却说搞这么多过场,就为了创建一个对象?!咱不着急,接下来的才是重头戏
5.以接口方式注入
接着刚才的例子,添加个接口IAnimal,让Doge来实现它;
public interface IAnimal { void SayHello(); } public class Doge : IAnimal { public void SayHello() { Console.WriteLine("我是小狗,汪汪汪~"); } } public class Cat : IAnimal { public void SayHello() { Console.WriteLine("我是小猫,喵喵喵~"); } } public class Pig : IAnimal { public void SayHello() { Console.WriteLine("我是小猪,呼呼呼~"); } }
然后IOC注册对象的方式改变为:
var builder = new ContainerBuilder();//准备容器 builder.RegisterType<Doge>().As<IAnimal>();//映射对象 var container = builder.Build();//创建容器完毕 var dog = container.Resolve<IAnimal>();//通过IOC容器创建对象 dog.SayHello();
如果一个类型被多次注册,以最后注册的为准。通过使用PreserveExistingDefaults() 修饰符,可以指定某个注册为非默认值。
builder.RegisterType<Doge>().As<IAnimal>();//映射对象 builder.RegisterType<Cat>().As<IAnimal>().PreserveExistingDefaults();//指定Cat为非默认值 var dog = container.Resolve<IAnimal>();//通过IOC容器创建对象 dog.SayHello();
如果一个接口类被多个实例对象实现,可以进行命名,注入的时候使用名字进行区分
builder.RegisterType<Doge>().Named<IAnimal>("doge");//映射对象 builder.RegisterType<Pig>().Named<IAnimal>("pig");//映射对象 var dog = container.ResolveNamed<IAnimal>("pig");//通过IOC容器创建对象 dog.SayHello();
ResolveNamed()只是Resolve()的简单重载,指定名字的服务其实是指定键的服务的简单版本。有Named的方式很方便,但是只支持字符串,但有时候我们可能需要通过其他类型作键,比如枚举。
先声明一个枚举类:
public enum AnumalType { Doge, Pig, Cat }
然后将上面的代码改造成:
builder.RegisterType<Doge>().Keyed<IAnimal>(AnumalType.Doge);//映射对象 builder.RegisterType<Pig>().Keyed<IAnimal>(AnumalType.Pig);//映射对象 var dog = container.ResolveKeyed<IAnimal>(AnumalType.Cat);//通过IOC容器创建对象 dog.SayHello();
不过这种方式是不推荐使用的,因为autofac容器会被当作Service Locator使用,推荐的做法是通过索引类型来实现,Autofac.Features.Indexed.IIndex<K,V>是Autofac自动实现的一个关联类型。使用IIndex<K,V>作为参数的构造函数从基于键的服务中选择需要的实现:
var animal = container.Resolve<IIndex<AnumalType,IAnimal>>(); var cat = animal[AnumalType.Cat]; cat.SayHello();
IIndex中第一个泛型参数要跟注册时一致,在例子中是AnimalType枚举。其他两种注册方法没有这样的索引查找功能,这也是为什么设计者推荐Keyed注册的原因之一。
原:深入浅出依赖注入容器——Autofac - CharyGao - 博客园 (cnblogs.com)