.Net依赖注入神器Scrutor(上)
前言
从.Net Core 开始,.Net 平台内置了一个轻量,易用的 IOC 的框架,供我们在应用程序中使用,社区内还有很多强大的第三方的依赖注入框架如:
内置的依赖注入容器基本可以满足大多数应用的需求,除非你需要的特定功能不受它支持否则不建议使用第三方的容器。
我们今天介绍的主角Scrutor是内置依赖注入的一个强大的扩展,Scrutor有两个核心的功能:一是程序集的批量注入 Scanning,二是 Decoration 装饰器模式,今天的主题是Scanning。
学习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对象保存了到了ServiceTypeSelector的Types属性中供下一步选择。
除了提供程序集的方式外还可以直接提供类型的方式比如
创建接口和实现
public interface IForTypeService
{
}
public class ForTypeService : IForTypeService
{
}
builder.Services.Scan(typeSourceSelector =>
{
typeSourceSelector.FromTypes(typeof(ForTypeService));
});
这种方式提供类型内部会调用AddClass()方法把符合条件的参数保存到ServiceTypeSelector
第三步确定注册策略
在AddClass之后可以调用UsingRegistrationStrategy()配置注册策略是 Append,Skip,Throw,Replace
下面是各个模式的详细解释
- 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对象里面包含了程序集的所有类型集合 - 调用
IImplementationTypeSelector的AddClasses方法获取IServiceTypeSelector对象,AddClass这里面可以根据条件选择 过滤一些不需要的类型 - 调用
UsingRegistrationStrategy确定依赖注入的策略 是覆盖 还是跳过亦或是抛出异常 默认Append追加注入的方式 - 配置注册的场景 比如是
AsImplementedInterfaces还是AsSelf等 - 选择生命周期 默认
Transient
借助ServiceDescriptorAttribute更简单,生命周期和ServiceType都是在Attribute指定好的只需要确定选择程序集,调用UsingRegistrationStrategy配置依赖注入的策略然后UsingAttributes()即可
最后
本文从Scrutor的使用流程剖析了依赖注入批量注册的流程,更详细的教程可以参考Github 官方仓库。在开发过程中看到很多项目还有一个个手动注入的,也有自己写 Interface或者是Attribute反射注入的,支持的场景都十分有限,Scrutor的出现就是为了避免我们在项目中不停地造轮子,达到开箱即用的目的。
本文完整示例源代码
.Net依赖注入神器Scrutor(上)的更多相关文章
- 使用IDEA详解Spring中依赖注入的类型(上)
使用IDEA详解Spring中依赖注入的类型(上) 在Spring中实现IoC容器的方法是依赖注入,依赖注入的作用是在使用Spring框架创建对象时动态地将其所依赖的对象(例如属性值)注入Bean组件 ...
- <Pro .NET MVC4> 三大工具之依赖注入神器——Ninject
这篇内容是对<Pro .NET MVC4>一书中关于Ninject介绍的总结. Ninject是.NET MVC的一款开源的依赖注入工具. 使用场景:当MVC项目中使用了依赖注入技术来给程 ...
- Asp.Net Core 项目实战之权限管理系统(4) 依赖注入、仓储、服务的多项目分层实现
0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...
- WCF 依赖注入-- Attribute
最近,工作之余学习WCF(Windows Communication Fundation)时,感觉自己还有好多的东西需要学习呀⊙﹏⊙b汗,于是想记录下自己学习WCF的心得,以鼓励自己再接再厉,同时希望 ...
- Spring控制反转(IOC)和依赖注入(DI),再记不住就去出家!
每次看完spring的东西感觉都理解了,但是过了一段时间就忘,可能是不常用吧,也是没理解好,这次记下来. 拿ssh框架中的action,service,dao这三层举例: 控制反转:完成一个更新用户信 ...
- 细数Javascript技术栈中的四种依赖注入
作为面向对象编程中实现控制反转(Inversion of Control,下文称IoC)最常见的技术手段之一,依赖注入(Dependency Injection,下文称DI)可谓在OOP编程中大行其道 ...
- 轻松了解Spring中的控制反转和依赖注入(一)
我们回顾一下计算机的发展史,从最初第一台计算机的占地面积达170平方米,重达30吨,到现如今的个人笔记本,事物更加轻量功能却更加丰富,这是事物发展过程中的一个趋势,在技术领域中同样也是如此,企业级Ja ...
- yii2之依赖注入与依赖注入容器
一.为什么需要依赖注入 首先我们先不管什么是依赖注入,先来分析一下没有使用依赖注入会有什么样的结果.假设我们有一个gmail邮件服务类GMail,然后有另一个类User,User类需要使用发邮件的功能 ...
- 简单了解Spring的控制反转和依赖注入
浅谈控制反转(Inversion of Control,IOC) 我们首先先来了解一下控制二字,也就是在控制"正"转的情况下,在任何一个有请求作用的系统当中,至少需要有两个类互相配 ...
- PHP反射机制实现自动依赖注入
依赖注入又叫控制反转,使用过框架的人应该都不陌生.很多人一看名字就觉得是非常高大上的东西,就对它望而却步,今天抽空研究了下,解开他它的神秘面纱.废话不多说,直接上代码: /* * * * 工具类,使用 ...
随机推荐
- Hadoop相关面试题
1.简答说一下hadoop的map-reduce编程模型 首先map task会从本地文件系统读取数据,转换成key-value形式的键值对集合 使用的是hadoop内置的数据类型,比如longwri ...
- Python-pymysql操作MySQL数据库
一.安装pymysql py -m pip install pymysql; 二.pymysql数据库操作 1.简单示例 #coding=utf-8 import pymysql ## 打开数据库连接 ...
- [刺客伍六七&黑客] 魔刀千刃evilblade的使用手册与开源
0x00 前言 2023.8.15 夜里 非常欢迎使用我的魔刀千刃,并且欢迎各位师傅对我的开源代码进行指导! -–Offense without defense, unparalleled in th ...
- JOISC 2022 记录
Day1 T1 Jail 操作很类似华容道.由于这题是可以树,同时每一个人走的都是最短路,这也就意味着不会出现通过好多个人一起的挪动来匀出空间. 所以如果合法,必然存在一种方案是每一次直接将一个人挪到 ...
- 多层PCB的制造工艺流程
多层PCB的制造工艺流程 多层板制造方法有电镀通孔法以及高密度增层法两种,都是通过不同工艺的组合来实现电路板结构.其中目前采用最多的是电镀通孔法,电镀通孔法经过超过半个世纪的发展与完善,电镀通孔法无论 ...
- centos7使用repo方式安装zabbix4.0
1.安装zabbix的repo源 rpm -ivh https://mirrors.tuna.tsinghua.edu.cn/zabbix/zabbix/4.0/rhel/7/x86_64/zabbi ...
- spring事务的传播
目录 事务的传播行为类型 注意事项 关于事务的传播,我们先确定一个场景:方法A调用方法B,方法A可能存在事务,也可能不存在事务,我们这里重点关注方法B上定义的事务传播行为,以及方法B中出现异常时,方法 ...
- 用NavigationManager实现订单的列表和新增页面之间的导航
1.在订单列表页面放置"新增"按钮 2.注入NavigationManager,按钮被点击后导航到新增订单的路径 3.测试一下,能工作了. 4.订单新增后,返回订单列表页面 5.测 ...
- Javascript之Object、Array
Object.keys 对象的键转化为数组 Object.values 对象的属性值转化为数组 Object.assign 对象的合并 Array.from() 伪数组对象的属性值转化为数组.类似 ...
- C1. Good Subarrays (Easy Version)
思路:我们枚举每一个左端点,对于每一个左端点,寻找最长的满足条件的区间,这个区间长度就是左端点对答案的贡献,可以发现具有单调性,右端点只会前进不会倒退.所以我们两个指针各扫一遍区间就可以. #incl ...