关于 Microsoft Extension: DependencyInjection 的介绍已经很多,但是多数偏重于实现原理和一些特定的实现场景。作为 dotnet core 的核心基石,这里准备全面介绍它的概念、原理和使用。

这里首先介绍概念部分。

1. 概念

该项目在 GitHub 的地址:https://github.com/aspnet/Extensions/tree/master/src/DependencyInjection

Microsoft.Extensions.DependencyInjection 是微软对依赖倒置原则的实现。作为 ASP.NET Core 的基石,DependencyInjection 贯穿了整个项目的方方面面,掌握它的使用方式和原理,不仅对理解 ASP.NET Core 有重要意义,也有助于将它运用到其它项目的开发中,帮助提供项目开发的效率和质量。

1.1 问题的场景

在软件开发中,项目通常有多个不同的模块组成,模块之间存在依赖关系。例如,我们考虑一个简化的场景,我们有 3 个关于用户的类:

  1. AccountController,提供用户交互界面

  2. UserService,提供用户管理的业务逻辑

  3. UserRepository,提供用户管理的数据访问

AccountController 内部需要使用 UserService 的实例 来管理用户,而 UserService 内部则需要基于 UserRepository 来提供数据访问。我们称它们之间存在依赖关系。或者表达为,AccountController 依赖于 UserService ,而 UserService 依赖于 UserRepository 。而依赖注入就是用来帮助我们实现依赖管理的有力工具。

1.2 依赖倒置原则 DIP

依赖倒置原则是广为人知的设计原则之一,该原则是实现软件项目中模块的解耦的理论基石。

原则的定义如下:

翻译过来为:

  • 高层模块不应该依赖低层模块,两者都应该依赖抽象

  • 抽象不应该依赖细节

  • 细节应该依赖抽象

在没有实现依赖倒置原则的时候,我们通过在 AccountController 类中自己通过 new 关键字来创建其依赖的 UserService 对象实例,

public class AccountController {
​
    private readonly UserService _userService;
    public AccountController() {
        this._userService = new UserService();
    }
}

这导致了两个类之间的紧耦合,AccountControllerUserService 被绑定到一起, 在每次创建 AccountController 的时候,一定会创建一个 UserService 的对象实例,而如果我们需要测试 AccountController 的时候,也就不得不考虑 UserService,这样一级一级的依赖下来,UserService 又会依赖 UserRepository,就会发现项目中的类都被绑定在一起, 紧密耦合,难以分拆。

基于依赖倒置的原则,通常会考虑通过接口进行隔离。例如,我们可能会定义一个用户服务的接口:

public interface IUserService
{
}

而用户服务则会实现该接口

public class UserService : IUserService {
}

AccountController 类中,则改变成了基于接口来使用 UserService

public class AccountController {
​
    private readonly IUserService _userService;
    public AccountController() {
        this._userService = new UserService();
    }
}

虽然在 HomeController 内部,我们可以基于接口编程了,但是这样的作法并没有解决自己通过 new 来获取 UserService 对象实例的问题。

1.3 控制反转 IoC

IoC是一种著名的实现 DIP 的设计模式。

它的核心思想是:在需要对象实例的时候,不要总考虑自己通过 new 来创建对象,放下依赖对象的创建过程,而是把创建对象的工作交给别人来负责,这个别人我们通常称为 容器 (Container) 或者 服务提供者 (ServiceProvider), 我们后面使用这个 ServiceProvider 来指代它,

在需要对象实例的时候,从这个 ServiceProvider 中获取。

下面是一个广泛使用的示意图。拿总是要拿的,但是从 自己穿上 变成了 给你穿上

依赖注入在 dotnet core 中实现与使用:1 基本概念-LMLPHP

在控制反转中,引入了一个 ServiceProvider 来帮助我们获得对象实例

1.4 依赖注入 DI (DependencyInjection)

DI 是 IoC 模式的一种实现。

《Expert one on one J2EE Development without EJB》第 6 章

Martin Fowler 的原文:

大意是:

DI 的实现有多种,我们这里介绍的是微软官方在 Microsoft Extension 中内置提供的 DependencyInjection。它是 IoC 中一种实现,ASP.NET Core 的整个核心基于它来实现。同时,我们也可以在其它项目中使用,以实现对依赖倒置原则的支持。

2. DependencyInjection 中的基本概念

2.1 服务描述集合 ServiceCollection

在微软的 DI 实现中,所有的服务需要首先注册到一个公共的服务描述集合中,该集合对于整个 DI 来说,只需要一个,服务只需要在此集合中注册一次,即可在以后通过 DI 提供给使用者。

该集合的接口定义为 IServiceCollection,可以看出来,它其实就是一个用来保存服务注册的集合。

public interface IServiceCollection : IList<ServiceDescriptor>, ICollection<ServiceDescriptor>, IEnumerable<ServiceDescriptor>, IEnumerable
{
}

