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


    前言

    从.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委托参数。

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

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

    第一步 获取 types

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

    程序集选择

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


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

    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();
        });
    });
    

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

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

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

    类似功能还有很多,如可以根据命名空间也可以根据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()
    • As(params Type[] types)
    • As(IEnumerable types)
    • AsImplementedInterfaces()
    • AsImplementedInterfaces(Func predicate)
    • AsSelfWithInterfaces()
    • AsSelfWithInterfaces(Func predicate)
    • AsMatchingInterface()
    • AsMatchingInterface(Action? action)
    • As(Func> selector)
    • UsingAttributes()

    AsSelf 注册自身

    public class AsSelfService
    {
    }
    
    {
        builder.Services.Scan(typeSourceSelector =>
        {
            typeSourceSelector.FromEntryAssembly().AddClasses(iImplementationTypeFilter =>
            {
                iImplementationTypeFilter.InNamespaces("dotNetParadise_Scrutor.Application.AsSelf").WithoutAttribute();
            }).AsSelf();
        });
    
        Debug.Assert(builder.Services.Any(_ => _.ServiceType == typeof(AsSelfService)));
    }
    
    

    等效于builder.Services.AddTransient();


    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();
        }).As();
    });
        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();
            }).As(typeof(IAsSomeService), typeof(IAsOtherService));
        });
        List 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();
            }).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();
            }).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();
            }).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()]
    public class UsingAttributesService : IUsingAttributesService
    {
    }
    
        builder.Services.Scan(typeSourceSelector =>
        {
            typeSourceSelector.FromEntryAssembly().AddClasses(iImplementationTypeFilter =>
            {
                iImplementationTypeFilter.InNamespaces("dotNetParadise_Scrutor.Application.UsingAttributes").WithoutAttribute();
            }).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的出现就是为了避免我们在项目中不停地造轮子,达到开箱即用的目的。

    本文完整示例源代码

  • 相关阅读:
    Spring Cloud Alibaba微服务第22章之Oauth2
    Pytest系列- assert断言详细使用(4)
    PostCSS的使用
    SpringBoot
    LCT (link cut tree)动态树学习
    Vue3 学习笔记 —— 局部/全局组件、递归组件、动态组件、异步组件
    LVS-DR模式
    基于java+springboot+mybatis+vue+elementui的灯具购物商城网站
    API文档工具knife4j使用详解
    Elasticsearch面试题
  • 原文地址:https://www.cnblogs.com/ruipeng/p/18081965