提供服务注册描述信息来简化服务注册方式
asp.net core提供了依赖注入的支持,我们可以在Startup.ConfigureServices方法中注册系统所需要的服务映射关系,如services.TryAddScoped<TInterface, TImp>(),通过这样的方式可以完成一个服务注册,并在代码中可以通过注入的方式获取到TImp的实例。如果系统里需要的服务比较多,一个一个这样去注册,显然不是特别好的方法,如何简化注册的过程呢?我们可以制定一个规则,比如给服务定义增加特性描述信息,标识当前是一个需要注册的服务,然后通过反射解析方式获取到信息,并完成服务注册。或者把服务注册信息写到文件中,解析文件完成相同的过程。
首先我们先定义一个能够描述服务注册信息的一个类,定义如下:
public class ServiceRegisteDescriptor { public Type ServiceType { get; set; }//服务类型,可以是接口,也可以是一个类 public ServiceLifetime LifeTime { get; set; }//服务的生命周期类型 public bool AllowMultipleImp { get; set; }//该服务器是否允许多个实现 public Type Imp { get; set; }//指定特定的实现 public Type[] GenericParameterTypes { get; set; }//如果该服务是一个泛型类型,可以指定注册的泛型参数具体类型是什么 }
描述信息类定义好了,下面我们需要提供获取ServiceRegisteDescriptor集合的方法,我们这里先定义一个IServiceRegisteDescriptorCollectionProvider接口,定义如下:
public interface IServiceRegisteDescriptorCollectionProvider { ServiceRegisteDescriptorCollection ServiceRegisteDescriptors { get; } }
这个接口包含一个ServiceRegisteDescriptors属性,它是一个ServiceRegisteDescriptorCollection类型,从这个名字上可以看出,ServiceRegisteDescriptorCollection表示的就是ServiceRegisteDescriptor集合,具体定义如下:
public class ServiceRegisteDescriptorCollection { public ServiceRegisteDescriptorCollection(IReadOnlyList<ServiceRegisteDescriptor> items) { Items = items ?? throw new ArgumentNullException(nameof(items)); } public IReadOnlyList<ServiceRegisteDescriptor> Items { get; private set; } }
接口定义好了,那如何去实现它?我们前面提到,服务注册信息源可以是程序集反射信息,也可以是文件内容,甚至可以是数据库中的数据等等,为了满足信息源多样性的支持以及方便扩展的需求,我们抽象一个IServiceRegisteDescriptorProvider接口,定义如下:
public interface IServiceRegisteDescriptorProvider { /// <summary> /// 排序号,建议自定义的Provider,order从1开始,系统默认提供的Provider是0 /// </summary> int Order { get; } /// <summary> /// 从特定目标获取服务注册描述信息,放到ServiceRegisteDescriptorProviderContext中 /// </summary> /// <param name="context"></param> void OnProvidersExecuting(ServiceRegisteDescriptorProviderContext context); /// <summary> /// 在该方法中可以对收集好的ServiceRegisteDescriptor集合进行修改,比如删除,替换等 /// </summary> /// <param name="context"></param> void OnProvidersExecuted(ServiceRegisteDescriptorProviderContext context); }
这个接口的作用就是从特定目标获取信息,并转换成ServiceRegisteDescritor信息,放到我们一个ServiceRegisteDescriptorProviderContext中进行汇总,并且可以在OnProvidersExecuted中对结果进行修改。我们下面先实现一个从程序集中获取信息。从程序集中获取信息可以采用反射的方式,我们可以定义一个特性,反射的时候获取包含该特性的类型定义列表,然后根据特性提供的数据进行类型分析,最后得到一个服务映射列表。
实现方式说完了,下面就是具体实现了,那就先定义一个特性,定义如下:
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Interface,AllowMultiple =true,Inherited =false)] public class ServiceRegisteDescriptorAttribute:Attribute { public ServiceRegisteDescriptorAttribute(ServiceLifetime lifetime) { LifeTime = lifetime; } public ServiceLifetime LifeTime { get; } public bool AllowMultipleImp { get; set; } public Type Imp { get; set; } public Type GenericType { get; set; } }
这个特性的定义跟ServiceRegisteDescriptor很类似,唯一不包含ServiceType属性,其他属性含义跟ServiceRegisteDescriptor一致。这个特性可以应用到类,接口上。有了特性定义,我们可以在需要完成注册的类型定义上增加该特性,比如:
[ServiceRegisteDescriptor(Microsoft.Extensions.DependencyInjection.ServiceLifetime.Singleton)] public interface IServiceTest { void TestM(); }
这表示IServiceTest是一个需要注册的服务,我们从当前程序所包含的程序集中,获取到某个实现了该接口的类,建议一个服务映射关系。下面就是IServiceRegisteDescriptorProvider具体实现了。定义一个类DefaultServiceRegisterDescriptorProvider,实现IServiceRegisteDescriptorProvider接口,如下:
public class DefaultServiceRegisterDescriptorProvider : IServiceRegisteDescriptorProvider {}
核心是OnProvidersExecuting方法,在这个方法中实现我们刚才说的反射解析过程,具体实现代码:
public void OnProvidersExecuting(ServiceRegisteDescriptorProviderContext context) { //获取当前运行程序所有的程序集集合,一会再说AssemblyDisconvery实现 Assembly[] assemblys = AssemblyDiscovery.Discovery(); //得到所有包含ServiceRegisteDescriptorAttribute特性的类型集合 IEnumerable<Type> types = assemblys.SelectMany(m => m.GetTypes().Where(t => t.GetTypeInfo().GetCustomAttributes().Any(a => a.GetType() == typeof(ServiceRegisteDescriptorAttribute)))).ToList(); foreach (var type in types) { TypeInfo typeInfo = type.GetTypeInfo(); //获取当前类型的ServiceRegisteDescriptorAttribute特性对象 ServiceRegisteDescriptorAttribute attr = type.GetTypeInfo().GetCustomAttributes().FirstOrDefault(m => m.GetType() == typeof(ServiceRegisteDescriptorAttribute)) as ServiceRegisteDescriptorAttribute; //如果当前类型是一个泛型类型,必须执行GenericType if (typeInfo.IsGenericTypeDefinition && attr.GenericType == null) { throw new NotSupportedException(nameof(attr)); } //下面的过程是获取当前服务的实现类集合 Type[] impTypes = null; if ((typeInfo.IsInterface || typeInfo.IsAbstract) && attr.Imp == null) { //从程序集中获取所有实现了该服务端类 if (typeInfo.IsGenericTypeDefinition && typeInfo.IsInterface) { impTypes = assemblys.SelectMany(m => m.GetTypes().Where(t => t.GetTypeInfo().IsClass && !t.GetTypeInfo().IsAbstract && t.GetTypeInfo().GetInterfaces().Any(i => i.GetTypeInfo().IsGenericType && i.GetGenericTypeDefinition() == type))).ToArray(); } else { if (typeInfo.IsInterface) { impTypes = assemblys.SelectMany(m => m.GetTypes().Where(t => t.GetTypeInfo().IsClass && !t.GetTypeInfo().IsAbstract && t.GetTypeInfo().GetInterfaces().Any(i => i == type))).ToArray(); } else { impTypes = assemblys.SelectMany(m => m.GetTypes().Where(t => t.GetTypeInfo().IsClass && !t.GetTypeInfo().IsAbstract && t.GetTypeInfo().IsSubclassOf(type))).ToArray(); } } } else if (attr.Imp!=null) { impTypes = new Type[1] { attr.Imp }; } else { impTypes = new Type[1] { type }; } //建立映射关系 foreach (var imp in impTypes) { ServiceRegisteDescriptor d=new ServiceRegisteDescriptor { AllowMultipleImp = attr.AllowMultipleImp, Imp = imp, ServiceType = type, LifeTime = attr.LifeTime }; //如果是泛型类型,获取所有类型为attr.GenericType的类型集合,包含所有子孙类型 if (typeInfo.IsGenericType) { d.GenericParameterTypes = assemblys.SelectMany(m => m.GetTypes().Where(t => !t.GetTypeInfo().IsAbstract && (t.GetTypeInfo().IsSubclassOf(attr.GenericType) || t == attr.GenericType))).ToArray(); } context.Results.Add(d); } } }
通过上面的方式,我们就能从程序集中获取所有的服务定义信息。前面使用了一个AssemblyDisconery获取程序集集合,它的实现如下:
public class AssemblyDiscovery { public static Assembly[] Discovery() { return DependencyContext.Default.RuntimeLibraries.SelectMany(l => l.GetDefaultAssemblyNames(DependencyContext.Default)).Select(Assembly.Load).ToArray(); } }
AssemblyDiscovery使用DependencyContext来获取程序集集合。
上面是实现了从程序集中获取服务注册描述信息,如果源是文件呢?我们可以以xml或者json格式保存ServiceRegisteDescriptor配置集合,然后从文件中读取内容并按照特定格式解析,按照上面的分析过程,最终可以得到我们所需要的ServiceRegisteDescriptorCollection,如果源是数据库也一样,这些实现代码就不再提供了。
有了IServiceRegisteDescriptorProvider,我们可以调用所有IServiceRegisteDescriptorProvider对象的方法,完成信息收集,下面我们就实现IServiceRegisteDescriptorCollectionProvider,代码如下:
public class ServiceRegisteDescriptorCollectionProvider : IServiceRegisteDescriptorCollectionProvider { private readonly IServiceRegisteDescriptorProvider[] _serviceRegisteDescriptorProviders; private ServiceRegisteDescriptorCollection _collection; public ServiceRegisteDescriptorCollectionProvider( IEnumerable<IServiceRegisteDescriptorProvider> serviceRegisteDescriptorProviders) { _serviceRegisteDescriptorProviders = serviceRegisteDescriptorProviders .OrderBy(p => p.Order) .ToArray(); } private void UpdateCollection() { var context = new ServiceRegisteDescriptorProviderContext(); //循环所有的Provider完成解析 for (var i = 0; i < _serviceRegisteDescriptorProviders.Length; i++) { _serviceRegisteDescriptorProviders[i].OnProvidersExecuting(context); } //这里完成ServiceRegisteDescriptor集合的修改 for (var i = _serviceRegisteDescriptorProviders.Length - 1; i >= 0; i--) { _serviceRegisteDescriptorProviders[i].OnProvidersExecuted(context); } //生成集合 _collection = new ServiceRegisteDescriptorCollection( new ReadOnlyCollection<ServiceRegisteDescriptor>(context.Results)); } public ServiceRegisteDescriptorCollection ServiceRegisteDescriptors { get { if (_collection == null) { UpdateCollection(); } return _collection; } }
完成ServiceRegisteDescriptorCollection收集之后,下面就是注册到ServiceCollection中了,我们直接扩展IServiceCollection对象,代码如下:
public static class ServiceScanServiceCollectionExtensions { public static IServiceCollection AddScanServices(this IServiceCollection services) { return AddScanServices(services,null); } public static IServiceCollection AddScanServices(this IServiceCollection services,Action<ServiceScanOptions> options) { //提供自定义IServiceRegisteDescriptorProvider扩展的配置入口 ServiceScanOptions option = new ServiceScanOptions(); option.DescriptorProviderTypes.Add(new DefaultServiceRegisterDescriptorProvider()); options?.Invoke(option); //获取集合 IServiceRegisteDescriptorCollectionProvider provider = new ServiceRegisteDescriptorCollectionProvider(option.DescriptorProviderTypes); ServiceRegisteDescriptorCollection collection = provider.ServiceRegisteDescriptors; foreach (var item in collection.Items) { //完成注册 ServiceRegister.Registe(services,item); } return services; }
我们一个问题一个问题介绍,首先是ServiceScanOptions,这个是为了自定义IServiceRegisteDescriptorProvider扩展配置提供的类,代码如下:
public class ServiceScanOptions { public IList<IServiceRegisteDescriptorProvider> DescriptorProviderTypes { get; } = new List<IServiceRegisteDescriptorProvider>(); }
通过上面的代码我们可以看出,我们通过调用IServiceCollectoin.AddScanServices方法时,利用Action<ServiceScanOptions>委托,可以把自定义的扩展加入到ServiceScanOptions.DescriptorProviderTypes中,系统默认提供的是DefaultServiceRegisterDescriptorProvider。
ServiceRegister.Registe方法是根据ServiceRegisteDescriptor把服务注册到IServiceCollectoin中,实现如下:
public class ServiceRegister { private static void RegisteItem(IServiceCollection services,ServiceLifetime lifeTime,Type serviceType,Type impType,bool allowMultipleImp) { ServiceDescriptor serviceDescriptor = null; switch (lifeTime) { case ServiceLifetime.Singleton: serviceDescriptor = ServiceDescriptor.Singleton(serviceType, impType); break; case ServiceLifetime.Scoped: serviceDescriptor = ServiceDescriptor.Scoped(serviceType, impType); break; case ServiceLifetime.Transient: serviceDescriptor = ServiceDescriptor.Transient(serviceType, impType); break; } if (allowMultipleImp) { services.TryAddEnumerable(serviceDescriptor); } else { services.TryAdd(serviceDescriptor); } } public static void Registe(IServiceCollection services,ServiceRegisteDescriptor descriptor) { //判断是否是泛型接口 if (descriptor.ServiceType.GetTypeInfo().IsGenericType) { if (descriptor.GenericParameterTypes == null) { throw new NullReferenceException(nameof(descriptor.GenericParameterTypes)); } if (!descriptor.Imp.GetTypeInfo().IsGenericType) { throw new NotSupportedException(nameof(descriptor.Imp)); } //根据泛型类型注册服务 foreach (var item in descriptor.GenericParameterTypes) { RegisteItem(services,descriptor.LifeTime, descriptor.ServiceType.MakeGenericType(item), descriptor.Imp.MakeGenericType(item), descriptor.AllowMultipleImp); } } else { RegisteItem(services, descriptor.LifeTime, descriptor.ServiceType, descriptor.Imp,descriptor.AllowMultipleImp); } } }
这里重点说下泛型的规则,比如定义了一个泛型接口如下:
[ServiceRegisteDescriptor(Microsoft.Extensions.DependencyInjection.ServiceLifetime.Scoped,AllowMultipleImp =true,GenericType =typeof(EntityTest))] public interface IGenericTest<T> { }
ServiceRegisteDescriptor表示当前是一个需要注册的服务,然后泛型参数类型为EntityTest,意思就是说所有EntityTest类型(包括所有子孙类型)都要注册一个IGnericTest<>服务,如下映射关系:
IGenericTest<EntityTest> --> GenericTest<EntityTest>
IGenericTest<EntityTest1> --> GenericTest<EntityTest1> EntityTest1派生自EntityTest
到这里就介绍完了,具体使用方法:
1. 引入DepencencyInjectionScan库:Install-Package Microsoft.Extensions.DependencyInjection.Scan
2. 使用IServiceCollection.AddScanServices()完成服务注册
3. 自定义IServiceRegisteDescriptorProvider注册方法:IServiceCollection.AddScanServices(options=>{options.DescriptorProviderTypes.Add(对象);});
完整的实现代码也放到了github上,地址是:https://github.com/dxp909/DependencyInjectionScan.git,大家可以下载查看