依赖注入 autofac

2886人阅读 2021/5/31 11:43 总访问:652465 评论:0 收藏:0 手机
分类: .net core

前言

什么是依赖注入?
依赖注入(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)


评价
脚踏实地,一步一个脚印
排名
6
文章
6
粉丝
16
评论
8
{{item.articleTitle}}
{{item.blogName}} : {{item.content}}
ICP备案 :渝ICP备18016597号-1
网站信息:2018-2024TNBLOG.NET
技术交流:群号656732739
联系我们:contact@tnblog.net
欢迎加群交流技术