.Net依赖注入神器Scrutor
.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<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
的出现就是为了避免我们在项目中不停地造轮子,达到开箱即用的目的。
本文完整示例源代码
2024-03-20 11:24:54【出处】:https://www.cnblogs.com/ruipeng/p/18081965
=======================================================================================
.Net依赖注入神器Scrutor(下)
前言
上一篇文章我们讲到了Scrutor
第一个核心功能Scanning
,本文讲解的是Scrutor
第二个核心的功能Decoration
装饰器模式在依赖注入中的使用。
- 装饰器模式允许您向现有服务类中添加新功能,而无需改变其结构
Install-Package Scrutor
本文的完整源代码在文末
Decoration 依赖注入代理模式
首先首先一个 获取 User 的服务
定义 User 类
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string? Email { get; set; }
}
定义接口和实现
public interface IUserService
{
List<User> GetAllUsers();
}
public class UserService : IUserService
{
public List<User> GetAllUsers()
{
Console.WriteLine("GetAllUsers方法被调用~");
List<User> users = [
new User(){
Id= 1,
Name="张三",
Age=18,
Email="zhangsan@163.com"
},
new User(){
Id= 2,
Name="李四",
Age=19,
Email="lisi@163.com"
},
];
return users!;
}
}
现在有了我们的获取全部用户的服务了,需求是在不破坏当前类的添加装饰器模式,为 GetAllUsers
接口添加缓存。
创建装饰器类
public class UserDecorationService(IUserService userService, IMemoryCache cache) : IUserService
{
public List<User> GetAllUsers()
{
Console.WriteLine("GetAllUsers代理方法被调用~");
return cache.GetOrCreate("allUser", cacheEntry =>
{
cacheEntry.SetAbsoluteExpiration(
TimeSpan.FromMinutes(5));
var allUsers = userService.GetAllUsers();
return allUsers ?? [];
})!;
}
}
DI 容器添加服务
builder.Services.AddTransient<IUserService, UserService>();
builder.Services.AddMemoryCache();
builder.Services.Decorate<IUserService, UserDecorationService>();
创建接口测试一下
app.MapGet("/GetAllUsers", ([FromServices] IUserService userService) => userService.GetAllUsers()).WithSummary("获取全部用户接口");
调用第一次
GetAllUsers代理方法被调用~
GetAllUsers方法被调用~
第二次调用
GetAllUsers代理方法被调用~
可以看出第一次没缓存装饰器类和我们 UserService 都调用了,第二次因为只有了缓存所以只调用了装饰器类,可以看出我们的装饰器模式生效了。
依赖注入装饰器底层核心实现
/// <summary>
/// Decorates all registered services using the specified <paramref name="strategy"/>.
/// </summary>
/// <param name="services">The services to add to.</param>
/// <param name="strategy">The strategy for decorating services.</param>
public static bool TryDecorate(this IServiceCollection services, DecorationStrategy strategy)
{
Preconditions.NotNull(services, nameof(services));
Preconditions.NotNull(strategy, nameof(strategy));
var decorated = false;
for (var i = services.Count - 1; i >= 0; i--)
{
var serviceDescriptor = services[i];
if (IsDecorated(serviceDescriptor) || !strategy.CanDecorate(serviceDescriptor))
{
continue;
}
var serviceKey = GetDecoratorKey(serviceDescriptor);
if (serviceKey is null)
{
return false;
}
// Insert decorated
services.Add(serviceDescriptor.WithServiceKey(serviceKey));
// Replace decorator
services[i] = serviceDescriptor.WithImplementationFactory(strategy.CreateDecorator(serviceDescriptor.ServiceType, serviceKey));
decorated = true;
}
return decorated;
}
这个代码是在 dotNet8
的环境下编译的,可以看出做了几件事:
第一 IServiceCollection
集合倒序遍历,找到符合条件的ServiceType
核心代码一
// Insert decorated
services.Add(serviceDescriptor.WithServiceKey(serviceKey));
将原先的ServiceDescription
作为基础,添加了ServiceKey
后再进行添加操作,新的服务描述符会被添加到服务集合的末尾,
核心代码二
// Replace decorator
services[i] = serviceDescriptor.WithImplementationFactory(strategy.CreateDecorator(serviceDescriptor.ServiceType, serviceKey));
这一步是将原有的服务描述符替换为一个新的服务描述符,新的服务描述符使用装饰器工厂方法创建,实现了服务的装饰功能。
用的时候
app.MapGet("/GetAllUsers", ([FromServices] IUserService userService) => userService.GetAllUsers()).WithSummary("获取全部用户接口");
这样就可以获取到装饰器类提供服务,之前看到services.Add(serviceDescriptor.WithServiceKey(serviceKey));
在代码的最后添加了一个服务,那 IOC 获取的时候肯定是从后面优先获取,这地方用了 dotNet8
的键控依赖注入(KeyedService
),以 ServiceType
获取服务只会获取到我们提供的装饰器实例,这一手简直是神来之笔 👍。
最后
Scrutor
的装饰器模式可以用于动态地给依赖注入的实例添加额外职责,实现动态增加和撤销功能,而无需改变原有对象结构。可以在不影响其他对象的情况下,以透明且动态的方式给对象添加新功能,实现系统的灵活扩展和维护。
本文完整源代码
2024-03-20 11:25:47【出处】:https://www.cnblogs.com/ruipeng/p/18084771
=======================================================================================
关注我】。(●'◡'●)
如果,您希望更容易地发现我的新博客,不妨点击一下绿色通道的【因为,我的写作热情也离不开您的肯定与支持,感谢您的阅读,我是【Jack_孟】!
本文来自博客园,作者:jack_Meng,转载请注明原文链接:https://www.cnblogs.com/mq0036/p/18084864
【免责声明】本文来自源于网络,如涉及版权或侵权问题,请及时联系我们,我们将第一时间删除或更改!