Spring Framework核心技术-中文文档(资源)

资源

介绍

不幸的是,Java的标准的java.net.URL类和各种URL前缀标准的处理器并不能足够满足用于所有对低级别资源的访问。例如,没有标准化的URL实现可用于访问需要从类路径或者相对于ServletContext获得的资源。尽管对于特殊的URL前缀可以注册新的处理器(类似于http:这样前缀的已存在的处理器。),这通常比较复杂,并且URL接口始终缺少可取的功能,例如检查指向的资源是否存在的方法。

Resource接口

位于org.springframework.core.io包的Spring的Resource接口是一个抽象访问低级别资源的更有能力的接口。以下列表提供了一个Resource接口的概览。请查看Resource javadoc了解进一步详情。

public interface Resource extends InputStreamSource {

	boolean exists();

	boolean isReadable();

	boolean isOpen();

	boolean isFile();

	URL getURL() throws IOException;

	URI getURI() throws IOException;

	File getFile() throws IOException;

	ReadableByteChannel readableChannel() throws IOException;

	long contentLength() throws IOException;

	long lastModified() throws IOException;

	Resource createRelative(String relativePath) throws IOException;

	String getFilename();

	String getDescription();
}

正如Resource接口定义所展示的,它扩展了InputStreamSource接口。以下列表展示了InputStreamSource接口的定义:

public interface InputStreamSource {

	InputStream getInputStream() throws IOException;
}

一些来自Resource接口最重要的方法是:

  • getInputStream():定位和打开资源,返回一个InputStream从资源读取。预计每次调用返回一个新的InputStream。关闭流是调用者的责任。
  • exists():返回一个boolean,指示此资源是否以物理格式实际存在。
  • isOpen():返回一个boolean,指示此资源是否表示有带有一个打开流的处理器。如果true,InputStream不能被读取多次并且一次读取,然后关闭避免资源泄露。对所有常规资源返回false,除了InputStreamResource除外。
  • getDescription():返回资源的描述,当处理资源时用于错误输出。通常是完整限定文件名称或者资源的实际URL。

其他方法让你获取一个实际的URL或者File对象表示的资源(底层的实现是否兼容并支持该功能)。

Resesouce接口的一些实现也实现了扩展的WritableResource接口用于支持写入资源。

Spring本身广泛地使用Resoure抽象,当需要一个资源时,作为在许多方法签名中的一个参数类型。在一些Spring API中的其他方法(例如各种ApplicationContext实现的构造器)接受一个String,其使用未经修饰或者简单的格式用于创建适合该上下文实现的Resource,或者通过在String路径添加特殊的前缀,让调用者指定一个必须创建和使用特定的Resource实现。

虽然Resource接口通过Spring大量使用,但是对于你自己的代码(访问资源)中实际上也非常方便作为常用工具类来使用,即使你的代码并不知道或者不关心Spring的其他部分。尽管将你的代码和Spring结合,实际只将它结合到一个小的工具类集合,其作为一个更有能力的URL替代品并被认为等价你想用于此目的的任何其他类库。

Resource抽象不是替换功能。它尽可能的封装它。例如UrlResource封装了URL和使用封装的URL来做它的工作。

内建的Resource实现

Spring 包含几个内建的Resource实现:

要了解在Spring中可用的完整Resource实现列表,请查阅Resourcejavadoc的“All Known Implementing Class”部分。

UrlResource

UrlResource封装了java.net.URL,用于访问通常可以使用URL访问的任意对象,例如文件,HTTPS目标,FTP目标和其他。所有的URL有一个标准的String表达式,例如合理的标准化前缀用于识别不同的URL类型。这包含file:来访问文件系统路径,https:通过HTTPS协议来访问资源。ftp:通过FTP访问资源,等等。

UrlResource通过明显地使用UrlResource构造器的Java代码创建,但是当你调用一个接收表示路径的String参数的API方法通常被隐式创建。对于后者情况,JavaBean PropertyEditor最终确定要创建Resource的类型。如果路径字符串包含一个已知的前缀(例如classpath:),针对该前缀创建一个合理的指定的Resource。但是,如果他没有识别前缀,它假设此字符串是一个标准的URL字符串创建一个UrlResource

