2024 版 Spring Boot 基础知识复习手册

@Bean(“user”)

@ConditionalOnBean(type = “com.wwj.springboot.bean.Dog”)

public User getUser(){

return new User(“张三”,20);

}

还可以通过判断组件的类型:

@Bean(“user”)

@ConditionalOnBean(value = Dog.class)

public User getUser(){

return new User(“张三”,20);

}

尤其需要注意的是,因为代码是从上至下依次执行的,所以在注册组件时的顺序要特别注意,比如:

@Configuration

public class MyConfig {

@Bean(“user”)

@ConditionalOnBean(value = Dog.class)

public User getUser(){

return new User(“张三”,20);

}

@Bean(“dog”)

public Dog getDog(){

return new Dog();

}

}

在这段程序中,SpringBoot会先注册User对象,而此时Dog对象还没有被注册,所以会导致User对象无法注册。

而@ConditionalOnMissingBean注解的作用与@ConditionalOnBean注解正好相反,它会判断当前容器中是否不存在指定的Bean,若不存在则生效,否则不生效。

这些注解除了能够标注在方法上,还能作用于类上,当被标注在类上时,若条件成立,则配置类的所有注册方法生效;若条件不成立,则配置类的所有注册方法均不成立。

@Configuration

@ConditionalOnBean(value = Dog.class)

public class MyConfig {

@Bean(“user”)

public User getUser(){

return new User(“张三”,20);

}

@Bean(“dog”)

public Dog getDog(){

return new Dog();

}

}

@ImportResource

===================

该注解用于导入资源,比如现在有一个Spring的配置文件:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns=“http://www.springframework.org/schema/beans”

xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”

xsi:schemaLocation=“http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd”>

若是想将其转化为配置类,代码少一点倒还好说,当配置文件中注册的Bean非常多时,采用人工的方式显然不是一个好的办法,为此,SpringBoot提供了@ImportResource注解,该注解可以将Spring的配置文件直接导入到容器中,自动完成组件注册。

@Configuration

@ImportResource(“classpath:bean.xml”)

public class MyConfig {

@Bean(“user”)

public User getUser(){

return new User(“张三”,20);

}

@Bean(“dog”)

public Dog getDog(){

return new Dog();

}

}

@ConfigurationProperties

============================

该注解用于配置绑定,也大量运用于SpringBoot底层。首先在配置文件中编写两个键值:

user.name=zhangsan

user.age=30

然后使用该注解将其绑定到User类上:

@Component

@ConfigurationProperties(prefix = “user”)

public class User {

private String name;

private int age;

public User() {

}

@Override

public String toString() {

return “User{” +

“name='” + name + ‘’’ +

“, age=” + age +

‘}’;

}

}

但结果却有些出乎意料:

User{name=‘Administrator’, age=30}

这是因为我们将前缀 prefix 指定为了user,而user可能和我们的系统配置产生了重复,所以导致了这个问题,此时我们只需将前缀修改一下即可:

@Component

@ConfigurationProperties(prefix = “users”)

public class User {

private String name;

private int age;

public User() {

}

@Override

public String toString() {

return “User{” +

“name='” + name + ‘’’ +

“, age=” + age +

‘}’;

}

}

前缀修改了,配置文件的内容也需要做相应的修改:

users.name=zhangsan

users.age=30

需要注意的是,若是想实现配置绑定,就必须要将这个待绑定的类注册到容器中,比如使用@Component注解,当然,SpringBoot也提供了一个注解与其配套使用,它就是:@EnableConfigurationProperties

该注解必须标注在配置类上:

@Configuration

@EnableConfigurationProperties(User.class)

public class MyConfig {

}

作用是开启指定类的配置绑定功能,它的底层其实也是通过@Import注解实现的,此时User类就无需将其注册到容器中:

@ConfigurationProperties(prefix = “users”)

public class User {

private String name;

private int age;

public User() {

}

@Override

public String toString() {

return “User{” +

“name='” + name + ‘’’ +

“, age=” + age +

‘}’;

}

}

Spring Boot 会自动将属性值绑定到 User 类,并将其注册到容器中。Spring Boot 相关的技术文章我整理成了 PDF,关注微信公众号「Java后端」回复「666」下载这一本技术栈手册。

02

自动配置原理

有了前面的注解基础之后,我们就能够更深入地了解Spring Boot的自动配置原理,自动配置正是建立在这些强大的注解之上的。

我们首先观察一下主启动类上的注解:

@SpringBootApplication

