惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
Security @ Cisco Blogs
H
Hacker News: Front Page
P
Privacy International News Feed
N
News and Events Feed by Topic
T
Threatpost
Simon Willison's Weblog
Simon Willison's Weblog
S
Schneier on Security
K
Kaspersky official blog
S
Secure Thoughts
V2EX - 技术
V2EX - 技术
Security Latest
Security Latest
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
www.infosecurity-magazine.com
www.infosecurity-magazine.com
C
CERT Recently Published Vulnerability Notes
L
Lohrmann on Cybersecurity
Jina AI
Jina AI
P
Proofpoint News Feed
AI
AI
雷峰网
雷峰网
T
Tailwind CSS Blog
Engineering at Meta
Engineering at Meta
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Recent Commits to openclaw:main
Recent Commits to openclaw:main
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
博客园 - 叶小钗
Webroot Blog
Webroot Blog
Apple Machine Learning Research
Apple Machine Learning Research
SecWiki News
SecWiki News
罗磊的独立博客
N
Netflix TechBlog - Medium
Martin Fowler
Martin Fowler
Google DeepMind News
Google DeepMind News
Cyberwarzone
Cyberwarzone
MongoDB | Blog
MongoDB | Blog
博客园 - Franky
Schneier on Security
Schneier on Security
The GitHub Blog
The GitHub Blog
S
Security Affairs
Blog — PlanetScale
Blog — PlanetScale
Last Week in AI
Last Week in AI
P
Proofpoint News Feed
月光博客
月光博客
D
Docker
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
S
Securelist
W
WeLiveSecurity
T
Troy Hunt's Blog
A
Arctic Wolf
博客园 - 司徒正美

博客园 - oec2003

读《有效需求分析》 重读《打造 Facebook》 dotNet Core 3.1 使用 Aspose (部署 Docker) 为什么这么忙,还依然做不好事情? dotNET Core:编码规范 程序员是终身学习的职业,应该怎么学习? 一款用了就不想走的工具 dotNET:怎样处理程序中的异常(实战篇)? dotNET:怎样处理程序中的异常(理论篇)? Nginx:rewrite 的几个技巧 如何激发团队潜能? dotNET Core 3.X 使用 Autofac 来增强依赖注入 读《鞋狗》 Jenkins 中以构建 Tag 来实现版本管理 dotNET Core 3.X 使用 Jwt 实现接口认证 dotNET Core 3.X 请求处理管道和中间件 dotNET Core 3.X 使用 Web API 程序员还有35岁的坎吗? 远程办公也可以高效
dotNET Core 3.X 依赖注入
oec2003 · 2020-06-01 · via 博客园 - oec2003

如果说在之前的 dotNET 版本中,依赖注入还是个比较新鲜的东西,那么在 dotNET Core 中已经是随处可见了,可以说整个 dotNET Core 的框架是构建在依赖注入框架之上。本文讲解下对 dotNET Core 中依赖注入的理解。

什么是依赖

在面向对象的语言中,所说的依赖通常指类与类之间的关系,比如有个用户类 User 和日志类 Log , 在 User 类中需要记录日志,就需要引入日志类 Log,这样 User 类就对 Log 类产生了依赖,代码如下:

public class User
{
    private Log _log=new Log();
    public string GetUserName()
    {
        _log.Write("获取用户名称");
        return "oec2003";
    }
}
public class Log
{
    public void Write(string message)
    {
        Console.WriteLine(message);
    }
}

或者直接在类的方法中对其他类进行了依赖,如下:

public class Dept
{
    public string GetDeptNameByUserId(string userId)
    {
        return "开发部";
    }
}
public string GetUserFullName(string userId)
{
    Dept dept=new Dept();
    return $"oec2003({dept.GetDeptNameByUserId(userId)})";
}

这样的类与类之间的直接依赖有如下几个问题:

  • 要换一种 Log 的实现方式,所有引用的地方都要进行修改;
  • 如果整个项目中到处都是这种类与类之间的强关联,代码维护会变得非常困难;
  • 对单元测试不友好。

要解决上面的问题,需要将依赖的类抽象成接口,不直接依赖具体的实现类类,而是依赖接口,这就是面向对象的六大原则中的依赖倒置原则:高层模块不应该依赖于底层模块,二者都应该依赖于抽象;抽象不应该依赖于实现细节,实现细节应该依赖于抽象。

User 类调整后的代码如下:

public interface ILog
{
    void Write(string message);
}
public class Log:ILog
{
    public void Write(string message)
    {
        Console.WriteLine(message);
    }
}
public class User
{
    private ILog _log;