系统默认已经实现了一个对 IServiceCollection 的实现,名为 ServiceCollection。在 ASP.NET Core 中,内部会创建该对象的实例,我们也可以在其它项目中,自己来创建它,很简单,直接 new 出来就可以使用了。

IServiceCollection services = new ServiceCollection ();

 在 ASP.NET Core MVC 中,你可能已经见过它了,不需要你来创建,系统已经帮你做了。

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IRepository, MemoryRepository>();
    services.AddMvc();
}

2.2 服务 Service

在 DI 语境中,服务特指通过 DI 容器管理的对象实例。这个服务并不一定被称为 **Service,而是可以是任何由 DI 所管理的对象,只是在 DI 这个语境下,我们将其统称为服务。

服务是我们自己定义的,例如前面提到的 AccountControllerUserService 等等。

我们通过 DI 来获得服务对象实例,管理服务对象的生命周期,对于存在复杂依赖关系的对象, DI 还负责管理这些实例之间的依赖关系。

服务必须首先注册在 DI 中才能使用,但是,注册前需要首先考虑和决定服务的生命周期。

2.3 服务的生命周期

服务对象实例有着不同类型的生命周期。有些对象的生命周期与应用程序相同,在应用程序启动时创建,在应用程序退出时才需要释放。例如我们的数据访问对象实例。有些对象仅仅在当前方法中使用,在方法调用结束之后就应该销毁。服务的生命周期管理用来管理这些需求。

DI 支持三种类型的生命周期:

  1. Singleton,单例,在当前应用程序环境下只有一个实例。例如数据访问服务对象实例。

  2. Scoped,限定范围,一旦退出此范围,在此范围内的服务对象都需要销毁。例如 Web 开发中的请求对象实例。

  3. Transient,瞬态,一次性使用,每次从 DI 中获取,都返回一个新的实例。

Microsoft.Extensions.DependencyInjection.ServiceLifetime

public enum ServiceLifetime
{
    Singleton,
    Scoped,
    Transient
}

服务的生命周期在注册服务的时候确定。在使用的时候,直接获取实例,不再指定服务的生命周期。微软提供了多种扩展方法来便于在注册服务时指定服务的生命周期。例如下面是通过泛型方式来指定单例模式的生命周期。

// 基于接口的注册
services.AddSingleton<IUserService, UserService>();

2.4 服务提供者 ServiceProvider

在需要使用服务对象实例的时候,不是从注册服务的集合中获取,而是需要通过服务提供者来获取,这个服务提供者显然需要来自注册服务的集合。服务提供者的接口定义为 IServiceProvider,它是 .net 的基础定义之一,不是在该 DI 框架中定义的。

public interface IServiceProvider
{
    object GetService(Type serviceType);
}

DI 中的 ServiceCollectionContainerBuilderExtensions 扩展了 IServiceCollection,提供了获得这个服务提供者 ServiceProvider 的支持。

public static ServiceProvider BuildServiceProvider(this IServiceCollection services)
{
    return BuildServiceProvider(services, ServiceProviderOptions.Default);
}

所以,我们通常使用该方法来获取并使用它。

// 创建注册服务的容器
IServiceCollection services = new ServiceCollection ();
// 注册服务,这里指定了单例
services.AddSingleton<IUserService, UserService>();
// 通过容器获得服务提供者
IServiceProvider provider = services.BuildServiceProvider ();

2.5 获取服务对象实例

通过服务提供者来手动获取服务对象实例。通过注册的服务类型,直接调用 GetService 方法即可。

例如,前面我们注册了服务类型 IUserService 的实现类型是 UserService ,那么,可以通过此类型来获取实际实现该接口的对象实例。

// 创建注册服务的容器
IServiceCollection services = new ServiceCollection ();
// 注册服务,这里指定了单例
services.AddSingleton<IUserService, UserService>();
// 通过容器获得服务提供者
IServiceProvider provider = services.BuildServiceProvider ();
// 通过接口获取服务对象实例
IUserService instance = provider.GetService<IUserService> ();

看起来,更加复杂了。在实际使用中,我们很少使用这样的方式来使用 DI,后面我们再深入讨论具体的使用过程。

 

2.6 构造函数注入

DI 支持构造函数注入。

定义 IUserRepository 接口,并实现 UserRepository

public interface IUserRepository {
}
​
public class UserReposotory: IUserRepository {
}

UserService 通过构造函数依赖 IUserRepository

public class UserService : IUserService {
    private readonly IUserRepository _userRepository;
    public UserService(IUserRepository userRepository) {
        this._userRepository = userRepository;
    }
}

在通过 DI 获得 UserService 实例的时候,DI 帮助实例化其所依赖的 UserRepository 实例。

UserService 的定义中,我们只需要通过构造函数声明所需的依赖即可。

IServiceCollection services = new ServiceCollection ();
​
// 基于接口的注册
services.AddSingleton<IUserService, UserService>();
services.AddSingleton<IUserRepository, UserReposotory>();
​
IServiceProvider provider = services.BuildServiceProvider ();
IUserService instance = provider.GetService<IUserService> ();

 

08-18 11:23