Fork me on GitHub

1、前言

Asp.Net Core 默认支持 DI(依赖注入) 软件设计模式,那使用 DI 的过程中,我们势必会接触到对象的生命周期,那么几种不同的对象生命周期到底是怎么样的呢?我们拿代码说话。

关于 DI 与 IOC:

个人理解:IOC(控制反转) 是目的(降低代码、服务间的耦合),而 DI 是达到该目的的一种手段(具体办法)。

2、DI生命周期

DI的生命周期,根据框架、库的不同,会略有差异。此处,我们就以微软的DI扩展为例,来说下DI中常用的几种生命周期。

首先,我们想象一个这样一个场景。假设我们有寄快递的需求,那么我们会致电快递公司:“我们要寄快递,派一个快递员过来收货”。接着,快递公司会如何做呢?

  1. 一直派遣同一个快递员来收货。
  2. 第一周派遣快递员A、第二周派遣快递员B收货。
  3. 每次都派遣一个新的快递员收货。

这对应到生命周期就是:

  1. 单例(Singleton),单一实例,每次使用都是该实例。
  2. 作用域实例(Scoped),在一个作用域(比如单次请求)内是同一个实例,不同的作用域实例不同。
  3. 瞬时实例(Transient),每次使用都创建新的实例。

快递公司也就是我们在DI中常说的容器(Container)了。

2.1、验证准备

首先,我们需要三个Services(Service1\Service2\Service3)内容一致,如下:

// Service1.cs,Service2、Service3除类名以外,内容一致
public class Service1
{
    private int value = 0;

    public int GetValue()
    {
        this.value++;
        return this.value;
    }
}

然后,我们需要一个业务类,再一次注入这三个Service,内容如下:

// DefaultBusiness.cs 
public class DefaultBusiness
{
    private readonly Service1 s1;
    private readonly Service2 s2;
    private readonly Service3 s3;

    public DefaultBusiness(Service1 s1, Service2 s2, Service3 s3)
    {
        this.s1 = s1;
        this.s2 = s2;
        this.s3 = s3;
    }

    public int GetS1Value()
    {
        return this.s1.GetValue();
    }

    public int GetS2Value()
    {
        return this.s2.GetValue();
    }

    public int GetS3Value()
    {
        return this.s3.GetValue();
    }
}

最后,还需要在Startup.cs进行注入

// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
  // 单例模式
  services.AddSingleton<Service1>();
  // 作用域模式
  services.AddScoped<Service2>();
  // 瞬时模式
  services.AddTransient<Service3>();
  // 为了保证结果,将Business类注册为瞬时模式,每次注入都是全新的。
  services.AddTransient<Business.DefaultBusiness>();
}

2.2、验证单例(Singleton)

对于单例来说,我们期望,在整个程序启动期间,都是同一个实例,所以,我们只需要在Service中,增加一个局部变量,做累加就可以验证。

// DefaultController.cs
[Route("singleton")]
public IActionResult SingletonTest()
{
    this.defaultBiz.GetS1Value();
    return Json(new
    {
        s1 = s1.GetValue()
    });
}

然后我们访问 http://localhost:5000/singleton 多次,输入如下:

// 第一次
{ s1: 2 }
// 第二次
{ s1: 4 }
// 第三次
{ s1: 6 }

可以发现,每请求一次,value都会增加2。分析下怎么来的呢?

  1. 首先是执行 defaultBiz.GetValue() 中,根据内部代码,此处会对注入的实例,value + 1。
  2. 之后,Json()中的,s1 = s1.GetValue(),此处再一次增加了1。
  3. 综上,一次请求,s1 中的value值会增加2,由于是单例模式,在整个服务运行期间,都是一个实例,所以,每次请求都会累加2。

2.3、验证作用域实例(Scoped)

// DefaultController.cs
[Route("scoped")]
public IActionResult ScopedTest()
{
    this.defaultBiz.GetS2Value();
    return Json(new
    {
        s2 = s2.GetValue()
    });
}

然后我们访问 http://localhost:5000/scoped 多次,输入如下:

// 第一次
{ s2: 2 }
// 第二次
{ s2: 2 }
// 第三次
{ s2: 2 }

从结果可以看出,每次请求的返回值是固定的,都为2,也就是证明了Service2中,value++执行了两次。对于执行value++的代码,只有 defaultBiz.GetS2Value()s2 = s2.GetValue(),所以这两处操作的是同一个实例。这也就证明了,对于 Scoped 生命周期,在作用域(可以简单理解为单次请求,实际上并不准确,注意,此处为考虑多线程的情况)内,都是使用的同一个实例。在不同的请求之间,则是不同的实例。

2.4、验证瞬时实例(Transient)

// DefaultController.cs
[Route("transient")]
public IActionResult TransientTest()
{
    return Json(new
    {
        s3 = s3.GetValue()
    });
}

然后我们访问 http://localhost:5000/transient 多次,输入如下:

// 第一次
{ s3: 1 }
// 第二次
{ s3: 1 }
// 第三次
{ s3: 1 }

从结果来看,每次请求的都是相同的返回值,s3 = 1,这说明了,两次操作的value++,是针对的不同实例。也就是每次使用 Service1,都是全新的实例。

3、扩展(Autofac DI 类库)

Asp.Net Core中默认的DI,相对还是比较简单的,只有三个生命周期。对于时下比较的依赖注入库,一般都会有更多的生命周期,有些还会有生命周期事件可以监控。

Autofac 为例,该类库提供了如下一些生命周期,可以做到更精细化的控制:

  1. 单次依赖(Instance Per Dependency)- 也就是Transient,每次获取实例都是全新的。
  2. 单例(Single Instance) - 也就是单例,整个服务周期都是一个实例。
  3. 作用域隔离的实例(Instance Per Lifetime Scope) - 也就是一个作用域一个,示例代码如下:
// 先创建作用域
using(var scope1 = container.BeginLifetimeScope())
{
  
  for(var i = 0; i < 100; i++)
  {
    // 在作用域内,Resolve 的都是同一个实例
    var w1 = scope1.Resolve<Worker>();
  }
}

// 创建另一个作用域
using(var scope2 = container.BeginLifetimeScope())
{
  for(var i = 0; i < 100; i++)
  {
    // 在作用域内,Resolve 的都是同一个实例,但是这个实例和 scope1 作用域中的 w1 不是同一个。
    var w2 = scope2.Resolve<Worker>();
  }
}
  1. 带标签的作用域隔离实例(Instance Per Matching Lifetime Scope)
  2. 单次请求作用域实例(Instance Per Request) - 每个请求作为一个作用域。
  3. 指定Owner的作用域实例(Instance Per Owned)- 对于同一个Owner,实例保持一致
  4. 线程作用域实例(Thread Scope)

更多 Autofac 生命周期相关内容,请参考:https://autofac.readthedocs.io/en/latest/lifetime/instance-scope.html

4、总结

本文主要简单演示了 Asp.Net Core 中默认的几种服务生命周期效果,也抛砖引玉的说了下 Autofac 中的服务生命周期。合理的利用生命周期,可以减少对象的创建,提交程序性能。但是,用错了生命周期,则容易产生隐含的bug。在使用 DI 类库的时候,一定要理解清楚不同的生命周期的应用场景。

本文示例代码:https://github.com/hstarorg/HstarDemoProject/tree/master/dotnet_demo/servicelifttime-demo/ServicelifttimeDemo

本文github地址