    public User(ILog log)
    {
        _log = log;
    }
    public string GetUserName()
    {
        _log.Write("获取用户名称");
        return "oec2003";
    }
}
  • 创建了 ILog 接口;
  • User 类调整为对 ILog 的依赖;
  • 在 User 中类添加构造函数,在构造函数中传入接口 ILog 的实例。

那么构造函数中的实例什么时候创建呢?这时就需要用到注入了。

什么是注入?

在上面示例中,注入就是在某个时机,将 ILog 的实例传递到 User 类的构造函数中,而 User 类中根本就不关心 ILog 的实现。在 dotNET Core 中提供了一个内置的服务容器 IServiceProvider,然后在 Startup 类的 ConfigureServices 方法中进行注册,注册代码如下:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IUser, User>();
    services.AddSingleton<ILog, Log>();
    services.AddControllers();
}
  • 上面代码中是以单例的模式进行注册;
  • 将原来在 User 类中的对 Log 类的直接依赖,转移到了 ConfigureServices 方法中,这便是控制反转(IoC)了,Log 类采用什么实现,不在 User 类中来控制,而是转移到了框架层面。

借助框架的依赖注入,相比较我们自己在类中互相关联依赖地去创建对象有以下好处:

  • 方便管理类之间的依赖,对我们使用面向对象的设计原则有帮助;
  • 代码有更好的维护性和扩展性;
  • 可以方便管理各个对象的生命周期。

依赖涉及的核心类

在 dotNET Core 中使用内置的依赖注入需要引入 using Microsoft.Extensions.DependencyInjection;i 命名空间。相关的几个核心类型如下:

  • IServiceCollection:利用此类的扩展方法进行服务的注册;
  • IServiceProvider:由 IServiceCollection 创建的依赖注入容器体现为,IServiceProvider 接口;
  • ServiceDescriptor:具体注册的服务的描述,IServiceProvider 就是通过这个描述来构建我们需要的服务实例;
  • ServiceLifetime:一个服务生命周期的枚举,有 Singleton、Scoped、Transient 三种类型。

服务注册的生命周期

服务注册的生命周期有三种:

  • Singleton:单例模式,创建在全局的 IServiceProvider 的根容器上;
  • Scoped:范围模式,用 Scope 注册的对象,在同一个 ServiceProvider 的 Scope 下相当于单例;
  • Transient:瞬时模式,每次从容器获取对象时都是得到的一个全新的对象。

下面用一个示例来看下这三种不同生命周期的区别
1、创建分别代表不同生命周期的接口和类,代码如下:

public interface ISingletonService{}
public interface IScopedService{}
public interface ITransientService{}

public class SingletonService:ISingletonService{}
public class ScopedService:IScopedService{}
public class TransientService:ITransientService{} 

2、在 Controller 中创建接口方法:

[HttpGet]
public void GetService([FromServices]ISingletonService singleton1,
    [FromServices]ISingletonService singleton2,
    [FromServices]IScopedService scoped1,
    [FromServices]IScopedService scoped2,
    [FromServices]ITransientService transient1,
    [FromServices]ITransientService transient2
  )
{
    System.Console.WriteLine($"singleton1:{singleton1.GetHashCode()}");
    System.Console.WriteLine($"singleton2:{singleton2.GetHashCode()}");
    System.Console.WriteLine($"scoped1:{scoped1.GetHashCode()}");
    System.Console.WriteLine($"scoped2:{scoped2.GetHashCode()}");
    System.Console.WriteLine($"transient1:{transient1.GetHashCode()}");
    System.Console.WriteLine($"transient2:{transient2.GetHashCode()}");
}

3、连续调用两次该接口,输入如下图:

测试示例中每个不同生命周期的对象都通过 FromServices 的方式注入了两次,分析结果如下:

  • Singleton:两次请求的四个对象都相同;
  • Scoped:相同请求的两个对象是一致,重新请求会生成新的对象;
  • Transient:两次请求的四个对象都不相同,每次都构建新的对象。

总结

  • 依赖注入的目的是为了解耦;
  • 不依赖于具体类,而是依赖抽象类或者接口,这叫依赖倒置;
  • 把服务的注册和实例化的工作交给 dotNET Core 框架,而不是在具体实现类中处理,这个叫控制反转即IoC (Inversion of Control)

就先写到这儿了,dotNET Core 框架本身的依赖注入功能已经比较强大,但还是有些功能不能满足,需要引入第三方的注入框架,关于如何引入第三方依赖注入框架以及为什么要用第三方依赖注入框架,后面单独开篇写。

示例代码:https://github.com/oec2003/DotNetCoreThreeAPIDemo/tree/master/InjectDemo