引言
.net core 为我们提供了良好的容器机制,并可以简单的在 Configuration 方法中向容器中注入服务,但是当服务比较多的时候,就会使我们的注入过程变得很麻烦,不容易管理,所以就需要我们自己动手写一些代码,实现服务的管理以及自动注入。
关于这篇博客所写的代码,我已经上传至Github,大家可以下载源码观看,如果觉得不错,顺手给个 Star 哦。
实现思路
要实现自动注入,需要做好以下几件事:
- 使容器可以自动发现需要注入的服务,并注册服务
- 服务需要标记自己需要的生命周期,方便容器注入
- 异常的处理
针对以上问题,我需要首先定义一个IServiceCollection
的扩展方法,可以在服务中使用我们的服务;其次,需要定义一个枚举,声明服务的生命周期;最后,我们需要定义一个属性,可以在服务类上使用,以便声明服务的声明周期等信息,使容器可以发现服务。
自动注入的实现
生命周期枚举类的定义
要定义生命周期枚举,首先需要了解一下.net 中的生命周期的机制,.net 中具有 3 种生命周期
AddTransient
,暂时性生存期;AddScoped
,范围内生存期;AddSingleton
,单例生命期。
关于生命周期方面的知识不属于这篇文章的重点,在此不多做赘述,不太熟悉的可以去微软文档查看。
根据 3 种生命周期,我们可以定义以下一个枚举类:
/// <summary>
/// 注入类型
/// </summary>
public enum InjectionType
{
/// <summary>
/// Transient
/// </summary>
Transient,
/// <summary>
/// Scoped
/// </summary>
Scoped,
/// <summary>
/// Singleton
/// </summary>
Singleton
}
自动注入属性的实现
实现自定义属性,主要是为了使用方便,这样的话我们在服务类的定义时,就可以标记这个服务是用来怎样进行服务注册的,废话不多说,上代码:
/// <summary>
/// 服务注入
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class ServiceInjectionAttribute : Attribute
{
/// <summary>
///
/// </summary>
public Type InterfaceType { get; set; }
/// <summary>
/// 注入类型
/// </summary>
public InjectionType InjectionType { get; }
/// <summary>
/// 服务注入
/// </summary>
public ServiceInjectionAttribute()
{
this.InjectionType = InjectionType.Scoped;
}
/// <summary>
/// 服务注入
/// </summary>
/// <param name="injectionType">注入类型</param>
public ServiceInjectionAttribute(InjectionType injectionType)
{
this.InjectionType = injectionType;
}
/// <summary>
/// 服务注入
/// </summary>
/// <param name="interfaceType">服务的接口类型</param>
/// <param name="injectionType">注入的类型</param>
public ServiceInjectionAttribute(Type interfaceType, InjectionType injectionType)
{
this.InterfaceType = interfaceType;
this.InjectionType = injectionType;
}
}
如代码所示,我们自定义的ServiceInjectionAttribute
类继承于 C#的Attribute
类,并且在上面标记了这个属性类的适用范围,只能标记在类上面。可以注意到,我们这个类有三种类型的构造函数。我们可以根据自己的使用需求来决定使用哪一种构造定义。
在我们的类中,有一个属性为InterfaceType
,这个属性是指服务继承的接口类,根据这个属性我们注册服务的时,会使用这个接口进行注入。
在这里面,我们定义了默认的生命周期为Scoped
,如果没有定义继承的服务接口,则使用具体服务类所继承的第一个接口进行注册
服务扩展方法的实现
这部份是实现自动注入的核心,它负责容器的组装,自动扫描我们需要注入的服务,代码如下:
/// <summary>
/// 服务自动注入
/// </summary>
/// <param name="serviceCollection">需要自动注入服务的服务集合</param>
/// <exception cref="ArgumentOutOfRangeException">指定的注入类型不在可注入的范围内</exception>
/// <exception cref="NoImplementationInterfaceException">指定注入的类型未实现任何服务</exception>
/// <exception cref="ArgumentException">输入的参数错误:1、注入的类型未实现指定的服务。2、指定的服务不是Interface类型</exception>
/// <returns>自动注入服务后的服务集合</returns>
public static IServiceCollection ServicesAutoInjection(this IServiceCollection serviceCollection)
{
var directory = AppDomain.CurrentDomain.BaseDirectory;
var types = Directory.GetFiles(directory, "*.dll", SearchOption.TopDirectoryOnly)
.Select(Assembly.LoadFrom)
.SelectMany(a => a.GetTypes());
Injection(serviceCollection, types);
return serviceCollection;
}
在这个方法中我们可以看出,程序会扫描程序运行目录下的所有 Dll,并加载具有我们自定义属性的类,并根据我们标记的生命周期等属性,注入到我们的容器中。
但是,有时候,有一些 Dll 不需要我们扫描注入,需要进行排除,所以在这个基础上,我又封装了一层,可以传入过滤条件,代码如下:
/// <summary>
/// 服务自动注入
/// </summary>
/// <param name="serviceCollection">需要自动注入服务的服务集合</param>
/// <param name="selector">应用于每个Assembly的筛选函数</param>
/// <exception cref="ArgumentOutOfRangeException">指定的注入类型不在可注入的范围内</exception>
/// <exception cref="NoImplementationInterfaceException">指定注入的类型未实现任何服务</exception>
/// <exception cref="ArgumentException">输入的参数错误:1、注入的类型未实现指定的服务。2、指定的服务不是Interface类型</exception>
/// <returns>自动注入服务后的服务集合</returns>
public static IServiceCollection ServicesAutoInjection(this IServiceCollection serviceCollection, Func<Assembly, bool> selector)
{
var directory = AppDomain.CurrentDomain.BaseDirectory;
var types = Directory.GetFiles(directory, "*.dll", SearchOption.TopDirectoryOnly)
.Select(Assembly.LoadFrom)
.Where(selector)
.SelectMany(a => a.GetTypes());
Injection(serviceCollection, types);
return serviceCollection;
}
上述代码中,selector
就是我们的过滤条件,它是一个 Func 函数,参数为Assembly
类型,我们可以根据类型或者一些信息排除我们不需要的类型,比如根据类的名称等信息。
自动注入的使用
服务的配置
首先需要我们在Startup
中的ConfigureServices
函数中配置服务,如下所示:
public void ConfigureServices(IServiceCollection services)
{
// 声明为WebAPI,也可为AddMvc()或者其他
services.AddControllers();
// 配置其他服务
// 配置自动注入服务
services.ServicesAutoInjection();
// 配置其他服务
}
标记服务的注入
我们的服务的配置后,接下来的使用就比较简单了,找到我们的实现类,标记上我们自定义的属性标签,然后服务就可以通过我们的服务自动注入啦。
[ServiceInjection(typeof(IService),InjectionType.Scoped)]
public class ServiceImpl : IService
{
// 业务代码
}
结语
通过我们自定义代码实现,服务就可以很简单的被容器发现并注册了,并且还做了简单的服务排除逻辑,如果代码中有什么出错的地方,欢迎大家批评指正哦。
码字不易,请顺手给个 Star 吧