public class SpringbootApplication {

public static void main(String[] args) {

SpringApplication.run(SpringbootApplication.class, args);

}

}

翻阅源码可以得知,@SpringBootApplication注解其实是由三个注解组成的:

@SpringBootConfiguration

@EnableAutoConfiguration

@ComponentScan(

excludeFilters = {@Filter(

type = FilterType.CUSTOM,

classes = {TypeExcludeFilter.class}

), @Filter(

type = FilterType.CUSTOM,

classes = {AutoConfigurationExcludeFilter.class}

)}

)

public @interface SpringBootApplication {

}

其中@SpringBootConfiguration底层是@Configuration注解,它表示主启动类是一个配置类;而@ComponentScan是扫描注解,它默认扫描的是主启动类当前包及其子包下的组件;最关键的就是@EnableAutoConfiguration注解了,该注解便实现了自动配置。

查看@EnableAutoConfiguration注解的源码,又会发现它是由两个注解组合而成的:

@AutoConfigurationPackage

@Import({AutoConfigurationImportSelector.class})

public @interface EnableAutoConfiguration {

}

我们继续查看@AutoConfigurationPackage注解的源码:

@Import({Registrar.class})

public @interface AutoConfigurationPackage {

}

@Import注解我们非常熟悉,它是用来导入一个组件的,然而它比较特殊:

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

Registrar() {

}

public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {

AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));

}

public Set determineImports(AnnotationMetadata metadata) {

return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));

}

}

这里的 Registrar 组件中有两个方法,它是用来导入一系列组件的,而该注解又被间接标注在了启动类上,所以它会将主启动类所在包及其子包下的所有组件均注册到容器中。

接下来我们继续看@EnableAutoConfiguration的第二个合成注解:@Import({AutoConfigurationImportSelector.class}) 该注解也向容器中注册了一个组件,翻阅该组件的源码:

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

public String[] selectImports(AnnotationMetadata annotationMetadata) {

if (!this.isEnabled(annotationMetadata)) {

return NO_IMPORTS;

} else {

AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);

return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());

}

}

}

这个方法是用来选择导入哪些组件的,该方法又调用了getAutoConfigurationEntry()方法得到需要导入的组件,所以我们查看该方法:

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {

if (!this.isEnabled(annotationMetadata)) {

return EMPTY_ENTRY;

} else {

AnnotationAttributes attributes = this.getAttributes(annotationMetadata);

List configurations = this.getCandidateConfigurations(annotationMetadata, attributes);

configurations = this.removeDuplicates(configurations);

Set exclusions = this.getExclusions(annotationMetadata, attributes);

this.checkExcludedClasses(configurations, exclusions);

configurations.removeAll(exclusions);

configurations = this.getConfigurationClassFilter().filter(configurations);

this.fireAutoConfigurationImportEvents(configurations, exclusions);

return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);

}

}

在getCandidateConfigurations()方法处打一个断点,通过debug运行后我们可以发现,configurations集合中就已经得到了127个自动配置类:

那么这些类究竟从何而来呢?我们需要探究一下getCandidateConfigurations()方法做了什么操作,它其实是调用了loadFactoryNames()方法:

List configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());

最终调用的是loadSpringFactories()方法来得到一个Map集合:

private static Map<String, List> loadSpringFactories(@Nullable ClassLoader classLoader) {

MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);

if (result != null) {

return result;

} else {

try {

Enumeration urls = classLoader != null ? classLoader.getResources(“META-INF/spring.factories”) : ClassLoader.getSystemResources(“META-INF/spring.factories”);

LinkedMultiValueMap result = new LinkedMultiValueMap();

}

}

}

可以看到,它其实是从 META-INF/spring.factories 文件中获取的组件,我们可以看看导入的依赖中:

在spring-boot-autoconfigure-2.3.7.RELEASE.jar的META-INF目录下就有一个spring.factories文件,打开看看文件内容:

# Initializers

org.springframework.context.ApplicationContextInitializer=\

org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\

org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners

org.springframework.context.ApplicationListener=\

org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configuration Import Listeners

org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\

org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters

org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\

org.springframework.boot.autoconfigure.condition.OnBeanCondition,\

org.springframework.boot.autoconfigure.condition.OnClassCondition,\

org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

# Auto Configure

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\

org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\

org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\

org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\

org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\

文件里的内容其实就是在最开始需要注册的组件,这些组件都是一些配置类,只要项目一启动,Spring Boot就会将这些配置类全部注册到容器中。

按需开启自动配置

