深入浅出SpringBoot源码——SpringFactoriesLoader

【SpringBoot】SpringBoot核心启动流程源码解析 阅读详情

前言

 要想深入学习源码,那么就得先学会读懂它的注释,毕竟是一手知识嘛。大家都知道SpringBoot是一款多么优秀的框架,它给Java开发者带来了极大的便利,再也不用去整合SSM了,这里就不继续赞美SpringBoot了。相信大家都会很好奇SpringBoot底层源码是怎么运行的?它是怎么神奇的让我们可以快速开发JAVAEE企业级项目?如何快速整合第三方框架?接下来的深入学习SpringBoot源码系列,让我和各位小伙伴们一同学习SpringBoot底层源码。

这里先学习下 SpringFactoriesLoader,这个类在SpringBoot整个底层源码中起到了非常关键的作用,置于有多关键,等读完整片文章你们就知道了。

SpringBoot版本:5.2.1.RELEASE

正文

1. 话不多说,源码注释解读一波

General purpose factory loading mechanism for internal use within the framework.

<p>{@code SpringFactoriesLoader} {@linkplain #loadFactories loads} and instantiates
 factories of a given type from {@value #FACTORIES_RESOURCE_LOCATION} files which
 may be present in multiple JAR files in the classpath. The {@code spring.factories}
 file must be in {@link Properties} format, where the key is the fully qualified
 name of the interface or abstract class, and the value is a comma-separated list of
 implementation class names. For example:
 

翻译过来就是:

SpringFactoriesLoader是用于Spring框架内部的通用工厂加载机制。

SpringFactoriesLoader通过loadFactories方法来加载并实例化来自FACTORIES_RESOUCE_LOCATION路径中的文件给定的工厂类型,
而这些文件可能包含在类路径的jar包中。这些文件通常都命名为spring.factories,并且都是以properties属性作为格式,文件中key表示
的是接口或者抽象类的全限定名称,而值是以逗号分隔的实现类的名称列表。

2. 上源码

 首先,可以看到SpringFactoriesLoader是final类,final修饰的类是不可以被继承,类中的方法都是不可以被覆盖的,且默认都是final修饰的方法,可以猜想到SpringFactoriesLoader类在被设计之初,是不想开发者继承该类并对该类进行扩展。所以,如果在开发中不想让别人对你的类继承或者扩展,那就用final来修饰吧~~

public final class SpringFactoriesLoader {
}

下面看下SpringFactoriesLoader类有哪些成员变量?

	/**
	 * 寻找工厂的位置
	 * 工厂可以存放在多个jar文件中
	 */
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

先来看看spring-boot:2.2.1.RELEASEjar包下,spring.factories文件的内容。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在spring.factories文件中,有非常多的工厂类,包括了属性源加载器、错误报告器、容器初始化器、容器监听器等,这些工厂类在SpringBoot中都有非常重要的作用,具体的读者可以自行前往查看。

// 自定义的用于存储工厂的缓存
private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();

 这里的缓存是通过ConcurrentReferenceHashMap类实现的,对于这个类估计很多读者都是第一次见吧… 下面就简单介绍下ConcurrentReferenceHashMap这个数据结构。

通过阅读ConcurrentReferenceHashMap的类注释,可以总结出以下几点:

  1. 它是一个ConcurrentHashMap,它的key和value使用ReferenceType.SOFT或者是ReferenceType.WEAK,即软引用和弱引用。
  2. 它可以用作Collections.synchronizedMap(new WeakHashMap<K, Reference>())的替代方法。
  3. 它遵循ConcurrentHashMap相同的设计约束,但不同的是它还支持null键和null值。
  4. 它默认使用的是SoftReference软引用。
  5. 使用软引用就意味着,在进行下一次GC时,如果即将发生OOM,GC就会把软引用指向的对象给回收掉。这一特性适合用作缓存处理。
2.1 loadFactories方法

 下面先来看下loadFactories方法的注释。

	/**
	 * Load and instantiate the factory implementations of the given type from
	 * {@value #FACTORIES_RESOURCE_LOCATION}, using the given class loader.
	 * <p>The returned factories are sorted through {@link AnnotationAwareOrderComparator}.
	 * <p>If a custom instantiation strategy is required, use {@link #loadFactoryNames}
	 * to obtain all registered factory names.
	 * @param factoryType the interface or abstract class representing the factory
	 * @param classLoader the ClassLoader to use for loading (can be {@code null} to use the default)
	 * @throws IllegalArgumentException if any factory implementation class cannot
	 * be loaded or if an error occurs while instantiating any factory
	 * @see #loadFactoryNames
	 */

翻译过来就是:
 loadFactories方法通过类加载器来加载并且实例化FACTORIES_RESOURCE_LOCATION路径文件中定义的工厂实现。在返回工厂之前,都会通过AnnotationAwareOrderComparator这个类来进行排序。如果需要自定义实例化策略,请使用loadFactoryNames去获取所有注册的工厂名称。
 loadFactories方法中,入参factoryType表示工厂类的接口或者抽象类;入参classLoader表示加载工厂的类加载器,如果为空则会使用默认的类加载器。

public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
		Assert.notNull(factoryType, "'factoryType' must not be null");
		// 类加载器
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			// 如果为空则用系统默认类加载器
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
		// 通过获取所有工厂实现类的名称集合
		List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
		if (logger.isTraceEnabled()) {
			logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
		}
		List<T> result = new ArrayList<>(factoryImplementationNames.size());
		for (String factoryImplementationName : factoryImplementationNames) {
			// 实例化工厂实现类,然后添加进result集合中
			result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
		}
		// 通过AnnotationAwareOrderComparator#sort方法对工厂名称进行排序
		AnnotationAwareOrderComparator.sort(result);
		return result;
	}
	private static <T> T instantiateFactory(String factoryImplementationName, Class<T> factoryType, ClassLoader classLoader) {
		try {
			// 通过classUtils工具类获取工厂实现类的Class对象
			Class<?> factoryImplementationClass = ClassUtils.forName(factoryImplementationName, classLoader);
			if (!factoryType.isAssignableFrom(factoryImplementationClass)) {
				throw new IllegalArgumentException(
						"Class [" + factoryImplementationName + "] is not assignable to factory type [" + factoryType.getName() + "]");
			}
			// 通过反射工具创建工厂类实例对象
			return (T) ReflectionUtils.accessibleConstructor(factoryImplementationClass).newInstance();
		}
		catch (Throwable ex) {
			throw new IllegalArgumentException(
				"Unable to instantiate factory class [" + factoryImplementationName + "] for factory type [" + factoryType.getName() + "]",
				ex);
		}
	}

 可以看到,loadFactories方法逻辑还是比较简单的,作用也比较明确,即:
① 通过classLoader去加载工厂获取其对应类名称;
② 通过instantiateFactory方法实例化工厂类;
③ 通过AnnotationAwareOrderComparator#sort方法对工厂进行排序;

看下哪些地方用到了loadFactories方法:
在这里插入图片描述
上图中的ConfigFileApplicationListener类是用于加载application配置文件的监听器类,对于application配置文件的加载,后面会详细讲解~~

2.2 loadFactoryNames方法

 由于loadFactoryNames方法的注释和loadFactories内容一样,所以这里就不写出来了。

	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		// 获取到factoryType工厂类型
		String factoryTypeName = factoryType.getName();
		// 加载SpringFactories,如果没有则返回一个空集合
		return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
	}
	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		// 从缓存中获取已经加载过的SpringFactories
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
			Enumeration<URL> urls = (classLoader != null ?
					// 通过类加载器读取类路径下的spring.factories文件,然后封装成URL存储于Enumeration中
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			// 遍历urls,再将url封装成UrlResource对象
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				/**
				 *  通过PropertiesLoaderUtils属性加载器去加载spring.factories中的value值。
				 *  这里的Properties是继承了HashTable的一个属性,key和value就对应着spring.factories文件里的key和value。
				 *  在PropertiesLoaderUtils中,底层是通过IO流读取的文件数据,这里就不细说了。
				 */ 
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					// 遍历获取工厂实现类名称
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			// 将获取结果存入缓存中
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

通过IDEA来看下有哪些地方用到了loadFactoryName的方法:
在这里插入图片描述
可以看到上面图片中SpringApplication中是哪个地方用到了loadFactoryNames方法,
在这里插入图片描述
进入到SpringApplication的构造器中,会调用setInitializers方法,这个方法是用于设置初始化器,初始化器也是非常重要的一个知识点,后面会详细介绍。

在这里插入图片描述
可以看到,在SpringApplication中的getSpringFactoriesInstance方法中,调用了SpringFactoriesLoader#loadFacotyNames。SpringFactoriesLoader#loadFactoryNames方法调用完之后就获取到了spring.factories中的value值,并存储到Set集合中,接着就调用createSpringFactoriesInstances方法通过反射工具实例化Set集合中存储的工厂类,经过排序之后再返回给上一层调用。

下面用一张简单的书序图描述下SpringFactoriesLoader在SpringBoot中的调用过程。
在这里插入图片描述

3. 总结

学习完SpringFactoriesLoader源码之后,算是真正踏入学习SpringBoot源码的大门了,通过SpringFactoriesLoader,SpringBoot得以去加载类路径下jar包中的spring.factories文件,才能够读取该文件中的各种工厂类:包括监听器、初始化器、属性源加载器等,而通过这些工厂类,才得以让SpringBoot变得这么强大!!!接下来几篇关于SpringBoot源码将会对监听器、初始化器、属性源以及配置加载等机制进行深入的分析~~

觉得作者写的不错的点个赞,关注作者。
本文 Github https://github.com/coderbruis/JavaSourceCodeLearning 已收录,更多源码文章以及源码在github中可以学习。

SpringBoot源码学习系列之启动原理简介 本博客通过debug方式简单跟一下Springboot application启动的源码,Springboot的启动源码是比较复杂的,本博客只是简单梳理一下源码,浅析其原理 阅读详情

相关推荐

Bladed风电仿真软件新手必看:从界面操作到功率曲线计算的完整指南

本文为风电工程师提供Bladed仿真软件的完整入门指南,涵盖界面操作、功率曲线计算及动态仿真等核心功能。通过实战技巧和常见问题解决方案,帮助新手快速掌握这款全球领先的风电机组仿真工具,提升风电项目开发效率。

weixin_30839881的博客 100

请你谈谈SpringFactoriesLoader详解、SpringBoot自动配置原理解析

1SpringFactoriesLoader详解 SpringFactoriesLoader 它本质上属于Spring框架私有的一种扩展方案,Spring Boot在Spring基础上的很多核心的功能都是基于此。根据资源文件的URL,就可以根据相应的文件来读取资源内容。 首先从classpath下每个jar包下搜寻文件名是META-INF/spring.factories的配置文件,然后将解析properties文件,找到指定名称的配置后返回,需要注意的是,这里不仅仅是在classpath路径下查找,会扫

一个搬砖工人 3527

密码学:一文读懂数字证书

消息摘要算法用于验证数据完整性服务,对称加密算法和非对称加密算法用于保证数据保密性服务,数字签名算法用于抗否认性服务。数字证书集合了多种密码学算法:自身带有公钥信息,可完成相应的加密/解密操作;同时,还带有数字签名,可鉴别消息来源;且自身带有消息摘要信息,可验证证书的完整性;由于证书本身含有用户身份信息,因而具有认证性。

Shujie 2907

SpringFactoriesLoader 简介

1.1 SpringFactoriesLoader 简介 SpringFactoriesLoader 工厂加载机制是 Spring 内部提供的一个约定俗成的加载方式,与 java spi 类似,只需要在模块的 META-INF/spring.factories 文件中,以 Properties 类型(即 key-value 形式)配置,就可以将相应的实现类注入 Spirng 容器中。Properties 类型格式:key:是全限定名(抽象类|接口) value:是实现,多个实现通过 逗号 进行分隔 1.2

qq_43540469的博客 1040

SAP ERP销售应收&成本科目确定

ERP作为业财一体软件,其中最核心的就是业务与财务模块的自动集成,无需财务进行手工做账,提高工作效率的同时降低人工操作产生的误差。今天浅浅的分享一下SD模块生成会计凭证时,借贷项确定的系统配置。一般项目上借贷项、科目都有FICO提供,但是具体的集成配置是在SD这边。

weixin_43657667的博客 1568

Python实战:如何用高德API批量获取POI数据(附完整代码)

本文详细介绍了如何使用Python通过高德API批量获取POI数据,包括API基础准备、核心功能模块设计、高级功能实现及实战应用案例。文章提供了完整代码示例,帮助开发者高效解决商业网点分析、公共设施规划等实际项目中的数据采集需求。

stem5的博客 317

SpringFactoriesLoader机制实例详解

SpringFactoriesLoader 工厂加载机制是 Spring 内部提供的一个约定俗成的加载方式,与 java spi 类似,只需要在模块的 META-INF/spring.factories 文件中,以 Properties 类型(即 key-value 形式)配置,就可以将相应的实现类注入 Spirng 容器中。Properties 类型格式:key:是全限定名(抽象类|接口)value:是实现,多个实现通过 **逗号** 进行分隔。

meser88的博客 814

Arthas实战(3)- CPU使用率高问题排查

Arthas实战(3)- CPU使用率高问题排查

java_liuyuan的博客 1731

SpringFactoriesLoader解析

SpringFactoriesLoader 工厂加载机制是 Spring 内部提供的一个约定俗成的加载方式,与 java spi 类似,只需要在模块的 META-INF/spring.factories 文件中,以 Properties 类型(即 key-value 形式)配置,就可以将相应的实现类注入 Spirng 容器中。

qq_32907491的博客 775

条件风险价值CVaR内容介绍(MATLAB例程)

在对微电网优化调度过程中,由于新能源机组出力和负荷的不确定性,若采用确定性的优化调度方案会存在一定的风险。反之,若风电和光伏输出功率的实际值高于预测值时,则需要降低可控机组出力或者弃风弃光,这都会给系统带来运行费用增加的风险。下,损失超过VaR值时的条件均值[1]。VaR是指在一定的置信水平下,某一投资组合在未来某一时间段内的最大损失。为m维决定系统风险损失的随机向量f(y)为y的概率密度函数。式中,m为所有场景的数量,yk为第k个场景下随机变量的值,为第k个场景发生的概率。的最小值即为CVaR,对应的。

贾贾的博客 3056

基于opencv的鱼群检测和数量统计识别鱼群密度带界面

摘要:本项目利用OpenCV图像处理技术实现了视频鱼类自动检测计数系统。通过背景减除、形态学处理和轮廓检测等方法,在视频帧中标记鱼类并统计数量。系统包含视频读取、前景提取、轮廓分析、中心点跟踪等核心功能,可实时显示计数结果。适用于水产养殖、生态监测等领域,具有实用价值。未来可通过参数优化、引入深度学习和多目标跟踪技术进一步提升准确性。(147字)

1719

Spring 源码之 SpringFactoriesLoader 类简介

SpringFactoriesLoader 是 Spring core 包中一个用于读取 classpath 下 META-INFO/spring.factories 文件中某个特定接口对应的实现类并完成实现类实例化的一个工具。该类是final 修饰的,即不能被继承,不能被修改。内部主要对外暴露了两个 public 类型的静态方法供使用方调用。

若千年后 1015

基于Simulink的MIMO-OFDM系统仿真建模示例

MIMO概述MIMO技术利用多个天线进行信号发送和接收,通过空间复用来提高数据传输速率,或通过空间分集来增强信号的可靠性。OFDM概述OFDM是一种将高速率数据流分成多个低速数据流并行传输的技术,每个子载波都采用调制方式(如BPSK、QPSK),并通过IFFT/FFT变换实现串并转换。MIMO-OFDM优势结合了MIMO的空间分集增益与OFDM对抗多径效应的能力,适用于宽带无线通信系统。提供了更高的数据速率和更好的频谱效率,是现代无线通信标准(如4G LTE、5G NR等)的核心技术之一。

xiaoheshang_123的博客 327

SpringFactoriesLoader 源码分析

SpringFactoriesLoader Author: HuiFer 源码阅读仓库: SourceHot-spring-boot 全路径 : org.springframework.core.io.support.SpringFactoriesLoader 测试类 : org.springframework.core.io.support.SpringFactoriesLoaderTests loadFactories **加载并实例化工厂 ** public static &l

huifer 499

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

SpringFactoriesLoader源码分析

SpringFactoriesLoader源码解析

qq_43466228的博客 395

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

SpringBoot源码解析(五):SpringFactoriesLoader如何加载META-INF/spring.factories

SpringBoot源码解析(五):SpringFactoriesLoader如何加载META-INF/spring.factories

我从不打没有准备的仗! 9583

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

Springboot 内部工具类 SpringFactoriesLoader 源码解析

类名 : SpringFactoriesLoader 所在包 : org.springframework.core.io.support 官方文档 概述 该类并不对外暴露给应用开发者使用,而是spring框架自己使用的内部工具类,本身本声明为 abstract,不可以被实例化。 在 Springboot 应用启动的过程中,这个类的工作很重要, 启动逻辑使用该类从classpath上所有jar包中找...

Details Inside Spring 3747

【SpringBoot】深入浅出 SpringBoot 自动装配

本文深入解析了SpringBoot自动装配机制的发展历程与实现原理。文章首先回顾了传统Spring框架的配置痛点,包括XML配置臃肿、注解配置零散等问题。随后重点剖析了SpringBoot2.x的自动装配机制,详细讲解了从@SpringBootApplication注解触发、AutoConfigurationImportSelector执行到条件筛选的完整流程,并解释了spring.factories文件的作用范围。最后介绍了SpringBoot3.x改用AutoConfiguration.imports文

27届Java后端炒粉练习生,AICoding中........ 1937

springboot源码分析4-springboot之SpringFactoriesLoader使用

摘要:本文我们重点分析一下Spring框架中的SpringFactoriesLoader类以及META-INF/spring.factories的使用。在详细分析之前,我们可以思考一个问题?在我们设计一套API供别人调用的时候,如果同一个功能的要求特别多,或者同一个接口要面对很复杂的业务场景,这个时候我们该怎么办呢?其一:我们可以规范不同的系统调用,也就是传递一个系统标识;其二:我们在内部编码的时

分享牛 1万+

SpringBoot源码分析:SpringBoot启动源码(一)

SpringBoot启动的源码分为前期初始化,和后期启动两个部分,我们从这两个部分开始介绍。

weixin_55229531的博客 5万+

springboot 源码(一)初始化器加载 SpringFactoriesLoader、ApplicationContextInitializer

springboot 源码 (一) SpringFactoriesLoaders 前言 SpringFactoriesLoaders 通用工厂加载机制 提示:以下是本篇文章正文内容,下面案例可供参考 一、pandas是什么? 示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。 二、使用步骤 1.引入库 代码如下(示例): import numpy as np import pandas as pd import matplotlib.pyplot as plt

weixin_37765815的博客 661

【springboot源码解读系列】(五、springboot启动之解析META-INF/spring.factories的SpringFactoriesLoader详细解读)

上一讲【springboot源码解读系列】(四、springboot启动之SpringApplicationRunListeners:通知所有监听器,系统启动中…用于非常早的初始化)说到通过SpringFactoriesLoader加载META-INF/spring.factories获取我们的自定义监听器和启动器。 那么这讲就来揭开SpringFactoriesLoader神秘的面纱:讲解都在注释里面: 值得学习的是他的这种设计理念,通过指定规范,然后对其使用者提供扩展,springboot的启动器原理也

CoderOu 2066

SpringBoot 2.x SpringFactoriesLoader工厂加载机制解析

文章目录SpringFactoriesLoader 作用介绍源码解析 SpringFactoriesLoader 作用介绍 其源码注释如下 General purpose factory loading mechanism for internal use within the framework. SpringFactoriesLoader loads and instantiates factories of a given type from "META-INF/spring.factories"

Java持续实践 839

SpringBoot源码初学者(一):SpringBoot功能扩展接口的使用与源码分析

ps:   在长期的源码学习中,我一直没有找到一个讲解SpringBoot源码的文章,适合刚开始阅读源码的新手,包括我之前的写的一些文章,说实话SpringBoot的源码的确不适合新手,跳转多、结构复杂混乱,很容易迷乱其中。 长时间的学习当中,也总结出这些文章的部分问题: 没有说明用法,直接讲解源码,其实很多新手对SpringBoot了解不够多,还不明白怎么用,更不要想能理解源码了 源码阅读跳跃...

敲代码的旺财的博客 1145

springboot源码解析

springboot源码解析 转自: https://mp.weixin.qq.com/s__biz=MzA5NzgzODI5NA==&mid=2454036220&idx=1&sn=453b2f87ae66863242184d1f80c29e90&chksm=872b8b45b05c02536ff82f3609cd75d16b0ccf4a735c36c0aba8...

lwy572039941的博客 2513

Spring Boot之SpringFactoriesLoader原理剖析

SpringFactoriesLoader是Spring框架中的加载类的核心加载器,专门用来读取classpath下各个jar包里META-INF/spring.factories目录里面的配置。学习Spring Boot实现原理,这个必须得搞清楚! 官方说明: General purpose factory loading mechanism for internal use within t...

zhaodongchao的博客 691
上一篇: 一篇文章快速深入学习ThreadLocal
下一篇: 深入浅出SpringBoot源码——系统初始化器
CoderBruis
博客等级 码龄10年 4444粉丝 102原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值