提供服务注册描述信息来简化服务注册方式

  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,大家可以下载查看

  

 

  

posted @ 2017-03-30 14:01  杜现鹏  阅读(895)  评论(1编辑  收藏  举报