• ASP.NET Web API 框架研究 服务容器 ServicesContainer


      ServicesContainer是一个服务的容器,可以理解为—个轻量级的IoC容器,其维护着一个服务接口类型与服务实例之间的映射关系,可以根据服务接口类型获取对应的服务实例。构成ASP.NET Web API核心框架的消息处理管道的每个环节都注册了相应的组件来完成某项独立的任务,这些 “标准化 ”的组件—般都实现了某个预定义的接口,如果这些原生的组件不能满足我们的需求,我们完全可以通过实现相应的接口创建自定义的组件,然后以某种形式将它们 “注册安装 ”到消息处理管道上。

      ASP.NET Web API的 配置都是通过HttpConfiguration来完成的,自定义 “服务实例 ”的注册也是由它来完成,有一个类 型为 ServicesContainer的只读属性Sewice,默认使用的 ServicesContainer是 一个类型为 Defaultservices的对象.

    一、涉及的类和源码分析

     

    1、ServicesContainer 

      抽象类,用IEnumerable<object>或List<object>及其子类存放服务实例集合,具体用哪种类型由其子类来决定。提供了对服务类型的服务实例的添加,删除,查询,覆盖,查找,还有可以标记是否是单服务实例,如GetService,GetServices,Add,FindIndex,Replace等方法,公开了几个抽象接口,如下边的抽象方法,由子类实现。

    public abstract class ServicesContainer : IDisposable
        {
            internal readonly Lazy<IExceptionLogger> ExceptionServicesLogger;
            internal readonly Lazy<IExceptionHandler> ExceptionServicesHandler;
    
            protected ServicesContainer()
            {
                ExceptionServicesLogger = new Lazy<IExceptionLogger>(CreateExceptionServicesLogger);
                ExceptionServicesHandler = new Lazy<IExceptionHandler>(CreateExceptionServicesHandler);
            }
            //抽象方法,获取某个接口类型对应的实例类型
            public abstract object GetService(Type serviceType);
    
            //抽象方法,获取某个接口类型的对应的多个实例类型
            public abstract IEnumerable<object> GetServices(Type serviceType);
    
            //抽象方法,获取某个接口类型的对应的多个实例类型
            protected abstract List<object> GetServiceInstances(Type serviceType);
            //虚方法清空某个服务类型的缓存
            protected virtual void ResetCache(Type serviceType)
            {
            }
    
          
            //服务类型是单实例还是多实例
            public abstract bool IsSingleService(Type serviceType);
    
            //往某个服务接口的服务实例列表末尾添加一个服务实例
            public void Add(Type serviceType, object service)
            {
                Insert(serviceType, Int32.MaxValue, service);
            }
    
            //往某个服务接口的服务实例列表末尾添加多个服务实例
            public void AddRange(Type serviceType, IEnumerable<object> services)
            {
                InsertRange(serviceType, Int32.MaxValue, services);
            }
    
            
            //移除服务类型的所有服务实例,分单实例和多实例情况
            public virtual void Clear(Type serviceType)
            {
                if (serviceType == null)
                {
                    throw Error.ArgumentNull("serviceType");
                }
    
                if (IsSingleService(serviceType))
                {
                    ClearSingle(serviceType);
                }
                else
                {
                    ClearMultiple(serviceType);
                }
                ResetCache(serviceType);
            }
    
            //抽象方法,清除单实例
            protected abstract void ClearSingle(Type serviceType);
    
            //清除多实例
            protected virtual void ClearMultiple(Type serviceType)
            {
                List<object> instances = GetServiceInstances(serviceType);
                instances.Clear();
            }
    
          
            //根据匹配的委托查找符合条件的第一个索引位置(从0开始),找不到就返回-1
            public int FindIndex(Type serviceType, Predicate<object> match)
            {
                if (serviceType == null)
                {
                    throw Error.ArgumentNull("serviceType");
                }
                if (match == null)
                {
                    throw Error.ArgumentNull("match");
                }
    
                List<object> instances = GetServiceInstances(serviceType);
                return instances.FindIndex(match);
            }
    
           
            //在服务类型的服务实例集合的指定索引位置(0开始)插入一个服务实例 
            public void Insert(Type serviceType, int index, object service)
            {
                if (serviceType == null)
                {
                    throw Error.ArgumentNull("serviceType");
                }
                if (service == null)
                {
                    throw Error.ArgumentNull("service");
                }
                if (!serviceType.IsAssignableFrom(service.GetType()))
                {
                    throw Error.Argument("service", SRResources.Common_TypeMustDriveFromType, service.GetType().Name, serviceType.Name);
                }
    
                List<object> instances = GetServiceInstances(serviceType);
                if (index == Int32.MaxValue)
                {
                    index = instances.Count;
                }
    
                instances.Insert(index, service);
    
                ResetCache(serviceType);
            }
    
            //在服务类型的服务实例集合的指定索引位置(0开始)插入多个服务实例 
            public void InsertRange(Type serviceType, int index, IEnumerable<object> services)
            {
                if (serviceType == null)
                {
                    throw Error.ArgumentNull("serviceType");
                }
                if (services == null)
                {
                    throw Error.ArgumentNull("services");
                }
    
                object[] filteredServices = services.Where(svc => svc != null).ToArray();
                object incorrectlyTypedService = filteredServices.FirstOrDefault(svc => !serviceType.IsAssignableFrom(svc.GetType()));
                if (incorrectlyTypedService != null)
                {
                    throw Error.Argument("services", SRResources.Common_TypeMustDriveFromType, incorrectlyTypedService.GetType().Name, serviceType.Name);
                }
    
                List<object> instances = GetServiceInstances(serviceType);
                if (index == Int32.MaxValue)
                {
                    index = instances.Count;
                }
    
                instances.InsertRange(index, filteredServices);
    
                ResetCache(serviceType);
            }
    
           
            //从服务类型的服务实例列表中移除指定的服务实例
            public bool Remove(Type serviceType, object service)
            {
                if (serviceType == null)
                {
                    throw Error.ArgumentNull("serviceType");
                }
                if (service == null)
                {
                    throw Error.ArgumentNull("service");
                }
    
                List<object> instances = GetServiceInstances(serviceType);
                bool result = instances.Remove(service);
    
                ResetCache(serviceType);
    
                return result;
            }
    
           
            //移除某个服务类型的所有委托匹配的服务实例元素
            public int RemoveAll(Type serviceType, Predicate<object> match)
            {
                if (serviceType == null)
                {
                    throw Error.ArgumentNull("serviceType");
                }
                if (match == null)
                {
                    throw Error.ArgumentNull("match");
                }
    
                List<object> instances = GetServiceInstances(serviceType);
                int result = instances.RemoveAll(match);
    
                ResetCache(serviceType);
    
                return result;
            }
    
           
            //移除某个服务类型的服务实例集合的指定索引的服务实例
            public void RemoveAt(Type serviceType, int index)
            {
                if (serviceType == null)
                {
                    throw Error.ArgumentNull("serviceType");
                }
    
                List<object> instances = GetServiceInstances(serviceType);
                instances.RemoveAt(index);
    
                ResetCache(serviceType);
            }
    
          
            //用某个服务实例替换某个服务类型所有的服务实例
            public void Replace(Type serviceType, object service)
            {
                // 更早的空检查,不需要在插入之前就调用RemoveAll,而导致空服务异常
                if (serviceType == null)
                {
                    throw Error.ArgumentNull("serviceType");
                }
    
                if ((service != null) && (!serviceType.IsAssignableFrom(service.GetType())))
                {
                    throw Error.Argument("service", SRResources.Common_TypeMustDriveFromType, service.GetType().Name, serviceType.Name);
                }
    
                if (IsSingleService(serviceType))
                {
                    ReplaceSingle(serviceType, service);
                }
                else
                {
                    ReplaceMultiple(serviceType, service);
                }
                ResetCache(serviceType);
            }
            //抽象方法,替换单个实例
            protected abstract void ReplaceSingle(Type serviceType, object service);
    
            protected virtual void ReplaceMultiple(Type serviceType, object service)
            {
                //先移除所有,再在头部插入
                RemoveAll(serviceType, _ => true);
                Insert(serviceType, 0, service);
            }
    
            
            //用多个服务实例替换某个服务类型所有的服务实例
            public void ReplaceRange(Type serviceType, IEnumerable<object> services)
            {
                if (services == null)
                {
                    throw Error.ArgumentNull("services");
                }
    
                RemoveAll(serviceType, _ => true);
                InsertRange(serviceType, 0, services);
            }
            //资源回收空实现,子类重写
            public virtual void Dispose()
            {
            }
    
            private IExceptionLogger CreateExceptionServicesLogger()
            {
                return ExceptionServices.CreateLogger(this);
            }
    
            private IExceptionHandler CreateExceptionServicesHandler()
            {
                return ExceptionServices.CreateHandler(this);
            }
        }

     2、DefaultServices 

      继承自ServicesContainer,是默认实现,在HttpConfiguration里指定,构造函数中注册一些默认的实例,注意点:

    • 使用到了IDependencyResolver进行DI操作获取实例
    • 使用了缓存提高性能,ConcurrentDictionary

      另外,两个核心方法逻辑  

    • GetService,重写方法,根据服务类型获取单个服务实例,缓存里有就从缓存里,否则把DI解析,如果有就返回,否则从字典里获取返回,并添加进缓存
    • GetServices,重写方法,根据服务类型获取服务实例列表,缓存里有就从缓存里,否则把DI解析的和字典里的一起返回,,并添加进缓存
     public class DefaultServices : ServicesContainer
        {
            //缓存
            private ConcurrentDictionary<Type, object[]> _cacheMulti = new ConcurrentDictionary<Type, object[]>();
            private ConcurrentDictionary<Type, object> _cacheSingle = new ConcurrentDictionary<Type, object>();
            private readonly HttpConfiguration _configuration;
    
            //服务实例存放集合
            private readonly Dictionary<Type, object> _defaultServicesSingle = new Dictionary<Type, object>();
            private readonly Dictionary<Type, List<object>> _defaultServicesMulti = new Dictionary<Type, List<object>>();
            //通过它来
            private IDependencyResolver _lastKnownDependencyResolver;
            private readonly HashSet<Type> _serviceTypesSingle;
            private readonly HashSet<Type> _serviceTypesMulti;
    
            /// <summary>
            /// This constructor is for unit testing purposes only.
            /// </summary>
            protected DefaultServices()
            {
            }
    
            private void SetSingle<T>(T instance) where T : class
            {
                _defaultServicesSingle[typeof(T)] = instance;
            }
            private void SetMultiple<T>(params T[] instances) where T : class
            {
                var x = (IEnumerable<object>)instances;
                _defaultServicesMulti[typeof(T)] = new List<object>(x);
            }
    
            public DefaultServices(HttpConfiguration configuration)
            {
                if (configuration == null)
                {
                    throw Error.ArgumentNull("configuration");
                }
    
                _configuration = configuration;
    
                //初始化注册服务类型的服务实例,即使是空的,获得空会抛异常
    
                SetSingle<IActionValueBinder>(new DefaultActionValueBinder());
                SetSingle<IApiExplorer>(new ApiExplorer(configuration));
                SetSingle<IAssembliesResolver>(new DefaultAssembliesResolver());
                SetSingle<IBodyModelValidator>(new DefaultBodyModelValidator());
                SetSingle<IContentNegotiator>(new DefaultContentNegotiator());
                SetSingle<IDocumentationProvider>(null); // Missing
    
                SetMultiple<IFilterProvider>(new ConfigurationFilterProvider(),
                                          new ActionDescriptorFilterProvider());
    
                SetSingle<IHostBufferPolicySelector>(null);
                SetSingle<IHttpActionInvoker>(new ApiControllerActionInvoker());
                SetSingle<IHttpActionSelector>(new ApiControllerActionSelector());
                SetSingle<IHttpControllerActivator>(new DefaultHttpControllerActivator());
                SetSingle<IHttpControllerSelector>(new DefaultHttpControllerSelector(configuration));
                SetSingle<IHttpControllerTypeResolver>(new DefaultHttpControllerTypeResolver());
                SetSingle<ITraceManager>(new TraceManager());
                SetSingle<ITraceWriter>(null);
    
                // This is a priority list. So put the most common binders at the top. 
                SetMultiple<ModelBinderProvider>(new TypeConverterModelBinderProvider(),
                                            new TypeMatchModelBinderProvider(),
                                            new KeyValuePairModelBinderProvider(),
                                            new ComplexModelDtoModelBinderProvider(),
                                            new ArrayModelBinderProvider(),
                                            new DictionaryModelBinderProvider(),
                                            new CollectionModelBinderProvider(),
                                            new MutableObjectModelBinderProvider());
                SetSingle<ModelMetadataProvider>(new DataAnnotationsModelMetadataProvider());
                SetMultiple<ModelValidatorProvider>(new DataAnnotationsModelValidatorProvider(),
                                            new DataMemberModelValidatorProvider());
    
                // This is an ordered list,so put the most common providers at the top. 
                SetMultiple<ValueProviderFactory>(new QueryStringValueProviderFactory(),
                                               new RouteDataValueProviderFactory());
    
                ModelValidatorCache validatorCache = new ModelValidatorCache(new Lazy<IEnumerable<ModelValidatorProvider>>(() => this.GetModelValidatorProviders()));
                SetSingle<IModelValidatorCache>(validatorCache);
    
                SetSingle<IExceptionHandler>(new DefaultExceptionHandler());
                SetMultiple<IExceptionLogger>();
    
                _serviceTypesSingle = new HashSet<Type>(_defaultServicesSingle.Keys);
                _serviceTypesMulti = new HashSet<Type>(_defaultServicesMulti.Keys);
    
                // Reset the caches and the known dependency scope
                ResetCache();
            }
    
            public override bool IsSingleService(Type serviceType)
            {
                if (serviceType == null)
                {
                    throw Error.ArgumentNull("serviceType");
                }
                return _serviceTypesSingle.Contains(serviceType);
            }
    
            //重写方法,根据服务类型获取单个服务实例,缓存里有就从缓存里,否则把DI解析,如果有就返回,否则从字典里获取返回,并添加进缓存
            public override object GetService(Type serviceType)
            {
                // 为空直接抛出异常,提高性能
                if (serviceType == null)
                {
                    throw Error.ArgumentNull("serviceType");
                }
    
                // 如果DependencyResolver改变了,缓存里解析的变为无效,要清空
                if (_lastKnownDependencyResolver != _configuration.DependencyResolver)
                {
                    ResetCache();
                }
    
                object result;
                //在缓存里直接返回
                if (_cacheSingle.TryGetValue(serviceType, out result))
                {
                    return result;
                }
    
                // Check input after initial read attempt for performance.
                if (!_serviceTypesSingle.Contains(serviceType))
                {
                    throw Error.Argument("serviceType", SRResources.DefaultServices_InvalidServiceType, serviceType.Name);
                }
    
                
                //从DI获取服务实例,为了不实例化多次,放到缓存
                object dependencyService = _lastKnownDependencyResolver.GetService(serviceType);
                //不在缓存里(肯定不在),添加到缓存,并设置结果
                if (!_cacheSingle.TryGetValue(serviceType, out result))
                {
                    //如果DI获取到,就用它,否则就从字典中获取
                    result = dependencyService ?? _defaultServicesSingle[serviceType];
                    _cacheSingle.TryAdd(serviceType, result);
                }
    
                return result;
            }
    
    
            //重写方法,根据服务类型获取服务实例列表,缓存里有就从缓存里,否则把DI解析的和字典里的一起返回,,并添加进缓存
            public override IEnumerable<object> GetServices(Type serviceType)
            {
                // 为空直接抛出异常,提高性能
                if (serviceType == null)
                {
                    throw Error.ArgumentNull("serviceType");
                }
    
                // 如果DependencyResolver改变了,缓存里解析的变为无效,要清空
                if (_lastKnownDependencyResolver != _configuration.DependencyResolver)
                {
                    ResetCache();
                }
    
                object[] result;
    
                if (_cacheMulti.TryGetValue(serviceType, out result))
                {
                    return result;
                }
    
                // Check input after initial read attempt for performance.
                if (!_serviceTypesMulti.Contains(serviceType))
                {
                    throw Error.Argument("serviceType", SRResources.DefaultServices_InvalidServiceType, serviceType.Name);
                }
    
                //从DI获取服务实例,为了不实例化多次,放到缓存
                IEnumerable<object> dependencyServices = _lastKnownDependencyResolver.GetServices(serviceType);
                //不在缓存里(肯定不在),添加到缓存,并设置结果
                if (!_cacheMulti.TryGetValue(serviceType, out result))
                {
                    //把DI中获取的和字典中获取的一起放到结果中
                    result = dependencyServices.Where(s => s != null)
                                                .Concat(_defaultServicesMulti[serviceType])
                                                .ToArray();
                    _cacheMulti.TryAdd(serviceType, result);
                }
    
                return result;
            }
    
            //重写方法,返回某个服务类型的所有服务实例
            protected override List<object> GetServiceInstances(Type serviceType)
            {
                Contract.Assert(serviceType != null);
    
                List<object> result;
                if (!_defaultServicesMulti.TryGetValue(serviceType, out result))
                {
                    throw Error.Argument("serviceType", SRResources.DefaultServices_InvalidServiceType, serviceType.Name);
                }
    
                return result;
            }
    
            //重写方法,清除单个 
            protected override void ClearSingle(Type serviceType)
            {
                _defaultServicesSingle[serviceType] = null;
            }
    
            //重写方法 替换某个
            protected override void ReplaceSingle(Type serviceType, object service)
            {
                if (serviceType == null)
                {
                    throw Error.ArgumentNull("serviceType");
                }
                _defaultServicesSingle[serviceType] = service;
            }
    
            //移除所有缓存
            private void ResetCache()
            {
                _cacheSingle = new ConcurrentDictionary<Type, object>();
                _cacheMulti = new ConcurrentDictionary<Type, object[]>();
                _lastKnownDependencyResolver = _configuration.DependencyResolver;
            }
    
            //重写方法 移除某个服务的缓存
            protected override void ResetCache(Type serviceType)
            {
                object single;
                _cacheSingle.TryRemove(serviceType, out single);
                object[] multiple;
                _cacheMulti.TryRemove(serviceType, out multiple);
            }
        }

     3、HttpConfiguration

     

    public class HttpConfiguration : IDisposable
        {
            private readonly HttpRouteCollection _routes;
            private readonly ConcurrentDictionary<object, object> _properties = new ConcurrentDictionary<object, object>();
            private readonly MediaTypeFormatterCollection _formatters;
            private readonly Collection<DelegatingHandler> _messageHandlers = new Collection<DelegatingHandler>();
            private readonly HttpFilterCollection _filters = new HttpFilterCollection();
            //IDependencyResolver
            private IDependencyResolver _dependencyResolver = EmptyResolver.Instance;
            private Action<HttpConfiguration> _initializer = DefaultInitializer;
            private bool _initialized;
    
            private bool _disposed;
    
            public HttpConfiguration()
                : this(new HttpRouteCollection(String.Empty))
            {
            }
    
           
            public HttpConfiguration(HttpRouteCollection routes)
            {
                if (routes == null)
                {
                    throw Error.ArgumentNull("routes");
                }
    
                _routes = routes;
                _formatters = DefaultFormatters(this);
                //使用DefaultServices
                Services = new DefaultServices(this);
                ParameterBindingRules = DefaultActionValueBinder.GetDefaultParameterBinders();
            }
    
            //内部设置
            public ServicesContainer Services { get; internal set; }
        }

     4、ServicesExtensions

      ServicesContainer扩展方法,方便调用,调用具体的某个服务类型的服务实例,空判断,抛异常。

     public static class ServicesExtensions
        {
            public static IEnumerable<ModelBinderProvider> GetModelBinderProviders(this ServicesContainer services)
            {
                return services.GetServices<ModelBinderProvider>();
            }
    
            public static ModelMetadataProvider GetModelMetadataProvider(this ServicesContainer services)
            {
                return services.GetServiceOrThrow<ModelMetadataProvider>();
            }
    
            public static IEnumerable<ModelValidatorProvider> GetModelValidatorProviders(this ServicesContainer services)
            {
                return services.GetServices<ModelValidatorProvider>();
            }
    
            internal static IModelValidatorCache GetModelValidatorCache(this ServicesContainer services)
            {
                return services.GetService<IModelValidatorCache>();
            }
    
            public static IContentNegotiator GetContentNegotiator(this ServicesContainer services)
            {
                return services.GetService<IContentNegotiator>();
            }
    
            public static IHttpControllerActivator GetHttpControllerActivator(this ServicesContainer services)
            {
                return services.GetServiceOrThrow<IHttpControllerActivator>();
            }
    
            public static IHttpActionSelector GetActionSelector(this ServicesContainer services)
            {
                return services.GetServiceOrThrow<IHttpActionSelector>();
            }
    
            public static IHttpActionInvoker GetActionInvoker(this ServicesContainer services)
            {
                return services.GetServiceOrThrow<IHttpActionInvoker>();
            }
    
            public static IActionValueBinder GetActionValueBinder(this ServicesContainer services)
            {
                return services.GetService<IActionValueBinder>();
            }
    
            public static IEnumerable<ValueProviderFactory> GetValueProviderFactories(this ServicesContainer services)
            {
                return services.GetServices<ValueProviderFactory>();
            }
    
            public static IBodyModelValidator GetBodyModelValidator(this ServicesContainer services)
            {
                return services.GetService<IBodyModelValidator>();
            }
    
            public static IHostBufferPolicySelector GetHostBufferPolicySelector(this ServicesContainer services)
            {
                return services.GetService<IHostBufferPolicySelector>();
            }
    
          
            public static IHttpControllerSelector GetHttpControllerSelector(this ServicesContainer services)
            {
                return services.GetServiceOrThrow<IHttpControllerSelector>();
            }
    
            public static IAssembliesResolver GetAssembliesResolver(this ServicesContainer services)
            {
                return services.GetServiceOrThrow<IAssembliesResolver>();
            }
    
            public static IHttpControllerTypeResolver GetHttpControllerTypeResolver(this ServicesContainer services)
            {
                return services.GetServiceOrThrow<IHttpControllerTypeResolver>();
            }
    
            public static IApiExplorer GetApiExplorer(this ServicesContainer services)
            {
                return services.GetServiceOrThrow<IApiExplorer>();
            }
    
            public static IDocumentationProvider GetDocumentationProvider(this ServicesContainer services)
            {
                return services.GetService<IDocumentationProvider>();
            }
    
            public static IExceptionHandler GetExceptionHandler(this ServicesContainer services)
            {
                return services.GetService<IExceptionHandler>();
            }
    
           
            public static IEnumerable<IExceptionLogger> GetExceptionLoggers(this ServicesContainer services)
            {
                return services.GetServices<IExceptionLogger>();
            }
    
            public static IEnumerable<IFilterProvider> GetFilterProviders(this ServicesContainer services)
            {
                return services.GetServices<IFilterProvider>();
            }
    
            public static ITraceManager GetTraceManager(this ServicesContainer services)
            {
                return services.GetService<ITraceManager>();
            }
    
            public static ITraceWriter GetTraceWriter(this ServicesContainer services)
            {
                return services.GetService<ITraceWriter>();
            }
    
            internal static IEnumerable<TService> GetServices<TService>(this ServicesContainer services)
            {
                if (services == null)
                {
                    throw Error.ArgumentNull("services");
                }
    
                return services.GetServices(typeof(TService)).Cast<TService>();
            }
    
            // 通用的泛型方法,空判断,抛异常
            private static TService GetService<TService>(this ServicesContainer services)
            {
                if (services == null)
                {
                    throw Error.ArgumentNull("services");
                }
    
                return (TService)services.GetService(typeof(TService));
            }
    
            private static T GetServiceOrThrow<T>(this ServicesContainer services)
            {
                T result = services.GetService<T>();
                if (result == null)
                {
                    throw Error.InvalidOperation(SRResources.DependencyResolverNoService, typeof(T).FullName);
                }
    
                return result;
            }
        }

     二、自定义组件扩展

      扩展:

     public class ExtendedDefaultAssembliesResolver : DefaultAssembliesResolver
        {
            public override ICollection<Assembly> GetAssemblies()
            {
                PreLoadedAssembliesSettings settings = PreLoadedAssembliesSettings.GetSection();
                if (null != settings)
                {
                    foreach (AssemblyElement element in settings.AssemblyNames)
                    {
                        AssemblyName assemblyName = AssemblyName.GetAssemblyName(element.AssemblyName);
                        if (!AppDomain.CurrentDomain.GetAssemblies() .Any(assembly => AssemblyName.ReferenceMatchesDefinition(assembly.GetName(), assemblyName)))
                        {
                            AppDomain.CurrentDomain.Load(assemblyName);
                        }
                    }
                }
                return base.GetAssemblies();
            }
        }

      注册:

     httpServer.Configuration.Services.Replace(typeof(IAssembliesResolver),new ExtendedDefaultAssembliesResolver());
  • 相关阅读:
    test
    4css
    3css
    2css
    5html
    1css
    4html
    3html
    2html
    1.3 tensorflow2.0 常用函数
  • 原文地址:https://www.cnblogs.com/shawnhu/p/8065896.html
Copyright © 2020-2023  润新知