ClassPathResource

此类表示应该从类路径中获取资源。它可以使用线程上下文类加载器,给定的类加载器或者用于加载资源所给的类。

如果类路径资源位于文件系统中,此Resource实现支持将其解析为java.io.File,但是对于位于jar中的且未展开(由Servlet引擎或者其他环境执行)到文件系统的类路径资源,则不支持解析为java.io.File。要解决此问题,各种各样的Resource实现支持解析为java.net.URL.

ClassPathResource通过Java代码显性地使用ClassPathResource构造器创建,但是当你调用一个接收为表示路径的String参数API方法时则被隐式创建。对于后一种情况,JavaBean的PropertyEditor会识别字符串路径上的特殊的前缀,classpath:,并在这种情况下,创建一个ClassPathResource

FileSystemResource

这个是针对java.io.File处理的Resource实现。它也支持java.nio.file.Path处理,执行Spring的标准的基于字符串的路径转换,但是通过java.nio.file.FilesAPI提供所有操作。对于完整的java.nio.path.Path基础的支持使用一个PathResource代替。FileSystemResource支持将其解析为FileURL

PathResource

这个是针对java.nio.file.Path处理的Resource实现,通过Path API执行所有操作和转换。它支持将其解析为FileURL并且也实现了扩展的WritableResource接口。PathResource实际上基于纯java.nio.path.Path基本的FileSystemResource的替代方案,与FileSystemResource具有不同的createRelative行为。

ServletContextResource

这个是针对ServletContext资源的Resource实现,解析了在相关的web应用程序的根目录的相对路径。

它一直支持流式访问和URL访问,允许java.io.File访问,但是只有当web应用程序归档被解压并且资源实际上已经位于文件系统上时,才允许`java.io.File``访问。资源是否被展开到文件系统上,或者直接从JAR文件或者从其他地方(像数据库)访问,实际上取决于Servlet容器。

InputStreamResource

InputStreamResource是针对给定的InputStream的一个Resource实现。只有当没有特定的Resource实现可用时才会使用它。特别是在可能的情况下,优先使用ByteArrayResource或者任何基于文件的Resource实现。

与其他Resource实现相比,这个是对于已经打开资源的描述符。因此,从isOpen()返回true。如果你需要将资源描述符保存在某个地方或者读取一个流多次不要使用它。

ByteArrayResource

Resource实现是针对给定的字节数组。它为给定的字节数组创建了一个ByteArrayInputStream

从任意给定的字节数组加载内容是非常有用的,无需使用一次性的InputStreamResource

ResourceLoader接口

ResourceLoader接口被设计成由可以返回(即加载)Resource实例的对象来实现。以下列表展示了ResourceLoader接口定义:

public interface ResourceLoader {

	Resource getResource(String location);

	ClassLoader getClassLoader();
}

所有的应用程序上下文实现了ResrouceLoader接口。因此,所有应用程序上下文可以用于获取Resource实例。

当你在特定的应用程序上下文调用getResource(),并且指定的位置路径没有特定的前缀,你得到一个适用于该特定的应用程序上下文的Resource类型。例如,假设以下代码片段是针对ClassPathXmlApplicationContext实例上的运行的:

Resource template = ctx.getResource("some/resource/path/myTemplate.txt");

针对ClassPathXmlApplicationContext,此代码返回了ClassPathResource。如果针对FileSystemXmlApplicationContext运行相同的方法,它将返回FileSystemResource。对于WebApplicationContext,它将返回ServletContextResource。类似地,它将为每一个上下文返回适当的对象。

因此,你可以以适合特定上下文的方式加载资源。

换句话说,你也可以通过指定特殊的classpath:前缀强制使用ClassPathResource,不管应用程序上下文类型,正如以下示例所示:

Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");

类似地,你也可以通过指定任意标准的java.net.URL前缀使用UrlResource。以下示例使用filehttps前缀:

Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");
Resource template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt");

以下表格汇总了将String对象转换为Resource对象的策略:

前缀示例说明
classpath:classpath:com/myapp/config.xml从类路径加载
file:file:///data/config.xml从文件系统加载为URL。也可以查看FileSystemResource说明
https:https://myserver/logo.png加载为URL
(none)/data/config.xml取决于底层的ApplicationContext

ResourcePatternResolver接口

ResourcePatternResolver接口是对ResourceLoader接口的扩展,其定义了将位置格式(例如,Ant风格路径格式)解析到Resource对象的一种策略。

public interface ResourcePatternResolver extends ResourceLoader {

	String CLASSPATH_ALL_URL_PREFIX = "classpath*:";

	Resource[] getResources(String locationPattern) throws IOException;
}

正如上面所看到的,为了从类路径所有匹配的资源,此接口也定义了一个特殊的classpath*:资源前缀。注意,在这种情况下,资源位置预期是不带占位符的路径–例如,classpath*:/config/beans.xml。在类路径中JAR文件或者不同的目录可以包含多个相同路径和相同名称的文件。请查看在应用程序上下文构造器资源路径通配符和它的子章节了解更多关于带有classpath*:资源前缀的通配符支持的详情。

一个传入的ResourceLoader(例如,通过ResrouceLoaderAware语义提供的ResourceLoader)可以检查它是否也实现了这个扩展接口。

PathMatchingResourcePatternResolver是一个独立实现,它可以在ApplicationContext之外可用,通过ResourceArrayPropertyEditor用于填充Resource[] bean属性。PathMatchingResourcePatternResolver可以解析一个特定的资源位置路径到一个或者多个匹配的Resource对象。资源路径可以是一个简单的路径,它与目标Resource存在一对一的映射关系,或者可以包含特殊的classpath*:前缀或者内部的Ant风格的正则表达式(使用Spring的org.springframework.util.AntPathMatcher工具)。后面的两个都是有效的通配符。

在任意标准的ApplicationContext中默认的ResourceLoader实际情况是PathMatchingResourcePatternResolver的一个示例,其实现了ResourcePatternResolver接口。同样适用于ApplicationContext实例本身,其也实现了ResourcePatternResolver接口并委托给默认的PathMatchingResourcePatternResolver

ResourceLoaderAware接口

ResourceLoaderAware接口是一个特殊的回调接口,用于标识那些期望提供ResourceLoader引用的组件。
以下列表展示了ResourceLoaderAware接口定义:

public interface ResourceLoaderAware {

	void setResourceLoader(ResourceLoader resourceLoader);
}

当一个类实现ResourceLoaderAware并且部署到一个应用程序上下文(作为一个Spring管理的bean),通过应用程序上下文识别为ResourceLoaderAware。应用程序上下文然后调用setResourceLoader(ResourceLoader),提供本身作为参数(记住,所有的应用程序上下文实现了ResourceLoader接口)。

因为ApplicationContext是一个ResourceLoader,这个bean也实现了ApplicationConetextAware接口,直接使用提供的应用程序上下文来加载资源。但是,通常,如果你只需要ResourceLoader接口,使用专门的ResourceLoader接口更好。代码只会和资源加载接口耦合并不会对所有的Spring ApplicationContext接口耦合在一起。

在应用程序组件中,你也可以依赖ResourceLoader的自动绑定作为实现ResourceLoaderAware接口的替代方案。传统的constructorbyType自动绑定模式(正如在自动绑定协作者中描述的)有能力提供ResourceLoader依次用于构造器参数或者setter方法参数。为了更加灵活(包括自动绑定字段和多个参数方法的能力),考虑使用基于注解的自动绑定特性。在该情况下,只要涉及的字段,构造器或者方法带有@Autowired注解,ResourceLoader就会自动绑定到期望ResourceLoader类型的字段,构造器参数或者方法参数。要了解更多信息,请查看使用@Autowired

要对包含通配符或者使用特殊的classpath*资源前缀的资源路径加载一个或者多个Resource对象,考虑使用自动装配的ResourcePatternResolver实例到你的应用程序组件代替ResourceLoader

资源作为依赖

如果bean本身将通过某些动态过程确定并提供资源路径,那么使用的ResourceLoader或者ResourcePatternResolver接口来加载资源可能是合理的。例如,考虑一些模板的加载,所需的特定的资源取决于用户的角色。如果资源是静态的,完全取消ResourceLoader接口的使用是合理的,让bean公开它所需的Resource属性并期望他们被注入到bean。

使它容易注入这些属性的是所有应用程序上下文注册和使用一个特殊的JavaBean PropertyEditor,其可以将String路径转换为Resource对象。例如,以下MyBean类有一个类型Resourcetemplate属性。

public class MyBean {

	private Resource template;

	public setTemplate(Resource template) {
		this.template = template;
	}

	// ...
}

在XML配置文件中,template属性可以使用简单的字符串配置该资源,正如以下示例所示:

<bean id="myBean" class="example.MyBean">
	<property name="template" value="some/resource/path/myTemplate.txt"/>
</bean>

注意,资源路径没有前缀。因此,因为应用程序上下文本身将用于ResourceLoader,通过ClassPathResource,FileSystemResource或者ServletContextResource加载资源,依赖应用程序上下文的精确类型。

如果你需要强制要使用的特定的Resource类型,你可以使用一个前缀。以下两个示例展示了如何强制使用ClassPathResourceUrlResource(后者用于访问文件系统中的一个文件):

<property name="template" value="classpath:some/resource/path/myTemplate.txt">
<property name="template" value="file:///some/resource/path/myTemplate.txt"/>

如果MyBean通过使用注解驱动配置进行重构,myTemplate.text路径可以在名称为template.path的key进行存储 – 例如,以属性文件的形式提供给Spring Environment(请查看环境抽象)。模板路径可以通过@Value注解使用属性占位符(请查看使用@Value)进行引用。Spring将检索模板路径作为字符串的值,特殊的PropertyEditor将字符串转换为Resource对象注入到MyBean构造器。以下示例提到了如何实现这个:

@Component
public class MyBean {

	private final Resource template;

	public MyBean(@Value("${template.path}") Resource template) {
		this.template = template;
	}

	// ...
}

如果我们想要支持在类路径中多个路径相同路径下发现的多个模板–例如,在类路径中的多个jars中 – 我们可以使用特殊的classpath*:前缀和通配符来定义一个为classpath*:/config/templates/*.txttemplates.path键。如果我们如下重定义MyBean类,Spring将模板路径格式转换为Resource数组对象,其可以注入到MyBean构造器。

@Component
public class MyBean {

	private final Resource[] templates;

	public MyBean(@Value("${templates.path}") Resource[] templates) {
		this.templates = templates;
	}

	// ...
}

应用程序上下文和资源路径

此章节涵盖了如何使用资源创建应用程序上下文,包括与XML工作的捷径,如何使用通配符和其他细节。

构造应用程序上下文

一个应用程序上下文构造器(用于特定的应用程序上下文类型)通常使用字符传或者字符串数组作为资源的位置路径,例如组成上下文定义的XML文件。

当这样的一个位置路径没有前缀时,从该路径构建的特定的Resource类型和用于加载bean定义取决于并应用于特定的应用程序上下文。例如,考虑以下示例,其创建了一个ClassPathXmlApplicationContext:

ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");

bean定义从类路径加载,因为使用了ClassPathResource。但是,考虑以下示例,其创建了一个FileSystemXmlApplicationContext:

ApplicationContext ctx =
	new FileSystemXmlApplicationContext("conf/appContext.xml");

现在bean定义从文件系统位置加载(在这种情况中,相对于当前工作目录)。

注意,在位置路径特殊的classpath前缀或者标准的URL前缀的使用覆盖加载bean定义要创建的Resouce的默认类型。考虑以下示例:

ApplicationContext ctx =
	new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");

从类路径使用FileSystemXmlApplicationContext加载bean定义。但是,它仍是一个FileSystemXmlApplicationContext。如果随后当做ResourceLoader使用,任何无前缀的路径仍会视为文件系统路径。

构造ClassPathXmlApplicationContext示例 – 捷径

ClassPathXmlApplication公开多个构造器来开启方便的实例化。基本的想法是你仅仅只提供包含XML文件的文件名称(无需前导路径信息)的字符串数组,也可以提供一个ClassClassPathXmlApplicationContext从提供的类获取路径信息。

考虑以下目录布局:

com/
  example/
    services.xml
    repositories.xml
    MessengerService.class

下面的实例展示了由名称为services.xmlrepositories.xml文件中定义的bean定义组成的ClassPathXmlApplicationContext实例如何实例化:

ApplicationContext ctx = new ClassPathXmlApplicationContext(
	new String[] {"services.xml", "repositories.xml"}, MessengerService.class);

请查看ClassPathXmlApplicationContextjavadoc 了解更多关于各种各样构造器的细节。

应用程序上下文构造器资源路径中的通配符

在应用程序上下文构造器值中的资源路径可以是简单的路径(正如早前展示的),他们的每一个存在一对一映射到一个目标Resource或者,可能包含特殊的classpath*:前缀或者内部的Ant风格格式(使用Spring的PathMatcher工具匹配)。后面的两个都是有效的通配符。

这种机制的一种使用是当你需要做组件风格的应用程序组装时。所有组件可以发布上下文定义模块到已知的位置路径,并且当最终的应用程序上下文使用前缀为classpath*前缀的相同的目录创建时,所有组件模块自动选择。

注意,通配符在应用程序上下文构造器(或者当你直接使用PathMatcher工具类)中是对资源路径的使用是专有的,并在构造时解析。它并没有使用Resource本身做其他事情。你不能使用classpath*:前缀来构造一个实际的Resource,因为一个资源在某刻仅仅指向一个资源。

Ant-style格式

路径位置可以包含Ant风格格式,正如以下示例所示:

/WEB-INF/*-context.xml
com/mycompany/**/applicationContext.xml
file:C:/some/path/*-context.xml
classpath:com/mycompany/**/applicationContext.xml

当路径位置包含Ant风格格式,解析器按照一个更加复杂过程来尝试解析通配符。从非通配符片段之间的路径并获取一个URL,生成一个Resource。如果URL不是一个jar: URL或者特定于容器的变体(例如,在WebLogic中的zip:,在WebShere中的wsjar等等),从它将获取java.io.File并用于通过遍历文件系统解析通配符。在jar URL这种情况下,解析器要么从它得到一个java.net.JarURLConnection要么手动解析jar URL,然后遍历jar文件的内容来解析通配符。

可移植性的影响

如果指定的路径已经是一个file URL(要么隐性地因为基本的ResourceLoader是一个文件系统加载器,要么是显性地),通配符保证在一个完全地便携方式工作。

如果特定的路径是一个classpath位置,解析器必须通过使用Classloader.getResource()调用获取最后的非通配符路径段的URL。因为这仅仅是一个路径的节点(不是最终的文件),在这种情况下,返回的URL的具体类型实际上是未定义的(在ClassLoader javadoc)。在事件中,它通常是一个表示目录(类路径资源解析为件系统位置)的java.io.File或者某种类型的jar URL(类路径资源解析为jar位置时)。确实,在这个操作中存在可移植性的问题。

如果对于最后一个非通配符段获得一个jar URL,解析器必须可以从它得到java.net.JarURLConnection或者手动转换jar URL,以便能够遍历jar文件的内容并解析通配符。在大多数环境中能够生效,但是在其他一些情况失败,我们强烈推荐,在你依赖它之前,来自jar的资源的通配符解析在你专有的环境中已经充分测试。

classpath*:前缀

当构建一个基于XML的应用程序上下文,位置字符串可以使用特殊的classpath*:前缀,正如以下示例所示:

ApplicationContext ctx =
	new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml");

特殊前缀指定了匹配给定名称的所有类路径资源必须获取(在内部,通过对ClassLoader.getResource(...)的调用发生),然后合并组成最终的应用程序上下文定义。

通配符类路径依赖底层的ClassLoadergetResources()方法。如今,正如大多数应用程序服务器提供他们自己的ClassLoader实现,行为可能存在差异,特别是当处理jar文件时。如果classpath*工作是使用ClassLoader来从类路径中的jar加载一个文件,要进行一个简单的测试来检测:getClass().getClassLoader().getResource("<someFileInsideTheJar")。使用有相同名称但是存在于两个不同的位置的文件尝试此测试–例如,相同名称的文件但是在类路径中不同的jar。如果返回不合理的结果,检查应用程序服务器文档来查找可能影响ClassLoader的行为的设置。

你也可以将classpath*:前缀与PathMatcher模式结合在其余的位置路径中(例如,classpath*:META-INF/*-beans.xml)。这种情况中,解决策略相当简单:在最后的非通配符路径片段使用ClassLoader.getResource()调用来获取在类加载器层次所有匹配的资源,然后每一个资源使用早前描述的相同的PathMatcher解决策略用于通配符子路径。

与通配符相关的其他注意事项

注意,classpath*:,当与Ant风格格式组合时,只有在格式之前至少存在一个根路径,才能可靠地工作,除非目标文件实际存在于在文件系统中。这意味着一个格式(例如classpath*:*.xml)可能不会从jar文件的根检索文件,而是从已经解压目录的根检索文件。

Spring的检索类路径条目的能力起源于JDK的ClassLoader.getResource()方法,该方法只针对空字符串(表示要搜索的潜在根目录)返回文件系统位置。Spring解析URLClassLoader运行时配置,以及解析在jar文件的java.class.path清单文件,但是并不保障产生可移植的行为。

类路径的扫描要求在类路径中相对应的条目存在。当你使用Ant构建JARs时,不要激活JAR任务的files-only开关。而且,基于某环境中的安全策略类路径可能不会暴露–例如,在JDK1.7.0_45或者更高版本独立运行的应用程序(其要求“授信的类库”在你的清单列表进行设置。请查看stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。

在JDK 9的模块路径(Jigsaw),Spring的类路径扫描通常也如期望一样工作。将资源放入到一个专用目录是强烈推荐的,避免使用搜索jar文件根目录级别的上述提到的可移植问题。

如果要搜索的根包在多个类路径位置可用,带有classpath:的Ant风格的资源不保证查找到匹配的出资源。考虑以下资源位置示例:

com/mycompany/package1/service-context.xml

现在考虑一个Ant风格路径,某些人可能用于尝试查找该文件:

classpath:com/mycompany/**/service-context.xml

这样的文件可能只存在类路径中的某一个位置,但是当使用前面示例路径来尝试解析它时,解析器会根据getResource("com/mycompany");方法返回的第一个URL进行处理。如果此基本的包节点在多个ClassLoader位置存在,期望的资源可能在第一个位置不存在。因此,在这样的情况下,你应该倾向于使用相同的Ant风格样式的classpath*,其搜索所有包含com.mycompany基本包:classpath*:com/mycompany/**/service-context.xml类路径位置。

FileSystemResource 注意事项

没有附属于FileSystemApplicationContextFileSystemResource(即,当一个FileSystemApplicationContext不是实际的ResourceLoader)将如你所期望的处理绝对和相对路径。相对路径是相对于当前工作目录,而绝对路径是相对于文件系统的根路径。

但是对于向后兼容(历史原因)的原因,当FileSystemAppicationContextResourceLoader时,情况会发生变化。FileSystemApplicationContext强制所有附带的FileSystemResource实例将所有的位置路径按照相对路径处理,无论他们是否以斜线开始。实践中,这意味着以下示例是等价的:

ApplicationContext ctx =
	new FileSystemXmlApplicationContext("conf/context.xml");
ApplicationContext ctx =
	new FileSystemXmlApplicationContext("/conf/context.xml");

以下示例也是等价的(尽管在逻辑上他们是不同的,一个是相对路径,另外一个是绝对路径):

FileSystemXmlApplicationContext ctx = ...;
ctx.getResource("some/resource/path/myTemplate.txt");
FileSystemXmlApplicationContext ctx = ...;
ctx.getResource("/some/resource/path/myTemplate.txt");

在实践中,如果你需要绝对文件系统路径,你应该避免使用FileSystemResource或者FileSystemXmlApplication的绝对路径,而是强制使用file:URL前缀的UrlResource。以下示例展示了如何这样做:

// actual context type doesn't matter, the Resource will always be UrlResource
ctx.getResource("file:///some/resource/path/myTemplate.txt");
// force this FileSystemXmlApplicationContext to load its definition via a UrlResource
ApplicationContext ctx =
	new FileSystemXmlApplicationContext("file:///conf/context.xml");
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值