zoukankan      html  css  js  c++  java
  • 提供服务注册描述信息来简化服务注册方式

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

      

      

  • 相关阅读:
    【转】strlen和mb_strlen区别(php获得中英文混合字符长度)
    PHP字符串替换的相关方法介绍
    php表单转换textarea换行符的方法
    vue生命周期及其作用
    elemenui点击单行触发样式,选中或不选中复选框
    flutter 介绍和环境搭建
    flutter组件
    tora消息机制(事件监听,触发,取消)
    Promise功能与应用
    CCF CSP 20018031 跳一跳
  • 原文地址:https://www.cnblogs.com/dxp909/p/6635531.html
Copyright © 2011-2022 走看看