百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

七爪源码:5 个高级 DI 技巧

bigegpt 2024-08-15 20:01 3 浏览

DI 本身是一个非常简单的话题,但即使存在一些可能会让您感到惊讶的东西。 我希望你能在这里学到新的东西。 事不宜迟,让我们开始吧……

1 IServiceScopeFactory

大多数初级开发人员被告知有 3 个服务生命周期:

  • Transient - 每次解决时都会创建服务
  • Scoped - 每个请求创建一次服务
  • Singleton - 每个应用程序创建一次服务

它只是部分正确。 关于 Scoped 生命周期的真实情况,它不受请求的限制。 它只是发生了,为请求创建了一个范围。 但是,还有更多关于它的内容。 您可以使用 IServiceScopeFactory 创建自己的范围:

public class HomeController : ControllerBase
{
    private readonly IServiceScopeFactory _serviceScopeFactory;

    public HomeController(IServiceScopeFactory serviceScopeFactory)
    {
        _serviceScopeFactory = serviceScopeFactory;
    }

    [HttpGet]
    public async Task Index()
    {
        using (var scope = _serviceScopeFactory.CreateScope())
        {
            var service = scope.ServiceProvider.GetRequiredService<IService>();

            service.DoJob();
        }

        // asynchronously Disposes service if supported
        await using (var asyncScope = _serviceScopeFactory.CreateAsyncScope())
        {
            var service = asyncScope.ServiceProvider.GetRequiredService<IService>();

            service.DoJob();
        }
    }
}

在上面的示例中,一个范围内的 IService 实例将为单个请求中的每个范围创建两次。

当您需要在 Singleton 中注入 Scoped 服务以及其他依赖注入范围与上下文生命周期不一致的情况时,这尤其有用。


2 使用多个接口注册服务

每当您有一个实现多个接口的类时:

interface IMessagePublisher { ... }
interface IMessageConsumer { ... }

class MessabeBus : IMessagePublisher, IMessageConsumer { ... }

并且应该由每个单独解决,只有一种真正的方法可以做到:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddScoped<MessageBus>();

        services.AddScoped<IMessagePublisher>(sp => sp.GetRequiredService<MessageBus>());
        services.AddScoped<IMessageConsumer>(sp => sp.GetRequiredService<MessageBus>());
    }
                . . .
}

您需要注册一个服务本身和解析该实例的两个接口。

但是,这里很容易出错:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddScoped<IMessagePublisher, MessageBus>();
        services.AddScoped<IMessageConsumer, MessageBus>();
    }
                . . .
}

使用上面的配置,您最终将为每个接口拥有不同的实例。

而这个错误是我自己犯的。 你能看出这里有什么问题吗?

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        var messageBus = new MessageBus();

        services.AddScoped<IMessagePublisher>(sp => messageBus);
        services.AddScoped<IMessageConsumer>(sp => messageBus);
    }
                . . .
}

MessageBus 是在我们的 DI 机制之外创建的。 尽管它已注册为 Scoped,但该单个实例的行为将像一个单例。

不要像我一样,不要重蹈我的覆辙


3 注入多个服务

没有什么能真正阻止您使用相同的界面注册多个服务:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddScoped<INotificationHandler, SmsNotificationHandler>();
        services.AddScoped<INotificationHandler, EmailNotificationHandler>();
        services.AddScoped<INotificationHandler, SkypeNotificationHandler>();
    }
                . . .
}

此外,它实际上可以非常方便地使用诸如责任链服务之类的模式,可以一次性解决所有问题。 要注入多个实例,请使用 IEnumerable<>:

class NotificationService : INotificationService
{
    private readonly IEnumerable<INotificationHandler> _handlers;

    // what are thoseeee!
    public NotificationService(IEnumerable<INotificationHandler> handlers)
    {
        _handlers = handlers;
    }

    public void Handle(MessageDto message)
    {
        foreach (var handler in _handlers.Where(h => h.CanHandle(message)))
        {
            handler.Handle(message);
        }
    }
}


4 尝试注册

如果您的代码中不需要多个服务,而只需要一个服务怎么办。 但是,有人不小心注册了两次。

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton(sp => new MyService(index: 1));
        services.AddScoped(sp => new MyService(index: 2));
        services.AddScoped(sp => new MyService(index: 3));
    }
                . . .
}

这可能会引起很多麻烦,尤其是当服务包含由于错误的生命周期而消失的状态时。 你能从上面的例子中找出哪个服务会被解析吗? 最新注册的服务将始终具有最高优先级,生命周期在这里无关紧要,请参阅:

[HttpGet]
public string Index()
{
    return _myService.WhoDaFakAreU(); // 3
}

为避免数小时的调试,请使用 TryAddXXX 方法集:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton(sp => new MyService(index: 1));
        services.TryAddScoped(sp => new MyService(index: 2));
        services.TryAddScoped(sp => new MyService(index: 3));
    }
                . . .
}

我们现在很安全。 只有第一个服务会在我们的 DI 容器中注册。


5 解决一个服务多个实现

它可能旨在对一个接口进行多种实现。

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddScoped<IMessageBus, RabbitMqMessageBus>();
        services.AddScoped<IMessageBus, AzureMessageBus>();
    }
                . . .
}

你想区分这些。

我们已经看到的一种方式,但现在在 Linq 的帮助下:

public class MyService : ControllerBase
{
    private readonly IMessageBus _messageBus;

    public MyService(IEnumerable<IMessageBus> collection)
    {
        _messageBus = collection.OfType<RabbitMqMessageBus>().Single();
    }
          . . .
}

就个人而言,我不喜欢它。 您需要注入集合以获取单个项目。 不仅看起来很糟糕,而且在测试时也不方便。 您依赖于直接实施。

撇开我的偏好不谈,当具体实例被标记为内部并位于另一个库中时,您的架构也无法实现。

另一种方法是添加更多接口:

interface IMessageBus { ... }

// marker interfaces
// does not have any implementation, just helps us with DI
interface IRabbitMqMessageBus : IMessageBus { /*empty here*/ }
interface IAzureMessageBus : IMessageBus { }

class RabbitMqMessageBus : IRabbitMqMessageBus { ... }
class AzureMessageBus : IAzureMessageBus { ... }

并使用这些新接口注册和解决它:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddScoped<IRabbitMqMessageBus, RabbitMqMessageBus>();
        services.AddScoped<IAzureMessageBus, AzureMessageBus>();
    }
                . . .
}

或者第三种选择,如果你真的想成为一个聪明人,你可以使用工厂

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddScoped<RabbitMqMessageBus>();
        services.AddScoped<AzureMessageBus>();

        services.AddTransient<Func<MessageBusType, IMessageBus>>(serviceProvider => key =>
        {
            return key switch
            {
                MessageBusType.RabbitMq => serviceProvider.GetService<RabbitMqMessageBus>(),
                MessageBusType.Azure => serviceProvider.GetService<AzureMessageBus>(),

                _ => throw new InvalidEnumArgumentException(),
            };
        });
    }
                . . .
}

随着它的时髦用法:

public class MyService
{
    private readonly IMessageBus _messageBus;

    public MyService(Func<MessageBusType, IMessageBus> messageBusFactory)
    {
        _messageBus = messageBusFactory(MessageBusType.RabbitMq);
    }
      . . .
}

您可以决定哪一种最适合您的需求。


关注七爪网,获取更多APP/小程序/网站源码资源!

相关推荐

了解Linux目录,那你就了解了一半的Linux系统

大到公司或者社群再小到个人要利用Linux来开发产品的人实在是多如牛毛,每个人都用自己的标准来配置文件或者设置目录,那么未来的Linux则就是一团乱麻,也对管理造成许多麻烦。后来,就有所谓的FHS(F...

Linux命令,这些操作要注意!(linux命令?)

刚玩Linux的人总觉得自己在演黑客电影,直到手滑输错命令把公司服务器删库,这才发现命令行根本不是随便乱用的,而是“生死簿”。今天直接上干货,告诉你哪些命令用好了封神!喜欢的一键三连,谢谢观众老爷!!...

Linux 命令速查手册:这 30 个高频指令,拯救 90% 的运维小白!

在Linux系统的世界里,命令行是强大的武器。对于运维小白而言,掌握一些高频使用的Linux命令,能极大提升工作效率,轻松应对各种系统管理任务。今天,就为大家奉上精心整理的30个Linu...

linux必学的60个命令(linux必学的20个命令)

以下是Linux必学的20个基础命令:1.cd:切换目录2.ls:列出文件和目录3.mkdir:创建目录4.rm:删除文件或目录5.cp:复制文件或目录6.mv:移动/重命名文件或目录7....

提高工作效率的--Linux常用命令,能够决解95%以上的问题

点击上方关注,第一时间接受干货转发,点赞,收藏,不如一次关注评论区第一条注意查看回复:Linux命令获取linux常用命令大全pdf+Linux命令行大全pdf为什么要学习Linux命令?1、因为Li...

15 个实用 Linux 命令(linux命令用法及举例)

Linux命令行是系统管理员、开发者和技术爱好者的强大工具。掌握实用命令不仅能提高效率,还能解锁Linux系统的无限潜力,本文将深入介绍15个实用Linux命令。ls-列出目录内容l...

Linux 常用命令集合(linux常用命令全集)

系统信息arch显示机器的处理器架构(1)uname-m显示机器的处理器架构(2)uname-r显示正在使用的内核版本dmidecode-q显示硬件系统部件-(SMBIOS/DM...

Linux的常用命令就是记不住,怎么办?

1.帮助命令1.1help命令#语法格式:命令--help#作用:查看某个命令的帮助信息#示例:#ls--help查看ls命令的帮助信息#netst...

Linux常用文件操作命令(linux常用文件操作命令有哪些)

ls命令在Linux维护工作中,经常使用ls这个命令,这是最基本的命令,来写几条常用的ls命令。先来查看一下使用的ls版本#ls--versionls(GNUcoreutils)8.4...

Linux 常用命令(linux常用命令)

日志排查类操作命令查看日志cat/var/log/messages、tail-fxxx.log搜索关键词grep"error"xxx.log多条件过滤`grep-E&#...

简单粗暴收藏版:Linux常用命令大汇总

号主:老杨丨11年资深网络工程师,更多网工提升干货,请关注公众号:网络工程师俱乐部下午好,我的网工朋友在Linux系统中,命令行界面(CLI)是管理员和开发人员最常用的工具之一。通过命令行,用户可...

「Linux」linux常用基本命令(linux常用基本命令和用法)

Linux中许多常用命令是必须掌握的,这里将我学linux入门时学的一些常用的基本命令分享给大家一下,希望可以帮助你们。总结送免费学习资料(包含视频、技术学习路线图谱、文档等)1、显示日期的指令:d...

Linux的常用命令就是记不住,怎么办?于是推出了这套教程

1.帮助命令1.1help命令#语法格式:命令--help#作用:查看某个命令的帮助信息#示例:#ls--help查看ls命令的帮助信息#netst...

Linux的30个常用命令汇总,运维大神必掌握技能!

以下是Linux系统中最常用的30个命令,精简版覆盖日常操作核心需求,适合快速掌握:一、文件/目录操作1.`ls`-列出目录内容`ls-l`(详细信息)|`ls-a`(显示隐藏文件)...

Linux/Unix 系统中非常常用的命令

Linux/Unix系统中非常常用的命令,它们是进行文件操作、文本处理、权限管理等任务的基础。下面是对这些命令的简要说明:**文件操作类:*****`ls`(list):**列出目录内容,显...