虽然配置类会被 Spring Boot 自动注册到容器中,但并不是每个配置类都会默认生效,SpringBoot会根据当前的场景按需开启自动配置。比如Thymeleaf模板引擎的自动配置类:

@ConditionalOnClass注解的作用是检查当前项目是否有指定的.class文件,若有则生效;否则不生效。因为我们并未引入Thymeleaf的依赖,导致TemplateMode.class和SpringTemplatengine.class都是不存在的,所以ThymeleafAutoCinfiguration并不会生效。

修改默认配置

既然SpringBoot帮助我们进行了大量的自动配置,那么对于特殊的一些应用场景,我们该如何修改它的默认配置呢?如果你不了解SpringBoot的配置原理,那么当你需要修改默认配置时,你肯定是束手无策的。我们可以找到SpringMVC的默认配置,看看SpringBoot是如何帮我们进行配置的:

@EnableConfigurationPropertie(WebMvcProperties.class)注解在之前也有介绍,它是用来开启指定类的配置绑定的,所以我们来看看WebMvcProperties类:

@ConfigurationProperties(prefix = “spring.mvc”)

public class WebMvcProperties {

}

配置绑定的前缀时spring.mvc,所以我们若是想修改SpringBoot的默认配置,则必须要将前缀写为spring.mvc,至于我们可以修改哪些配置,只需要查看该类中有哪些成员变量即可,比如:

public static class View {

private String prefix;

private String suffix;

public String getPrefix() {

return this.prefix;

}

public void setPrefix(String prefix) {

this.prefix = prefix;

}

public String getSuffix() {

return this.suffix;

}

public void setSuffix(String suffix) {

this.suffix = suffix;

}

}

在WebMvcProperties类中有这样一个内部类,内部类中有prefix和suffix两个成员变量,它们是分别用来设置视图的前缀和后缀的,所以我们若想进行配置,则需要在配置文件中这样编写:

spring.mvc.view.prefix=/views/

spring.mvc.view.suffix=.html

传统的Spring开发Web需要编写大量的配置,而使用SpringBoot将免去编写配置的操作,直接面向业务逻辑开发,一起来看看该如何使用SpringBoot进行Web开发吧!

03

Web开发

静态资源处理

Spring Boot默认设置了几个静态资源目录:

  • /static

  • /public

  • /resources

  • /META-INF/resources

这几个目录需要建立在类路径下,若如此做,则放置在这些目录下的静态资源可以被直接访问到。

也可以通过配置来设置资源的访问前缀:

spring.mvc.static-path-pattern=/res

此时若想访问静态资源,就必须添加res前缀才行。

我们还可以修改Spring Boot的默认资源路径,只需添加配置:

spring.web.resources.static-locations=classpath:/myImg

若如此做,则我们只能将静态资源放在myImg目录下,之前的所有静态资源目录都将失效。

欢迎页

Spring Boot提供了两种方式来实现欢迎页,第一种便是在资源目录放置欢迎页:

Title

SpringBoot Index!

访问结果:

第二种方式是通过Controller处理/index请求:

@Controller

public class HelloController {

@RequestMapping(“/”)

public String toIndex(){

return “hello”;

}

}

Favicon


Spring Boot也提供了自动设置网站图标的方式,只需要将名为 favicon.ico 的图片放在静态资源目录下即可:

Rest映射

在Spring Boot中,默认已经注册了HiddenHttpMethodFilter,所以可以直接编写Rest风格的url,只需在表单中添加一个_method属性的请求域即可:

Title

编写Controller处理请求:

@RestController

public class HelloController {

@GetMapping(“/user”)

public String getUser(){

return “Get”;

}

@PostMapping(“/user”)

public String postUser(){

return “Post”;

}

@DeleteMapping(“/user”)

public String deleteUser(){

return “Delete”;

}

@PutMapping(“/user”)

public String putUser(){

return “Put”;

}

}

最后需要在配置文件中开启对Rest的支持:

spring.mvc.hiddenmethod.filter.enabled=true

04

常用参数及注解

下面介绍Web开发中的一些常用参数和注解。

@PathVariable


该注解用于获取路径变量,比如:

@GetMapping(“/user/{id}”)

public String getUser(@PathVariable(“id”) Integer id){

return id + “”;

}

此时若请求url为http://localhost:8080/user/2,则获取到id值为2。

@RequestHeader


该注解用于获取请求头,比如:

@GetMapping(“/header”)

public String getHeader(@RequestHeader(“User-Agent”) String userAgent){

return userAgent;

}

它还能够通过一个Map集合获取所有的请求头信息:

@GetMapping(“/header”)

public Map<String, String> getHeader(@RequestHeader Map<String,String> headers){

return headers;

}

@RequestParam


该注解用于获取请求参数,比如:

@GetMapping(“/param”)

public String getParam(@RequestParam(“name”) String name,

@RequestParam(“age”) Integer age){

return name + “:” + age;

}

此时若请求url为http://localhost:8080/param?name=zhangsan&age=20,则得到值 zhangsan:20 。

@CookieValue


该注解用于获取Cookie值,比如:

@GetMapping(“/cookie”)

public String getCookie(@CookieValue(“Idea-8296e76f”) String cookie) {

return cookie;

}

它还可以通过Cookie键名获取一个Cookie对象:

@GetMapping(“/cookie”)

public String getCookie(@CookieValue(“Idea-8296e76f”) Cookie cookie) {

return cookie.getName();

}

@RequestBody


该注解用于获取获取请求体的值,比如:

@PostMapping(“/body”)

public String getBody(@RequestBody String content) {

return content;

}

既然是获取请求体的值,那么只有Post请求才有请求体,所以编写一个表单:

Title

账号:


密码:


通过该表单提交数据后,得到 username=admin&password=123 。

@RequestAttribute


该注解用于获取request域的数据,比如:

@GetMapping(“/success”)

public String success(@RequestAttribute(“msg”) String msg){

return msg;

}

通过键名即可获取request域中的数据。

@MatrixVariable


该注解用于获取矩阵变量,比如:

@GetMapping(“/matrix/{path}”)

public String getMatrix(@MatrixVariable(“name”) String name,

@MatrixVariable(“age”) Integer age,

@PathVariable(“path”) String path) {

return path + “—” + name + “:” + age;

总结

一般像这样的大企业都有好几轮面试,所以自己一定要花点时间去收集整理一下公司的背景,公司的企业文化,俗话说「知己知彼百战不殆」,不要盲目的去面试,还有很多人关心怎么去跟HR谈薪资。

这边给大家一个建议,如果你的理想薪资是30K,你完全可以跟HR谈33~35K,而不是一下子就把自己的底牌暴露了出来,不过肯定不能说的这么直接,比如原来你的公司是25K,你可以跟HR讲原来的薪资是多少,你们这边能给到我的是多少?你说我这边希望可以有一个20%涨薪。

最后再说几句关于招聘平台的,总之,简历投递给公司之前,请确认下这家公司到底咋样,先去百度了解下,别被坑了,每个平台都有一些居心不良的广告党等着你上钩,千万别上当!!!

Java架构学习资料,学习技术内容包含有:Spring,Dubbo,MyBatis, RPC, 源码分析,高并发、高性能、分布式,性能优化,微服务 高级架构开发等等。

还有Java核心知识点+全套架构师学习资料和视频+一线大厂面试宝典+面试简历模板可以领取+阿里美团网易腾讯小米爱奇艺快手哔哩哔哩面试题+Spring源码合集+Java架构实战电子书。
在这里插入图片描述

l lang=“en”>

Title

账号:


密码:


通过该表单提交数据后,得到 username=admin&password=123 。

@RequestAttribute


该注解用于获取request域的数据,比如:

@GetMapping(“/success”)

public String success(@RequestAttribute(“msg”) String msg){

return msg;

}

通过键名即可获取request域中的数据。

@MatrixVariable


该注解用于获取矩阵变量,比如:

@GetMapping(“/matrix/{path}”)

public String getMatrix(@MatrixVariable(“name”) String name,

@MatrixVariable(“age”) Integer age,

@PathVariable(“path”) String path) {

return path + “—” + name + “:” + age;

总结

一般像这样的大企业都有好几轮面试,所以自己一定要花点时间去收集整理一下公司的背景,公司的企业文化,俗话说「知己知彼百战不殆」,不要盲目的去面试,还有很多人关心怎么去跟HR谈薪资。

这边给大家一个建议,如果你的理想薪资是30K,你完全可以跟HR谈33~35K,而不是一下子就把自己的底牌暴露了出来,不过肯定不能说的这么直接,比如原来你的公司是25K,你可以跟HR讲原来的薪资是多少,你们这边能给到我的是多少?你说我这边希望可以有一个20%涨薪。

最后再说几句关于招聘平台的,总之,简历投递给公司之前,请确认下这家公司到底咋样,先去百度了解下,别被坑了,每个平台都有一些居心不良的广告党等着你上钩,千万别上当!!!

Java架构学习资料,学习技术内容包含有:Spring,Dubbo,MyBatis, RPC, 源码分析,高并发、高性能、分布式,性能优化,微服务 高级架构开发等等。

还有Java核心知识点+全套架构师学习资料和视频+一线大厂面试宝典+面试简历模板可以领取+阿里美团网易腾讯小米爱奇艺快手哔哩哔哩面试题+Spring源码合集+Java架构实战电子书。
[外链图片转存中…(img-Pf52iyQ4-1714235496289)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 29
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
学习尚硅谷视频整理的文档 Spring Boot 1 1 Spring Boot入门 4 1.1 简介 4 1.2 微服务(martin fowler发表了一篇文章) 5 1.3 环境约束 7 1.4 第一个Spring Boot项目(jar):HelloWorld 8 1.5 入门案例详解 11 1.5.1 POM文件 11 1.5.2 主程序类,主入口类 12 1.6 使用Spring Initializer向导快速创建Spring Boot 16 2 Spring Boot配置 18 2.1 配置文件 18 2.2 YML语法 19 2.3 YML配置文件值获取 21 2.4 properties配置文件乱码问题 24 2.5 @ConfigurationProperties与@Value的区别 25 2.6 配置@PropertySource、@ImportResource、@Bean 27 2.7 配置文件占位符 30 2.8 Profile多环境支持 31 2.9 配置文件的加载位置 33 2.10 外部配置加载顺序 36 2.11 自动配置原理 37 2.12 @Conditional派生注解 41 3 Spring Boot与日志 42 3.1 日志框架分类和选择 42 3.2 SLF4j使用 43 3.3 其他日志框架统一转换成slf4j+logback 44 3.4 Spring Boot日志使用 45 3.5 Spring Boot默认配置 47 3.6 指定日志文件和日志Profile功能 52 3.7 切换日志框架(不使用SLF4j+LogBack) 54 4 Spring Boot与Web开发 55 4.1 Web开发简介 55 4.2 静态资源映射规则 56 4.3 引入Thymeleaf 60 4.4 Thymeleaf语法 61 4.5 SpringMVC自动配置原理 67 4.6 SpringBoot扩展与全面接管 70 4.7 如何修改SpringBoot的默认配置 72 4.8 【实验】CRUD操作 73 4.8.1 默认访问首页 73 4.8.2 登录页面国际化 74 4.8.3 登录 80 4.8.4 拦截器进行登录检查 81 4.8.5 实验要求(没按要求做,不想改了!) 82 4.8.6 CRUD-员工列表 83 4.8.7 CRUD-员工修改 86 4.8.8 CRUD-员工添加 87 4.8.9 CRUD-员工删除 88 4.9 错误处理原理&错误页面定制 90 4.10 配置嵌入式Servlet容器(springboot 1.50本) 97 4.10.1 如何定制和修改Servelt容器的相关配置 97 4.10.2 注册servlet三大组件【servlet,filter,listener】 98 4.10.3 替换为其他嵌入式容器 102 4.10.4 嵌入式servlet容器自动配置原理 103 4.10.5 嵌入式servlet容器启动原理 103 4.11 使用外置的Servlet容器 104 4.11.1 步骤 104 4.11.2 原理 107 5 Spring Boot与Docker(虚拟化容器技术) 110 5.1 简介 110 5.2 核心概念 111 5.3 安装Docker 112 5.4 Docker常用命令&操作 113 5.5 安装MySQL示例 114 6 Spring Boot与数据访问 115 6.1 JDBC 115 6.1.1 实现 115 6.1.2 自动配置原理 116 6.2 整合Durid数据源 117 6.3 整合Mybatis 122 6.3.1 注解 123 6.3.2 配置文件 124 6.4 整合SpringData JPA 125 6.4.1 SpringData简介 125 6.4.2 整合 126 7 Spring Boot启动配置原理 128 7.1 启动流程(Springboot 1.50本) 128 7.1.1 创建SpringApplication对象 129 7.1.2 运行run方法 130 7.1.3 编写事件监听机制 132 8 Spring Boot自定义starters 136 8.1 概述 136 8.2 步骤 137 9 更多Springboot整合示例 144 10 Spring Boot与缓存 145 10.1 JSR107缓存规范 145 10.2 Spring的缓存抽象 146 10.2.1 基本概念 146 10.2.2 整合项目 146 10.2.3 CacheEnable注解 148 10.2.4 Cache注解 150 10.3 整合redis 154 10.3.1 在Docker上安装redis 154 10.3.2 Redis的Template 154 10.3.3 整合(百度) 155

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值