posted @ 2019-01-28 17:09 幻天芒 阅读(2099) 评论(0) 推荐(1) 编辑
摘要: 1、前言 某一刻,你已经把 .Net Core 的程序写好了。接下来,还可以做什么呢?那就是部署了。 作为一名开发工程师,如果不会部署自己开发的应用,那么这也是不完整的。接下来,我们就来说说,如何部署我们的 .Net Core 应用程序(主要是 Asp.Net Core 应用)。 2、Asp.Net 阅读全文
posted @ 2019-01-28 17:08 幻天芒 阅读(8756) 评论(25) 推荐(13) 编辑
摘要: 0、前言 当一个公司有多个开发团队时,我们可能会遇到这样一些问题: 1. 技术选项杂乱,大家各玩各 2. 业务重复度高,各种通用api,登录注销,权限管理都需要重复实现(甚至一个团队都需要重复实现) 3. 业务壁垒,业务之间的互通变得比较麻烦 4. 部署方式复杂,多个域名(或IP地址)访问,给用户造 阅读全文
posted @ 2017-11-23 13:48 幻天芒 阅读(3095) 评论(12) 推荐(3) 编辑
摘要: 1、导言 在 的世界里,异步(由于JavaScript的单线程运行,所以JavaScript中的异步是可以阻塞的)无处不在。 "Express" 是 环境中非常流行的Web服务端框架,有很大比例的 采用了 。 当使用 编写服务端代码时,我们无可避免的会大量使用到异步。随着 的进化,我们的异步处理方式 阅读全文
posted @ 2017-09-09 13:07 幻天芒 阅读(3402) 评论(4) 推荐(3) 编辑
摘要: 同源策略 在说跨域之前,我们需要先了解下 "同源策略" 。它是一个规范(Netscape 1995年提出),并没有指定具体的使用范围和实现方式。 为了保证使用者信息的安全,防止恶意网站篡改用户数据,一些常见的Web技术都默认采用了同源策略(如Silverlight, Flash, XMLHttpRe 阅读全文
posted @ 2017-07-08 13:02 幻天芒 阅读(3664) 评论(16) 推荐(15) 编辑
摘要: Docker私有仓库 在 中,当我们执行 的时候,可能会比较好奇, 会去哪儿查找并下载镜像呢? 它实际上是从 这个地址去查找,这就是Docker公司为我们提供的公共仓库,上面的镜像,大家都可以看到,也可以使用。 所以,我们也可以带上仓库地址去拉取镜像,如: ,不过要注意,这种方式下载的镜像的默认名称 阅读全文
posted @ 2017-06-09 08:41 幻天芒 阅读(6173) 评论(12) 推荐(13) 编辑
摘要: 0、前言 一段代码引发的思考: 先看看如上的代码,不要执行,自己先猜测下结果。 1、正则修饰符 正则在各个语言中,实现的标准并不完全一致。我们这里就讨论在 中的实现。 在 中,正则有四个修饰符: ,详细请参考: "MDN RegExp" 。 它们的含义如下: global(g) 针对字符串中所有可能 阅读全文
posted @ 2017-06-01 09:21 幻天芒 阅读(1472) 评论(0) 推荐(0) 编辑
摘要: 什么是Humpback? 项目开源地址在此: Humpback Source,觉得有用或者有趣,欢迎给个star~ 在回答这个问题前,我们得先了解下什么的 Docker(哦,现在叫 Moby,文中还是继续称 Docker)。 在 Docker-百度百科 中,对 Docker 已经解释得很清楚了。 简 阅读全文
posted @ 2017-05-16 08:48 幻天芒 阅读(9710) 评论(47) 推荐(10) 编辑
摘要: react version: 15.4.2 React.Component 组件能够让你将UI拆分为多个独立自治并可重用的部分。在 中提供了 。 概述 是一个抽象基类,直接引用 无太大意义。反而,我们会用子类来继承它,并至少定义一个 方法。 通常您将使用纯 "JavaScript class" 来定 阅读全文
posted @ 2017-04-14 17:56 幻天芒 阅读(1267) 评论(0) 推荐(0) 编辑
摘要: react version: 15.5.0 组件与属性 组件允许您将UI拆分为多个独立的,可重用的部分。 概念上,组件就类似于 函数。它们接收任意的输入( ),返回用于描述屏幕显示内容的 。 函数式组件和类组件 最简单定义组件的方式,是编写一个 函数: 这个函数是一个合法的组件,因为它接收包含数据的 阅读全文
posted @ 2017-04-14 17:55 幻天芒 阅读(652) 评论(0) 推荐(0) 编辑
点击右上角即可分享
微信分享提示