DI 依赖注入

我们熟悉软件工程的都知道IoC(Inverse of Control 控制反转)的概念,而DI(依赖注入)是IoC模式的实现方式之一,例如Java中的Spring框架,整体就是基于DI来设计的;微软从.Net Core开始也提供了官方DI实现框架Microsoft.Extensions.DependencyInjection,DI也是ASP.Net Core的核心。

关于什么是DI以及它的优点这里就不赘述了,具体可以参考软件工程相关章节。

安装DI扩展

如果在控制台项目中单独使用DI,我们需要先使用NuGet进行安装。

Install-Package Microsoft.Extensions.DependencyInjection

DI使用例子

DI的使用方法其实在各种语言、框架中都是类似的,.Net Core中的DI设计精良,使用非常直观,我们这里直接看一个完整的例子。

首先我们有一个学生服务类,其中包含了一个根据主键查询学生对象的方法,根据面向对象的原则我们这里还将其划分为了接口和实现类。

public interface StudentService
{
    public Student GetStudentById(int id);
}

public class StudentServiceImpl : StudentService
{
    public Student GetStudentById(int id)
    {
        // 此处省略具体查询数据源等业务逻辑
    }
}

此外我们还有一个学生控制器,你可以理解为它是某个MVC架构代码中的C,其中包含了一个GetStudentDetailPage()方法,它通过查询前面编写的StudentService,向页面返回学生信息。

public class StudentController
{
    private StudentService studentService;

    public StudentController(StudentService studentService)
    {
        this.studentService = studentService;
    }

    public Student GetStudentDetailPage()
    {
        Student student = studentService.GetStudentById(1);
        return student;
    }
}

代码中比较特别的是我们没有new任何的StudentService对象,服务类的实例是由IoC容器通过构造方法依赖注入到StudentController对象的。

public class Program
{
    static void Main()
    {
        // IServiceCollection用于向IoC容器中注册服务
        IServiceCollection services = new ServiceCollection();
        // 注册一个瞬时态的服务
        services.AddTransient<StudentController>();
        // 注册一个单例的服务
        services.AddSingleton<StudentService, StudentServiceImpl>();

        // ServiceProvider用于获取服务实例
        using (ServiceProvider serviceProvider = services.BuildServiceProvider())
        {
            // 获取服务
            StudentController controller = serviceProvider.GetRequiredService<StudentController>();
            // 调用服务中的方法
            Student student = controller.GetStudentDetailPage();
            Console.WriteLine("{0} {1}", student.Id, student.Name);
        }
    }
}

Main方法中,我们首先创建了DI框架提供的IServiceCollection对象,它用于向IoC容器注册服务,这里我们将前面创建的StudentServiceStudentController注册进去。

随后我们获取了ServiceProvider对象,注意它实现了IDisposable接口,因此我们使用了using语句块。

最后,我们获取了StudentController并调用了其中的方法。获取服务时我们使用了GetRequiredService()方法,该方法通过泛型参数指定获取的服务类型,注意该方法永远不会返回null,如果指定的服务从未注册过,此处将抛出异常。

依赖注入对象的生命周期

上面DI中我们注册服务时,使用过类似services.AddSingleton<StudentService, StudentServiceImpl>()的写法,这里我们其实指定的是对象的生命周期。可用的生命周期有三种:

生命周期 说明
Transient 瞬态,每次获取服务时都会创建新的对象
Singleton 单例,全局只会创建一个对象,每次获取的对象都是同一个
Scoped 作用域内单例,例如一次HTTP请求,在处理本次请求的过程中单例

有关Scoped这里还要单独说明一下,我们看如下例子代码。

IServiceCollection services = new ServiceCollection();
services.AddScoped<StudentService, StudentServiceImpl>();
using (ServiceProvider serviceProvider = services.BuildServiceProvider())
{
    // 创建作用域
    using (IServiceScope serviceScope = serviceProvider.CreateScope())
    {
        // 获取ServiceProvider
        IServiceProvider scopedServiceProvider = serviceScope.ServiceProvider;
        // 获取作用域内服务
        StudentService studentService = scopedServiceProvider.GetRequiredService<StudentService>();
        // ...
    }
}

在代码中,我们可以使用CreateScope()方法创建作用域,该方法返回IServiceScope实例,在同一个作用域实例上获取的服务都被认为是同一个作用域内,此时Scoped生命周期就会生效,获取同一个服务对象。

而在ASP.Net Core中,IServiceScope实例会由框架维护,每一次请求会生成对应的作用域。

如果熟悉Spring应该不难理解这3种生命周期,这里就不多介绍了。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap