首页 > 其他分享 >.Net依赖注入神器Scrutor(上)

.Net依赖注入神器Scrutor(上)

时间:2024-03-19 09:15:01浏览次数:29  
标签:ServiceType service builder Scrutor typeSourceSelector 神器 typeof Services Net

前言

从.Net Core 开始,.Net 平台内置了一个轻量,易用的 IOC 的框架,供我们在应用程序中使用,社区内还有很多强大的第三方的依赖注入框架如:

内置的依赖注入容器基本可以满足大多数应用的需求,除非你需要的特定功能不受它支持否则不建议使用第三方的容器。

我们今天介绍的主角Scrutor是内置依赖注入的一个强大的扩展,Scrutor有两个核心的功能:一是程序集的批量注入 Scanning,二是 Decoration 装饰器模式,今天的主题是Scanning

开始之前在项目中安装 nuget 包:

Install-Package Scrutor

学习Scrutor前我们先熟悉一个.Net依赖注入的万能用法。

builder.Services.Add(
    new ServiceDescriptor(/*"ServiceType"*/typeof(ISampleService), /*"implementationType"*/typeof(SampleService), ServiceLifetime.Transient)
    );

第一个参数ServiceType通常用接口表示,第二个implementationType接口的实现,最后生命周期,熟悉了这个后面的逻辑理解起来就容易些。

Scrutor官方仓库和本文完整的源代码在文末

Scanning

Scrutor提供了一个IServiceCollection的扩展方法作为批量注入的入口,该方法提供了Action<ITypeSourceSelector>委托参数。

builder.Services.Scan(typeSourceSelector => { });

我们所有的配置都是在这个委托内完成的,Setup by Setup 剖析一下这个使用过程。

第一步 获取 types

typeSourceSelector 支持程序集反射获取类型和提供类型参数

程序集选择

ITypeSourceSelector有多种获取程序集的方法来简化我们选择程序集


 typeSourceSelector.FromAssemblyOf<Program>();//根据泛型反射获取所在的程序集

typeSourceSelector.FromCallingAssembly();//获取开始发起调用方法的程序集

typeSourceSelector.FromEntryAssembly();//获取应用程序入口点所在的程序集

typeSourceSelector.FromApplicationDependencies();//获取应用程序及其依赖项的程序集

typeSourceSelector.FromDependencyContext(DependencyContext.Default);//根据依赖关系上下文(DependencyContext)中的运行时库(Runtime Library)列表。它返回一个包含了所有运行时库信息的集合。

typeSourceSelector.FromAssembliesOf(typeof(Program));//根据类型获取程序集的集合

typeSourceSelector.FromAssemblies(Assembly.Load("dotNetParadise-Scrutor.dll"));//提供程序集支持Params或者IEnumerable

第二步 从 Types 中选择 ImplementationType

简而言之就是从程序中获取的所有的 types 进行过滤,比如获取的 ImplementationType 必须是非抽象的,是类,是否只需要 Public等,还可以用 ImplementationTypeFilter 提供的扩展方法等


builder.Services.Scan(typeSourceSelector =>
{
    typeSourceSelector.FromEntryAssembly().AddClasses();
});

AddClasses()方法默认获取所有公开非抽象的类


还可以通过 AddClasses 的委托参数来进行更多条件的过滤
比如定义一个 Attribute,忽略IgnoreInjectAttribute

namespace dotNetParadise_Scrutor;

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class IgnoreInjectAttribute : Attribute
{
}
builder.Services.Scan(typeSourceSelector =>
{
    typeSourceSelector.FromEntryAssembly().AddClasses(iImplementationTypeFilter =>
    {
        iImplementationTypeFilter.WithoutAttribute<IgnoreInjectAttribute>();
    });
});

利用 iImplementationTypeFilter 的扩展方法很简单就可以实现

在比如 我只要想实现IApplicationService接口的类才可以被注入

namespace dotNetParadise_Scrutor;

/// <summary>
/// 依赖注入标记接口
/// </summary>
public interface IApplicationService
{
}
builder.Services.Scan(typeSourceSelector =>
{
    typeSourceSelector.FromEntryAssembly().AddClasses(iImplementationTypeFilter =>
    {
        iImplementationTypeFilter.WithoutAttribute<IgnoreInjectAttribute>().AssignableTo<IApplicationService>();
    });
});

类似功能还有很多,如可以根据命名空间也可以根据Type的属性用lambda表达式对ImplementationType进行过滤


上面的一波操作实际上就是为了构造一个IServiceTypeSelector对象,选出来的ImplementationType对象保存了到了ServiceTypeSelectorTypes属性中供下一步选择。
除了提供程序集的方式外还可以直接提供类型的方式比如

创建接口和实现

public interface IForTypeService
{
}
public class ForTypeService : IForTypeService
{
}
builder.Services.Scan(typeSourceSelector =>
{
    typeSourceSelector.FromTypes(typeof(ForTypeService));
});

这种方式提供类型内部会调用AddClass()方法把符合条件的参数保存到ServiceTypeSelector

第三步确定注册策略

AddClass之后可以调用UsingRegistrationStrategy()配置注册策略是 AppendSkipThrowReplace
下面是各个模式的详细解释

  • RegistrationStrategy.Append :类似于builder.Services.Add
  • RegistrationStrategy.Skip:类似于builder.Services.TryAdd
  • RegistrationStrategy.Throw:ServiceDescriptor 重复则跑异常
  • RegistrationStrategy.Replace: 替换原有服务

这样可以灵活地控制注册流程

    builder.Services.Scan(typeSourceSelector =>
    {
        typeSourceSelector.FromEntryAssembly().AddClasses().UsingRegistrationStrategy(RegistrationStrategy.Skip);
    });

不指定则为默认的 Append 即 builder.Services.Add

第四步 配置注册的场景选择合适的ServiceType

ServiceTypeSelector提供了多种方法让我们从ImplementationType中匹配ServiceType

  • AsSelf()
  • As<T>()
  • As(params Type[] types)
  • As(IEnumerable<Type> types)
  • AsImplementedInterfaces()
  • AsImplementedInterfaces(Func<Type, bool> predicate)
  • AsSelfWithInterfaces()
  • AsSelfWithInterfaces(Func<Type, bool> predicate)
  • AsMatchingInterface()
  • AsMatchingInterface(Action<Type, IImplementationTypeFilter>? action)
  • As(Func<Type, IEnumerable<Type>> selector)
  • UsingAttributes()

AsSelf 注册自身

public class AsSelfService
{
}
{
    builder.Services.Scan(typeSourceSelector =>
    {
        typeSourceSelector.FromEntryAssembly().AddClasses(iImplementationTypeFilter =>
        {
            iImplementationTypeFilter.InNamespaces("dotNetParadise_Scrutor.Application.AsSelf").WithoutAttribute<IgnoreInjectAttribute>();
        }).AsSelf();
    });

    Debug.Assert(builder.Services.Any(_ => _.ServiceType == typeof(AsSelfService)));
}

等效于builder.Services.AddTransient<AsSelfService>();


As 批量为 ImplementationType 指定 ServiceType

public interface IAsService
{
}
public class AsOneService : IAsService
{
}
public class AsTwoService : IAsService
{
}
{
    builder.Services.Scan(typeSourceSelector =>
{
    typeSourceSelector.FromEntryAssembly().AddClasses(iImplementationTypeFilter =>
    {
        iImplementationTypeFilter.InNamespaces("dotNetParadise_Scrutor.Application.As").WithoutAttribute<IgnoreInjectAttribute>();
    }).As<IAsService>();
});
    Debug.Assert(builder.Services.Any(_ => _.ServiceType == typeof(IAsService)));
    foreach (var asService in builder.Services.Where(_ => _.ServiceType == typeof(IAsService)))
    {
        Debug.WriteLine(asService.ImplementationType!.Name);
    }
}

As(params Type[] types)和 As(IEnumerable types) 批量为ImplementationType指定多个 ServiceType,服务必须同时实现这里面的所有的接口

上面的实例再改进一下

public interface IAsOtherService
{
}
public interface IAsSomeService
{
}

public class AsOneMoreTypesService : IAsOtherService, IAsSomeService
{
}
public class AsTwoMoreTypesService : IAsSomeService, IAsOtherService
{
}

{
    builder.Services.Scan(typeSourceSelector =>
    {
        typeSourceSelector.FromEntryAssembly().AddClasses(iImplementationTypeFilter =>
        {
            iImplementationTypeFilter.InNamespaces("dotNetParadise_Scrutor.Application.AsMoreTypes").WithoutAttribute<IgnoreInjectAttribute>();
        }).As(typeof(IAsSomeService), typeof(IAsOtherService));
    });
    List<Type> serviceTypes = [typeof(IAsSomeService), typeof(IAsOtherService)];
    Debug.Assert(serviceTypes.All(serviceType => builder.Services.Any(service => service.ServiceType == serviceType)));
    foreach (var asService in builder.Services.Where(_ => _.ServiceType == typeof(IAsSomeService) || _.ServiceType == typeof(IAsOtherService)))
    {
        Debug.WriteLine(asService.ImplementationType!.Name);
    }
}

AsImplementedInterfaces 注册当前 ImplementationType 和实现的接口

public interface IAsImplementedInterfacesService
{
}
public class AsImplementedInterfacesService : IAsImplementedInterfacesService
{
}
//AsImplementedInterfaces 注册当前ImplementationType和它实现的接口
{
    builder.Services.Scan(typeSourceSelector =>
    {
        typeSourceSelector.FromEntryAssembly().AddClasses(iImplementationTypeFilter =>
        {
            iImplementationTypeFilter.InNamespaces("dotNetParadise_Scrutor.Application.AsImplementedInterfaces").WithoutAttribute<IgnoreInjectAttribute>();
        }).AsImplementedInterfaces();
    });

    Debug.Assert(builder.Services.Any(service => service.ServiceType == typeof(IAsImplementedInterfacesService)));
    foreach (var asService in builder.Services.Where(_ => _.ServiceType == typeof(IAsImplementedInterfacesService)))
    {
        Debug.WriteLine(asService.ImplementationType!.Name);
    }
}

AsSelfWithInterfaces 同时注册为自身类型和所有实现的接口

public interface IAsSelfWithInterfacesService
{
}
public class AsSelfWithInterfacesService : IAsSelfWithInterfacesService
{
}

{
    builder.Services.Scan(typeSourceSelector =>
    {
        typeSourceSelector.FromEntryAssembly().AddClasses(iImplementationTypeFilter =>
        {
            iImplementationTypeFilter.InNamespaces("dotNetParadise_Scrutor.Application.AsSelfWithInterfaces").WithoutAttribute<IgnoreInjectAttribute>();
        }).AsSelfWithInterfaces();
    });
    //Self
    Debug.Assert(builder.Services.Any(service => service.ServiceType == typeof(AsSelfWithInterfacesService)));
    //Interfaces
    Debug.Assert(builder.Services.Any(service => service.ServiceType == typeof(IAsSelfWithInterfacesService)));
    foreach (var service in builder.Services.Where(_ => _.ServiceType == typeof(AsSelfWithInterfacesService) || _.ServiceType == typeof(IAsSelfWithInterfacesService)))
    {
        Debug.WriteLine(service.ServiceType!.Name);
    }
}

AsMatchingInterface 将服务注册为与其命名相匹配的接口,可以理解为一定约定假如服务名称为 ClassName,会找 IClassName 的接口作为 ServiceType 注册

public interface IAsMatchingInterfaceService
{
}
public class AsMatchingInterfaceService : IAsMatchingInterfaceService
{
}
//AsMatchingInterface 将服务注册为与其命名相匹配的接口,可以理解为一定约定假如服务名称为 ClassName,会找 IClassName 的接口作为 ServiceType 注册
{
    builder.Services.Scan(typeSourceSelector =>
    {
        typeSourceSelector.FromEntryAssembly().AddClasses(iImplementationTypeFilter =>
        {
            iImplementationTypeFilter.InNamespaces("dotNetParadise_Scrutor.Application.AsMatchingInterface").WithoutAttribute<IgnoreInjectAttribute>();
        }).AsMatchingInterface();
    });
    Debug.Assert(builder.Services.Any(service => service.ServiceType == typeof(IAsMatchingInterfaceService)));
    foreach (var service in builder.Services.Where(_ => _.ServiceType == typeof(IAsMatchingInterfaceService)))
    {
        Debug.WriteLine(service.ServiceType!.Name);
    }
}

UsingAttributes 特性注入,这个还是很实用的在Scrutor提供了ServiceDescriptorAttribute来帮助我们方便的对Class进行标记方便注入

public interface IUsingAttributesService
{
}

[ServiceDescriptor<IUsingAttributesService>()]
public class UsingAttributesService : IUsingAttributesService
{
}
    builder.Services.Scan(typeSourceSelector =>
    {
        typeSourceSelector.FromEntryAssembly().AddClasses(iImplementationTypeFilter =>
        {
            iImplementationTypeFilter.InNamespaces("dotNetParadise_Scrutor.Application.UsingAttributes").WithoutAttribute<IgnoreInjectAttribute>();
        }).UsingAttributes();
    });
    Debug.Assert(builder.Services.Any(service => service.ServiceType == typeof(IUsingAttributesService)));
    foreach (var service in builder.Services.Where(_ => _.ServiceType == typeof(IUsingAttributesService)))
    {
        Debug.WriteLine(service.ServiceType!.Name);
    }

第五步 配置生命周期

通过链式调用WithLifetime函数来确定我们的生命周期,默认是 Transient

public interface IFullService
{
}
public class FullService : IFullService
{
}
{

    builder.Services.Scan(typeSourceSelector =>
    {
        typeSourceSelector.FromEntryAssembly().AddClasses(iImplementationTypeFilter =>
        {
            iImplementationTypeFilter.InNamespaces("dotNetParadise_Scrutor.Application.Full");
        }).UsingRegistrationStrategy(RegistrationStrategy.Skip).AsImplementedInterfaces().WithLifetime(ServiceLifetime.Scoped);
    });

    Debug.Assert(builder.Services.Any(service => service.ServiceType == typeof(IFullService)));
    foreach (var service in builder.Services.Where(_ => _.ServiceType == typeof(IFullService)))
    {
        Debug.WriteLine($"serviceType:{service.ServiceType!.Name},LifeTime:{service.Lifetime}");
    }
}

总结

到这儿基本的功能已经介绍完了,可以看出来扩展方法很多,基本可以满足开发过程批量依赖注入的大部分场景。
使用技巧总结:

  • 根据程序集获取所有的类型 此时 Scrutor 会返回一个 IImplementationTypeSelector 对象里面包含了程序集的所有类型集合
  • 调用 IImplementationTypeSelectorAddClasses 方法获取 IServiceTypeSelector 对象,AddClass 这里面可以根据条件选择 过滤一些不需要的类型
  • 调用UsingRegistrationStrategy确定依赖注入的策略 是覆盖 还是跳过亦或是抛出异常 默认 Append 追加注入的方式
  • 配置注册的场景 比如是 AsImplementedInterfaces 还是 AsSelf
  • 选择生命周期 默认 Transient

借助ServiceDescriptorAttribute更简单,生命周期和ServiceType都是在Attribute指定好的只需要确定选择程序集,调用UsingRegistrationStrategy配置依赖注入的策略然后UsingAttributes()即可

最后

本文从Scrutor的使用流程剖析了依赖注入批量注册的流程,更详细的教程可以参考Github 官方仓库。在开发过程中看到很多项目还有一个个手动注入的,也有自己写 Interface或者是Attribute反射注入的,支持的场景都十分有限,Scrutor的出现就是为了避免我们在项目中不停地造轮子,达到开箱即用的目的。

本文完整示例源代码

标签:ServiceType,service,builder,Scrutor,typeSourceSelector,神器,typeof,Services,Net
From: https://www.cnblogs.com/ruipeng/p/18081965

相关文章

  • AI换脸神器A kool全面解析:超简单1分钟操作,创意爆棚,商业前景广阔!
    大家好,今天我要给大家介绍一款超级好用的AI换脸工具-Akool。这款工具不仅简单易用,而且在数字营销领域有着广泛的应用,比如定制写真、影视换脸、虚拟网红、短剧出海等等。听起来是不是很神奇呢?接下来,我就带大家一起了解一下这款神奇的AI换脸工具。一、Akool简介Akool是一......
  • ModbusTCP转Profinet网关高低字节交换切换
    ModbusTCP转Profinet网关高低字节交换切换背景:在现场设备与设备通迅之间通常涉及到从一种字节序(大端或小端)转换到另一种字节序。大端字节序是指高位字节存储在高地址处,而小端字节序是指低位字节存储在低地址处。在不动原有程序而又不想或不能添加程序下可选用ModbusTCP转Profinet......
  • 【App Service】在Azure App Service中分析.NET应用程序的性能的好帮手(Review Stack
    AzureAppService.NETProfiler在AppService服务中,如果部署了.NET应用,平台有一个非常好的工具可以查看请求的性能分布及异常时的StackTraces。进入路径:AppServiceAzureOverview-->  Networking(网络)-->Troubleshoot(排除故障)--> Collect.NETProfilerTrace......
  • ASP.NET Web Api 中 Grpc 的简单使用
    服务端添加Nuget包dotnetaddpackageGrpc.AspNetCore编写Protobuf文件syntax="proto3";optioncsharp_namespace="GrpcGreeter";packagegreet;//Thegreetingservicedefinition.serviceGreeter{//SendsagreetingrpcSayHello(Hell......
  • netcore接入elk
    一、elk的安装教程参考链接:https://www.8kiz.cn/archives/2623.html 二、netcore接入elk1、NLog接入NLog日志输出到logstash里,使用方式①配置logstash,添加tcp端口输入input{tcp{port=>5044type=>"service1-log"}}②重启logstashsudosystemc......
  • Net通用应用的项目框架脚手架
    说明通用应用的项目框架脚手架介绍1、新建模板项目的文件夹Tpl,并在该文件夹下面新建templates文件2、将我们需要打包的项目放到templates中3、在templates中新建.template.config文件夹4、在.template.config文件夹中新建template.json文件5、在template.json文......
  • 打造专业人像照片的必备神器Portraiture2024免费中文版PS/LR专用智能磨皮插件
    打造完美肤质,PortraiturePS/LR专用智能磨皮插件让你的照片焕发魅力副标题:让你的照片告别粗糙皮肤和毛孔,展现自然细腻的肤质在摄影后期处理中,给照片进行磨皮和肤质优化是一项必不可少的步骤。而今天,我们为你带来了一款专为Photoshop(PS)和Lightroom(LR)设计的智能磨皮插件——Po......
  • Linux Java调用 海康sdk报 Unable to load library '/home/slife/bsmt/HCNetSDK_linux
    1、问题在Linux下java调研libPlayCtrl.so文件失败 解决方案:sudovim~/.bashrc 在该文件末尾追加:exportLD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/slife/bsmt/HCNetSDK_linux64/刷新一下source~/.bashrcok参考链接 https://www.cnblogs.com/kikyoqiang/p/14911373.......
  • Hero Talk|无缝扩展:Kubernetes 上的 Amazon Aurora 分片和流量管理
    亚马逊云科技DataHero潘娟正在打开开源之门。作为“2020中国开源先锋人物”以及“2021OSCAR尖峰开源人物”奖项获得者,她致力于赋能数据领域的开发者,助力他们把握先机。在亚马逊云科技re:Invent2023大会上,潘娟就Kubernetes上的 Amazon Aurora 分片和流量管理发表了精......
  • 又发现一款免费好用的 AI 写代码神器,好用到爆,GitHub Copilot 可以扔了。。
    大家好,我是R哥。近两年AI太火了,风靡全球,AI编程工具也没有落下,比如微软的GitHubCopilot,还有阿里的通义灵码,连JetBrains系列工具都逼出了自家的AI功能。大家知道我是效率狂人,同样也是工具狂人,之前给大家分享了不少开发神器,其中也不乏国内的优秀选手,不是我吹,在某些方面,国......