关于.NET中的控制反转及AutoFac的简单说明
shanzm 人气:0
[TOC]
## 1.控制反转 ### 1.1 什么是依赖? **依赖**是面向对象中用来描述类与类之间一种关系的概念。两个相对独立的对象,当一个对象负责构造另一个对象的实例,或者依赖另一个对象的服务,这样的两个对象之间主要体现为**依赖关系** ### 1.2 什么是控制反转? 说反转则要先说“正转”,传统中,在程序中使用new关键字配合构造函数去创建一个对象,这就是程序主动的创建其所依赖对象,这就是“**正转**”。 调用者不自己创建被调用者对象,而交由第三方(容器)进行创建被调用者对象,这个过程称为**控制反转**(inversion of control,**IOC**)。 为什么要控制反转?控制反转是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度,便于扩展和后期维护。 ### 1.3 什么是依赖注入? 实现控制反转的主要方式是**依赖注入**。(当然不止依赖注入这一种方法,还有依赖查找(Dependency Lookup,DL)。二者区别可参考:[维基:控制反转](https://zh.wikipedia.org/wiki/%E6%8E%A7%E5%88%B6%E5%8F%8D%E8%BD%AC)) 依赖注入具体是指:调用类 不主动创建依赖对象,而是使用容器来帮忙创建及注入依赖对象,这个过程就称为**依赖注入**(Dependency Injection,**DI**) 具体的说:Class A(调用类)中用到 Class B 类型的对象(依赖对象),通常情况下,我们在 Class A 中使用new关键字配合构造函数创建一个 Class B 的对象 但是,采用依赖注入技术之后, Class A只需要定义一个Class B类型的属性,不需要直接new来获得这个对象,而是通过IOC容器 将Class B类型的对象在外部new出来并**注入**到Class A里的引用中,从而实现Class A和Class B**解耦**。 ### 1.4 简单总结 明白了上述几个概念,那么就可以理解这样一句话“**模块间的依赖关系从程序内部提到外部来实例化管理称之为控制反转,这个实例化的过程就叫做依赖注入**。”
## 2.控制反转容器 ### 2.1 IOC容器说明 在说到控制反转时提到“使用IOC容器在 调用类 外部创建 依赖对象 并注入到 调用类”,其中IOC容器是什么? **IOC容器**就是具有依赖注入功能的容器,IOC容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。从而,应用程序无需直接在代码中new相关的对象,应用程序由IOC容器进行组装。 简而言之,IOC容器主要就两个作用:1、绑定服务与实例之间的关系。2、对实例进行创建和销毁 在.NET程序中IOC容器有许多,如Unity、AutoFac、Spring.net等等。 据说AutoFac是关于.NET的最流行的IOC容器,本文简单的介绍一下AutoFac的使用方式。 ### 2.2 使用AutoFac的简介示例 使用AutoFac容器一般是**面向接口编程**。所以这里使用一个分层项目来演示AutoFac的使用,即**使用AutoFac创建接口实现类的对象** ([完整Demo下载](https://github.com/shanzm/ASP.NET-MVC/tree/master/009IOC-AutoFac)) **①新建一个类库项目TestIBLL** 用于定义一些接口 ```cs public interface IUserBll { //检查登录信息 bool Login(string userName, string pwd); //添加新用户 void AddNew(string userName, string pwd); } ``` **②新建一个类库项目TestBLLImpl** 添加对TestIBLL项目的引用,用于定义接口的实现类 ```cs public class UserBll : IUserBll { //实现接口 public void AddNew(string userName, string pwd) { Console.WriteLine($"新增了一个用户:{userName}");//为了演示,简单模拟 } public bool Login(string userName, string pwd) { Console.WriteLine($"登录用户是:{userName}");//为了演示,简单模拟 return true; } } ``` **【说明】**: 这里定义了`UserBll`类,实现了`IUserBll`接口, 按照AutoFac中的术语,有如下称呼: * `UserBll`类称为**组件(Component)** * `IUserBll`接口称为**服务(Service)** **③新建一个控制台项目TestUI** 用于模拟UI层 添加对TestIBLL和TestBLLImpl项目的引用 安装AutoFac:`PM>Install-Package Autofac` ```cs static void Main(string[] args) { //创建容器构造者 ContainerBuilder builder = new ContainerBuilder(); //注册组件UserBll类,并把服务IUserBll接口暴露给该组件 //把服务(IUserBll)暴露给组件(UserBll) builder.RegisterType().As();
//创建容器
IContainer container = builder.Build();
//使用容器解析服务,创建实例(不推荐,见下面说明):IUserBll userBll = container.Resolve();
//使用生命周期解析服务,创建实例
using (ILifetimeScope scope = container.BeginLifetimeScope())
{
IUserBll userBll = scope.Resolve();
userBll.Login("shanzm", "123456");
}
}
```
**【说明】**:
* 其中关于AutoFac的术语:
* **容器(Container)** :用于管理程序中所有的组件的结构(简单的说就是管理所有接口的实现类)
* **生命周期(Lifetime)**: 实例 的从 创建 到 释放 的持续时间
* **注册(Registration)**: 添加和配置 组件 到 容器 的行为
* **作用域(Scope)**: 一个特定的 上下文 , 在其中 组件 的 实例 将会被其他 组件 依据它们的 服务 所共享
* **解析服务**: 相当于给服务实例化对象
* **注册**:容器中添加一个实现了服务(接口)的组件(实现类)的操作
* 通过创建 `ContainerBuilder` 来注册**组件**
`ContainerBuilder`有一系列的注册方法,这里使用的是通过类型注册:`RegisterType()`;
任何通过 `RegisterType()` 注册的组件必须是个具体的类型, 后面解析服务的时候,Autofac就会创建了一个你注册对象的实例
* 每个**组件**暴露一个或多个**服务**(简单地说就是一个类(组件)可能实现一个或多个接口(服务)) ,他们使用 ContainerBuilder 上的 As() 方法连接起来.
* 解析服务,即创建一个服务的提供对象(简单的说就是为接口创建一个注册的实现类)
不推荐使用使用容器直接解析服务:`IUserBll userBll = container.Resolve();`
我看到一些文章和视频中使用容器去解析服务,但是我看了官方文档,其中是不推荐这么使用的,因为可能造成内存的泄露。
推荐你总是从生命周期中解析服务(即我的示例中的方式), 以确保服务实例被妥善地释放和垃圾回收
AutoFac文档中:“**永远从一个生命周期作用域而不是从根容器中解析服务!**”
后续为了示例代码的简洁,我还是直接使用容器解析服务,周知!
## 3 使用AutoFac的一些细节 下面演示一下AutoFac最基本的一些API,具体细节和其他的功能,可以参考AutoFac文档,其文档非常详细且有中文版本([AutoFac文档](https://autofaccn.readthedocs.io/zh/latest/getting-started/index.html)) ### 3.1 准备工作 接着上面的示例,在类库项目TestIBLL中添加以下接口: 创建IAnimalBll.cs ```cs //IAnimalBll接口 public interface IAnimalBll { void Cry();//动物都有叫的动作 } ``` 创建IMasterBll.cs ```cs //IMasterBll接口 public interface IMasterBll { void Walk(); } ``` 在类库项目TestBLLImpl中分别实现上述接口 创建DogBll.cs ```cs //DogBll类实现IAnimalBll接口 public class DogBll : IAnimalBll { public void Cry() { Console.WriteLine("汪汪汪!"); } } ``` 创建CatBll.cs ```cs //CatBll类实现IAnimalBll接口 public class CatBll : IAnimalBll { public void Cry() { Console.WriteLine("喵喵喵!"); } } ``` 创建MasterBll.cs ```cs //MasterBll类,实现了IMasterBll接口和IUserBll接口 public class MasterBll : IMasterBll,IUserBll { //注意这里,MasterBll是接口的实现类,这个类还有一个接口类型的属性 public IAnimalBll dogBll { get; set; } public void AddNew(string userName, string pwd) { Console.WriteLine($"新增了一个Master用户:{userName}"); } public bool Login(string userName, string pwd) { Console.WriteLine($"登录用户是Master:{userName}"); return true; } public void Walk() { Console.WriteLine("带着狗散步!"); dogBll.Cry();//在调用中,使用.PropertiesAutowired()方法给dogBll注册其实现类 } } ```
### 3.2 注册整个程序集中的所有实现类 项目中,其实我们可以使用`.RegisterAssemblyTypes()`,一次性把程序集(类库项目)中的的所有接口实现类都注册给相应的接口 ```cs static void Main(string[] args) { ContainerBuilder builder = new ContainerBuilder();//创建容器构造者 Assembly asm = Assembly.Load(" TestBLLImpl");//获取指定的程序集 builder.RegisterAssemblyTypes(asm).AsImplementedInterfaces();//注册指定程序集中的所有接口实现类 IContainer container = builder.Build();//创建容器 IUserBll userBll = container.Resolve();//解析服务,创建实例
userBll.Login("shanzm", "123456");//使用服务提供者
}
```
**【说明】**:
* 关于使用`.RegisterAssemblyTypes()`对指定的程序集扫描注册,可以使用`Where()`和`Except()`对类型进行过滤
具体的使用方式可以,查看[文档:程序集扫描](https://autofaccn.readthedocs.io/zh/latest/register/scanning.html)
* `.AsImplementedInterfaces()`:将程序集中的实现类注册给它所实现的所有接口。
### 3.3 注入接口实现类中的接口类型的属性 对实现类中的属性也是可以使用AutoFac注入的, 对于接口的实现类中若是有某个接口类型的属性,我们可以使用`.PropertiesAutowired()`在注册该实现类的同时,把该属性同时注册,即实现属性的自动装配,即属性注入 ```cs static void Mian(string[] args) { ContainerBuilder builder = new ContainerBuilder(); Assembly asm = Assembly.Load("TestBLLImpl"); //在这里通过.PropertiesAutowired(),给接口实现类中的接口属性也注册一个该类型的接口的实现类,即实现属性自装配 builder.RegisterAssemblyTypes(asm).AsImplementedInterfaces().PropertiesAutowired(); builder.RegisterType().As();
IContainer container = builder.Build();
IMasterBll masterBll = container.Resolve();
masterBll.Walk();//打印:带着狗散步!汪汪汪!
}
```
* 注意这里的一个细节,在`MasterBll`类中有一个`IAnimalBll`类型的属性`dogBll`,我们使用`PropertiesAutowired()`方法实现属性的自动装配,
但是呀,IAnimalBll接口在`TestBLLImpl程序集`中有两个实现类,而自动装配按顺序给dogBll属性注册的是CatBll类型的对象
而我的期望是注册DogBll类型的对象给IAnimalBll类型的属性
所以这里还要显示的把DogBll类注册给IAnimalBll接口
* 如果你预先知道属性的名字和值,你可以使用`WithProperty("PropertyName", propertyValue)`
所以示例中可以这样写:
`builder.RegisterType().As().WithProperty("dogBll",new DogBll());`
### 3.4 关于一个接口有多个不同的实现类 为已给接口注册实现类的时候,可能该接口有多个实现类,则我们可以为每一个注册提供已给命名 `builder.RegisterType
shanzm-2020年3月16日 02:17:35
## 1.控制反转 ### 1.1 什么是依赖? **依赖**是面向对象中用来描述类与类之间一种关系的概念。两个相对独立的对象,当一个对象负责构造另一个对象的实例,或者依赖另一个对象的服务,这样的两个对象之间主要体现为**依赖关系** ### 1.2 什么是控制反转? 说反转则要先说“正转”,传统中,在程序中使用new关键字配合构造函数去创建一个对象,这就是程序主动的创建其所依赖对象,这就是“**正转**”。 调用者不自己创建被调用者对象,而交由第三方(容器)进行创建被调用者对象,这个过程称为**控制反转**(inversion of control,**IOC**)。 为什么要控制反转?控制反转是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度,便于扩展和后期维护。 ### 1.3 什么是依赖注入? 实现控制反转的主要方式是**依赖注入**。(当然不止依赖注入这一种方法,还有依赖查找(Dependency Lookup,DL)。二者区别可参考:[维基:控制反转](https://zh.wikipedia.org/wiki/%E6%8E%A7%E5%88%B6%E5%8F%8D%E8%BD%AC)) 依赖注入具体是指:调用类 不主动创建依赖对象,而是使用容器来帮忙创建及注入依赖对象,这个过程就称为**依赖注入**(Dependency Injection,**DI**) 具体的说:Class A(调用类)中用到 Class B 类型的对象(依赖对象),通常情况下,我们在 Class A 中使用new关键字配合构造函数创建一个 Class B 的对象 但是,采用依赖注入技术之后, Class A只需要定义一个Class B类型的属性,不需要直接new来获得这个对象,而是通过IOC容器 将Class B类型的对象在外部new出来并**注入**到Class A里的引用中,从而实现Class A和Class B**解耦**。 ### 1.4 简单总结 明白了上述几个概念,那么就可以理解这样一句话“**模块间的依赖关系从程序内部提到外部来实例化管理称之为控制反转,这个实例化的过程就叫做依赖注入**。”
## 2.控制反转容器 ### 2.1 IOC容器说明 在说到控制反转时提到“使用IOC容器在 调用类 外部创建 依赖对象 并注入到 调用类”,其中IOC容器是什么? **IOC容器**就是具有依赖注入功能的容器,IOC容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。从而,应用程序无需直接在代码中new相关的对象,应用程序由IOC容器进行组装。 简而言之,IOC容器主要就两个作用:1、绑定服务与实例之间的关系。2、对实例进行创建和销毁 在.NET程序中IOC容器有许多,如Unity、AutoFac、Spring.net等等。 据说AutoFac是关于.NET的最流行的IOC容器,本文简单的介绍一下AutoFac的使用方式。 ### 2.2 使用AutoFac的简介示例 使用AutoFac容器一般是**面向接口编程**。所以这里使用一个分层项目来演示AutoFac的使用,即**使用AutoFac创建接口实现类的对象** ([完整Demo下载](https://github.com/shanzm/ASP.NET-MVC/tree/master/009IOC-AutoFac)) **①新建一个类库项目TestIBLL** 用于定义一些接口 ```cs public interface IUserBll { //检查登录信息 bool Login(string userName, string pwd); //添加新用户 void AddNew(string userName, string pwd); } ``` **②新建一个类库项目TestBLLImpl** 添加对TestIBLL项目的引用,用于定义接口的实现类 ```cs public class UserBll : IUserBll { //实现接口 public void AddNew(string userName, string pwd) { Console.WriteLine($"新增了一个用户:{userName}");//为了演示,简单模拟 } public bool Login(string userName, string pwd) { Console.WriteLine($"登录用户是:{userName}");//为了演示,简单模拟 return true; } } ``` **【说明】**: 这里定义了`UserBll`类,实现了`IUserBll`接口, 按照AutoFac中的术语,有如下称呼: * `UserBll`类称为**组件(Component)** * `IUserBll`接口称为**服务(Service)** **③新建一个控制台项目TestUI** 用于模拟UI层 添加对TestIBLL和TestBLLImpl项目的引用 安装AutoFac:`PM>Install-Package Autofac` ```cs static void Main(string[] args) { //创建容器构造者 ContainerBuilder builder = new ContainerBuilder(); //注册组件UserBll类,并把服务IUserBll接口暴露给该组件 //把服务(IUserBll)暴露给组件(UserBll) builder.RegisterType
## 3 使用AutoFac的一些细节 下面演示一下AutoFac最基本的一些API,具体细节和其他的功能,可以参考AutoFac文档,其文档非常详细且有中文版本([AutoFac文档](https://autofaccn.readthedocs.io/zh/latest/getting-started/index.html)) ### 3.1 准备工作 接着上面的示例,在类库项目TestIBLL中添加以下接口: 创建IAnimalBll.cs ```cs //IAnimalBll接口 public interface IAnimalBll { void Cry();//动物都有叫的动作 } ``` 创建IMasterBll.cs ```cs //IMasterBll接口 public interface IMasterBll { void Walk(); } ``` 在类库项目TestBLLImpl中分别实现上述接口 创建DogBll.cs ```cs //DogBll类实现IAnimalBll接口 public class DogBll : IAnimalBll { public void Cry() { Console.WriteLine("汪汪汪!"); } } ``` 创建CatBll.cs ```cs //CatBll类实现IAnimalBll接口 public class CatBll : IAnimalBll { public void Cry() { Console.WriteLine("喵喵喵!"); } } ``` 创建MasterBll.cs ```cs //MasterBll类,实现了IMasterBll接口和IUserBll接口 public class MasterBll : IMasterBll,IUserBll { //注意这里,MasterBll是接口的实现类,这个类还有一个接口类型的属性 public IAnimalBll dogBll { get; set; } public void AddNew(string userName, string pwd) { Console.WriteLine($"新增了一个Master用户:{userName}"); } public bool Login(string userName, string pwd) { Console.WriteLine($"登录用户是Master:{userName}"); return true; } public void Walk() { Console.WriteLine("带着狗散步!"); dogBll.Cry();//在调用中,使用.PropertiesAutowired()方法给dogBll注册其实现类 } } ```
### 3.2 注册整个程序集中的所有实现类 项目中,其实我们可以使用`.RegisterAssemblyTypes()`,一次性把程序集(类库项目)中的的所有接口实现类都注册给相应的接口 ```cs static void Main(string[] args) { ContainerBuilder builder = new ContainerBuilder();//创建容器构造者 Assembly asm = Assembly.Load(" TestBLLImpl");//获取指定的程序集 builder.RegisterAssemblyTypes(asm).AsImplementedInterfaces();//注册指定程序集中的所有接口实现类 IContainer container = builder.Build();//创建容器 IUserBll userBll = container.Resolve
### 3.3 注入接口实现类中的接口类型的属性 对实现类中的属性也是可以使用AutoFac注入的, 对于接口的实现类中若是有某个接口类型的属性,我们可以使用`.PropertiesAutowired()`在注册该实现类的同时,把该属性同时注册,即实现属性的自动装配,即属性注入 ```cs static void Mian(string[] args) { ContainerBuilder builder = new ContainerBuilder(); Assembly asm = Assembly.Load("TestBLLImpl"); //在这里通过.PropertiesAutowired(),给接口实现类中的接口属性也注册一个该类型的接口的实现类,即实现属性自装配 builder.RegisterAssemblyTypes(asm).AsImplementedInterfaces().PropertiesAutowired(); builder.RegisterType
### 3.4 关于一个接口有多个不同的实现类 为已给接口注册实现类的时候,可能该接口有多个实现类,则我们可以为每一个注册提供已给命名 `builder.RegisterType
加载全部内容