Spring、SpringMVC、Mybatis

一.Spring基础

1.Spring 框架是什么

Spring 是一款开源的轻量级 Java 开发框架,我们一般说 Spring 框架指的都是 Spring Framework,它是很多模块的集合,例如,Spring core、Spring JDBC、Spring MVC 等,使用这些模块可以很方便地协助我们进行开发。

2.Spring 包含的模块有哪些

Spring4.x 版本

85d7c8d0051f4eaa8303ee7fac097368.png

Spring5.x 版本

c9afd14996dc4361b59567012fdbed18.png

Spring5.x 版本中 Web 模块的 Portlet 组件已经被废弃掉,同时增加了用于异步响应式处理的 WebFlux 组件。

Spring 各个模块的依赖关系如下:

8f8eab0f4af34ece8948498de22a544e.png

Core Container:Spring 框架的核心模块,也可以说是基础模块,主要提供 IoC、DI 等功能的支持。Spring 其他所有的功能基本都需要依赖于该模块,我们从上面那张 Spring 各个模块的依赖关系图就可以看出来。以下模块为 Core Container 的子模块

  • spring-core:Spring 框架基本的核心工具类。

  • spring-beans:提供对 bean 的创建、配置和管理等功能的支持。

  • spring-context:提供对国际化、事件传播、资源加载等功能的支持。

  • spring-expression:提供对表达式语言(Spring Expression Language) SpEL 的支持,只依赖于 core 模块,不依赖于其他模块,可以单独使用。

AOP

  • spring-aspects:该模块为与 AspectJ 的集成提供支持。

  • spring-aop:提供了面向切面编程的实现。

  • spring-instrument:提供了为 JVM 添加代理的功能。具体来讲,它为 Tomcat 提供了一个织入代理,能够为 Tomcat 传递类文件,就像这些文件是被类加载器加载的一样。没有理解也没关系,这个模块的使用场景非常有限。

Data Access/Integration

  • spring-jdbc:提供了对数据库访问的抽象 JDBC。不同的数据库都有自己独立的 API 用于操作数据库,而 Java 程序只需要和 JDBC API 交互,这样就屏蔽了数据库的影响。

  • spring-tx:提供对事务的支持。

  • spring-orm:提供对 Hibernate、JPA、iBatis 等 ORM 框架的支持。

  • spring-oxm:提供一个抽象层支撑 OXM(Object-to-XML-Mapping),例如:JAXB、Castor、XMLBeans、JiBX 和 XStream 等。

  • spring-jms : 消息服务。自 Spring Framework 4.1 以后,它还提供了对 spring-messaging 模块的继承。

Spring Web

  • spring-web:对 Web 功能的实现提供一些最基础的支持。

  • spring-webmvc提供对 Spring MVC 的实现

  • spring-websocket:提供了对 WebSocket 的支持,WebSocket 可以让客户端和服务端进行双向通信。

  • spring-webflux:提供对 WebFlux 的支持。WebFlux 是 Spring Framework 5.0 中引入的新的响应式框架。与 Spring MVC 不同,它不需要 Servlet API,是完全异步。

Messaging

spring-messaging 是从 Spring4.0 开始新加入的一个模块,主要职责是为 Spring 框架集成一些基础的报文传送应用。

Spring Test

Spring 团队提倡测试驱动开发(TDD)。有了控制反转 (IoC)的帮助,单元测试和集成测试变得更简单。

Spring 的测试模块对 JUnit(单元测试框架)、TestNG(类似 JUnit)、Mockito(主要用来 Mock 对象)、PowerMock(解决 Mockito 的问题比如无法模拟 final, static, private 方法)等等常用的测试框架支持的都比较好。

3.Spring、Spring MVC、Spring Boot 之间的关系是什么?

  • Spring 一般指的是 SpringFramework,他是很多模块的集合。

  • Spring MVC 就是 Spring 众多模块中的一个,用来构建 MVC 架构的 Web 程序。

  • Spring 目的是简化应用程序的开发,但使用 Spring 进行开发,配置起来过于麻烦。因此,出现了Spring Boot,目的是简化 Spring 开发。

7207e81fd0bf478eb9794e7f5134e7a2.png

二.Spring IoC

1.谈谈自己对于 Spring IoC 的理解

IoC(Inversion of Control,控制反转)是一种设计思想,Spring Ioc 是对 Ioc 思想进行了实现。IoC 的思想就是将原本在程序中手动创建对象的控制权,交由 Ioc 容器。并且,对象之间的依赖关系和对象的依赖注入也交给 IoC 容器来管理。这样可以把应用从复杂的依赖关系中解放出来,很大程度上简化应用的开发。

63a4dd0841a648d08a1c177182e5f771.png

在实际项目中一个 Service 类可能依赖了很多其他的类,假如我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IoC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。

Note:

  • IoC是一种思想,在其他语言中也有对 IoC 思想的实现,并非 Spring 特有的。

  • 在 Spring 中, IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个 Map,Map 中存放的是各种对象。

2.什么是 Spring Bean?

Spring Bean 指的就是那些被 IoC 容器所管理的对象。我们可以通过配置文件或者注解的方式,告诉 IoC 容器,需要帮助我们管理哪些对象。

<bean id="..." class="...">
   <constructor-arg value="..."/>
</bean>

3.说一下依赖注入的方式有哪些

  • 属性注入:通过在属性上添加 @Autowired 注解注入,不需要提供属性的构造方法和 set 方法。

  • 构造方法注入:通过提供属性的构造方法,然后在构造方法上面添加 @Autowired 注解或者在xml 配置文件中进行配置。

  • set 注入:通过提供属性的 set 方法,然后在构造方法上面添加 @Autowired 注解或者在配置文件中进行配置。

4.Spring 的常见注解有哪些?

  • 第一类是声明 bean 的注解,有 @Component、@Controller、@Service、@Repository、@Bean

  • 第二类是依赖注入相关的,有 @Autowired、@Resourse

  • 第三类是设置 bean 的作用域 @Scope

  • 第四类是 Spring 配置相关的,比如 @Configuration,@ComponentScan

  • 第五类是跟 AOP 相关做增强的注解 @Aspect、@Pointcut、@Before、@After、@Around

5.@Component 注解和 @Bean 注解的区别是什么?

  • @Component 注解定义在类上,而 @Bean 注解定义在配置类中的方法。

  • @Component 注解可以标识任意类生成一个对象,加入到 IoC 容器,@Bean 注解一般用来将第三方技术的对象或者自定义的对象加入到 IoC 容器。

6.@Autowired 注解和 @Resource 注解的区别是什么?

  • @Autowired 注解是 Spring 提供的,优先根据类型注入,当根据类型无法注入的时候,比如一个接口有多个实现类,会根据名称注入。

  • @Resource 注解是 JDK 提供的,优先根据名称注入,当根据名称无法注入的时候,会根据类型注入。

  • @Autowired 注解支持在属性、构造函数、方法参数、方法上使用。@Resource 注解只能在属性和方法上使用。

7.Bean 的作用域有哪些?

Spring 中 Bean 的作用域最常用的是下面二种:

  • Singleton:在 Singleton 作用域下 IoC 容器中的 bean 都是唯一的,Ioc 容器中 bean 的作用域默认都是 Singleton。

  • Prototype:在 Prototype 作用域下每次获取 bean,都会创建一个新的 bean 实例。

下面这些仅在 Web 应用中可用

  • request(请求 bean)每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效。

  • session(会话 bean)每一次来自新 session 的 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效。

  • application/global-session(应用 Bean)每个 Web 应用在启动时创建一个 Bean,该 bean 仅在当前应用启动时间内有效。

  • websocket:每一次 WebSocket 会话产生一个新的 bean。

如何配置 bean 的作用域呢?

xml 方式:

<bean id="..." class="..." scope="singleton"></bean>

注解方式:

@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Person personPrototype() {
    return new Person();
}

8.Bean 是线程安全的吗?

Spring 框架中的 Bean 是否线程安全,取决于其作用域和是否包含可变的成员变量。比如说以最常用的两种作用域 singleton 和 prototype 为例。

  • singleton 作用域下,IoC 容器中每个 bean 都是唯一的,如果这个 bean 中存在可变的成员变量的话,那就存在线程安全问题。

  • prototype 作用域下,每次获取都会创建一个新的 bean 实例,不会存在线程安全问题。

但是,在 singleton 作用域下,其实大部分 Bean 都是无状态的。比如 Dao、Service。对于有状态单例 Bean 的线程安全问题,常见的有两种解决办法:

  • 在 Bean 中尽量避免定义可变的成员变量。

  • 在类中定义一个 ThreadLocal 成员变量,将需要的可变成员变量保存在 ThreadLocal 中。

可变成员变量必须符合二个条件,一是必须有成员变量,二是有方法中存在对这个变量修改的代码

有状态的 bean 举例:

在下面的例子中,count 是一个有状态的 Bean,因为它有一个成员变量 count,该变量可以在 increment 方法调用时发生改变。如果将该 Bean 声明为 Singleton 作用域,则该 bean 存在线程安全问题。

@Component
public class Counter {
    private int count;

    public void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

9.Spring 的三级缓存了解吗?

10.Spring 中的循环引用及其解决方法

循环引用其实就是循环依赖,也就是两个或两个以上的 bean 互相持有对方,形成了闭环。比如 A依赖于 B,B 依赖于 A。

11.Bean 的生命周期了解么?

三.Spring AOP

1.谈谈自己对于 AOP 的了解

AOP 的意思是面向切面编程,能够将一些和业务没有关系,但是被业务模块共同调用的功能封装起来。例如,事务管理、日志管理、权限控制等,AOP 可以减少系统的重复代码,降低模块间的耦合,有利于系统未来的可拓展性和可维护性。

Spring AOP 是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理,如下图所示:

4669353b38694762959793ba0ea9a404.png

AOP 切面编程涉及到的一些专业术语:

f39ffcf47b1b4726b7ba0fc269a04ca6.png

几个概念的关系图可以参考下图: 

3a61dc02148c421a9e6dd1faef5f5281.png

2.AOP当中通知的类型有哪些?

  • Before(前置通知):在目标对象的方法调用之前触发

  • After (后置通知):在目标对象的方法调用之后触发,无论方法是否抛出异常

  • AfterReturning(返回后通知):在目标对象的方法正常返回结果后触发,方法出现异常时不会执行。

  • AfterThrowing(异常通知):在目标对象的方法执行过程中,出现异常后触发。AfterReturning 和 AfterThrowing 两者互斥。如果方法调用成功无异常,则会有返回值;如果方法抛出了异常,则不会有返回值。

  • Around (环绕通知):在目标对象的方法调用前后执行。这里的前后并不是拦截器、过滤器那种前后,而是在方法内调用连接点,然后在连接点前后执行不同的代码逻辑

3.多个切面的执行顺序如何控制?

使用 @Order 注解直接定义切面顺序

// 值越小优先级越高
@Order(3)
@Component
@Aspect
public class LoggingAspect implements Ordered {

实现 Ordered 接口重写 getOrder 方法。

@Component
@Aspect
public class LoggingAspect implements Ordered {

    // ....

    @Override
    public int getOrder() {
        // 返回值越小优先级越高
        return 1;
    }
}

四.Spring MVC

1.说说自己对于 Spring MVC 了解?

Spring MVC 是 Spring 中的一个模块,用来构建 MVC 架构的 Web 应用程序。MVC 是模型(Model)、视图(View)、控制器(Controller)的缩写。相比于 Struts2 等 MVC 框架,Spring MVC 使用起来更加简单,运行速度更快。

2.Spring MVC 常见的注解有哪些?

  • @RequestMapping:用来定义请求路径。

  • @ResponseBody:用来将 controller 层方法返回的对象转为 json 字符串,响应给客户端。

  • @RequestHeader:获取指定的请求头数据。一般用在属性和方法参数上。

跟 HTTP 的请求方法相关的注解

  • @GetMapping:GET 请求。

  • @PostMapping:POST 请求。

  • @PutMapping:PUT 请求。

  • @DeleteMapping:DELETE 请求。

用来接收请求参数的一些注解

  • @RequestParam:@RequestParam 用于获取查询参数。

  • @Pathvariable:@PathVariable 用于获取路径参数

  • @RequestBody:@RequestBody 用来接收 http 请求体中的 JSON 字符串,并将 JSON 字符串转为 java 对象。

系统会使用 HttpMessageConverter 或者自定义的 HttpMessageConverter 将请求的 body 中的 json 字符串转换为对。

3.Spring MVC 的核心组件有哪些?

  • DispatcherServlet:核心的中央处理器,负责接收客户端的请求、并调用其它组件对请求进行处理、最后向客户端响应结果。

  • HandlerMapping:处理器映射器,它的作用是根据请求的 URL 去查找能处理请求的 Handler,并将 Handler 和请求涉及到的拦截器一起封装成处理链交给 DispatcherServlet。

  • HandlerAdapter:处理器适配器,适配并执行 HandlerMapping 找到的 Handler。

  • Handler:请求处理器,处理实际请求的处理器。

  • ViewResolver:视图解析器,根据 Handler 返回的逻辑视图,找到真正的视图。

4.Spring MVC 工作原理了解吗?

Spring MVC 原理如下图所示:

49a55bd3eb814309a61899268cd4b303.png

流程说明(重要):

  1. 客户端发送请求,DispatcherServlet 拦截请求。

  2. DispatcherServlet 会调用 HandlerMapping。HandlerMapping 根据 URL 去查找能处理请求的 Handler,并将Handler和请求涉及到的拦截器封装成一个处理链返回 DispatcherServlet 。

  3. DispatcherServlet 调用 HandlerAdapter 适配器执行 Handler。

  4. Handler 会对用户请求进行处理,然后返回一个 ModelAndView 对象给 DispatcherServlet。

  5. DispatcherServlet 会去调用 ViewResolver,ViewResolver 会根据逻辑 View 查找实际的 View 返回给 DispatcherServlet。

  6. 最后,DispatcherServlet 通过 Model 对 View 进行视图渲染,并把渲染后的 View 返回给客户端。

Note:

  • ModelAndView 包含了数据模型以及相应的视图信息。Model 是返回的数据对象,View 是个逻辑上的 View。
  • Handler 就是 Controller 层中的方法。
  • 在 Spring MVC 中,传递给视图解析器的逻辑视图是一个字符串,通常称为视图名称或逻辑视图名称。这个视图名称是在控制器方法中返回的,用于标识要呈现给用户的视图的逻辑名称。
  • 在前后端分离的情况下,渲染后的 view 可能是 JSON、XML 的数据格式,SpringMVC 会将这些数据发送给前端。

5.统一异常处理怎么做?

一般使用 @ControllerAdvice + @ExceptionHandler 这两个注解进行统一异常处理,通过这二个注解会给所有或者指定的 Controller 织入异常处理的逻辑(AOP),当 Controller 中的方法抛出异常的时候,会由 @ExceptionHandler 注解中的异常类型对异常进行匹配,匹配成功后由被修饰的方法进行处理。

@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {

    @ExceptionHandler(BaseException.class)
    public ResponseEntity<?> handleAppException(BaseException ex, HttpServletRequest request) {
      //......
    }

    @ExceptionHandler(value = ResourceNotFoundException.class)
    public ResponseEntity<ErrorReponse> handleResourceNotFoundException(ResourceNotFoundException ex, HttpServletRequest request) {
      //......
    }
}

五.Spring 事务

Spring 的事务是基于数据库的,如果数据库不支持事务,那Spring的事务也会失效,比如MySQL采用了 MyISAM 存储引擎。

1.Spring 的事务是如何实现的?

Spring 实现的事务是基于 AOP 完成的,在执行方法之前开启事务,在执行完方法之后根据执行情况提交或者回滚事务。

2.Spring 管理事务的方式有几种?

  • 编程式事务:通过 TransactionTemplate 或者 TransactionManager 手动管理事务,需要在代码中硬编码,实际应用中很少使用。

  • 声明式事务:通过使用 @Transactional 注解或者在 XML 配置文件中配置使用,基于 AOP 实现。

3.@Transactional 注解了解吗?

@Transactional 是一个事务注解

  • 当 @Transactional 注解作用于类上时,该类的所有方法都会具有该类型的事务

  • 当 @Transactional 注解作用于方法上时,该方法将具有该类型的事务。另外,在方法上的@Transactional 注解会覆盖类上的 @Transactional 注解。

  • 还有要注意一点的是,在 @Transactional 注解中如果不配置 rollbackFor = Exception.class 属性,那么事务只有遇到运行时异常的时候才会回滚,其它类型的异常都不会回滚。

Note:其它类型的异常可能是抛出的自定义异常或者编译时异常。

4.Spring 的事务传播行为是什么,有哪几种? 

Spring 的事务传播行为是指当一个事务方法被另一个方法调用时,这个事务方法应该如何传播。事务的传播行为有很多种,常用的有三种(前三种):

  • Required(默认):在 Required 传播行为下,如果要加入的方法有事务,就加入到该事务;如果没有事务,就开启新的事务。

  • Supports:在 Supports 传播行为下,如果要加入的方法有事务,就加入到该事务;如果没有事务,不会开启新的事务。

  • Requireds_new:在 Required_new 传播行为下,无论要加入的方法是否存在事务,都会创建新的事务。

f166fe4f679046aba4d2b96942236d40.png

Note:

  • 在Spring中,事务传播行为是由被调用方法的事务属性来决定的,而不是由调用方的事务属性来决定。

  • 新建的事务只包含加入方法自己的代码。

  • 当一个事务方法调用另一个方法时,如果被调用的方法的事务传播行为被设置为REQUIRES_NEW(创建一个新的事务),那么当前事务将被挂起。事务挂起指的是当前事务暂停执行,直到新的事务完成后才会继续执行。也就是,新事务的成功与否不会影响到被挂起的事务。

5.Spring 事务的隔离级别有哪几种?

和事务传播行为这块一样,为了方便使用,Spring 也相应地定义了一个枚举类:Isolation

public enum Isolation {

    //采用数据库的默认隔离级别
    DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),

    //读未提交
    READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),

    //读已提交
    READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),

    //可重复读
    REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),

    //串行化
    SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);

    private final int value;

    Isolation(int value) {
        this.value = value;
    }

    public int value() {
        return this.value;
    }

}
  • Default(默认):采用数据库的默认隔离级别。

  • READ_UNCOMMITTED(读未提交):读未提交指的是允许一个事务读取另一个事务未提交的数据,可能会导致脏读、幻读、不可重复读。

  • READ-COMMITTED(读已提交):读已提交指的是一个事物提交之后,它做的变更才会被其他事务看到,可以阻止脏读,但是幻读或不可重复读仍有可能发生。

  • REPEATABLE-READ(可重复读):可重复读指的是对相同数据的多次读取,结果都是一致的,除非数据是事务本身所修改的,可以阻止脏读和不可重复读,但幻读仍有可能发生。

  • SERIALIZABLE(可串行化):所有的事务依次逐个执行,这样事务之间就不可能产生干扰,该级别可以防止并发事务产生的所有问题。

6.Spring 中事务失效的场景有哪些

  • 事务方法的访问控制修饰符不是 public,事务会失效。

  • 在 @Transactional 注解上没有配置好 rollbackFor 属性为 Exception,事务可能会失效。

  • 事务方法没有使用代理对象调用,事务会失效。

六.Mybatis

1.Mybatis是什么

MyBatis是一个开源的持久层框架,是用Java编写的,主要用于简化访问数据库的代码编写。MyBatis的核心思想是将SQL语句和Java代码解耦,它提供了一种通过XML或注解的方式来配置SQL映射关系,从而使得开发人员可以将SQL语句与Java方法分离,避免在Java代码中硬编码SQL语句,降低了代码的耦合度。

2.Mybaits的优缺点

优点:

  • 简化的XML或注解配置:MyBatis允许开发人员使用XML或注解的方式来配置SQL映射关系,将 SQL 与程序代码进行了解耦。

  • 动态SQL:MyBatis提供了灵活的动态SQL功能,可以根据条件动态生成SQL语句,方便实现动态查询。 

  • 易于集成:MyBatis 可以与 Spring 等常用的 Java 框架进行集成,便于在企业应用中使用。

  • 与各种数据库兼容:因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持。

缺点:

  • SQL语句依赖:MyBatis需要开发人员手动编写SQL语句,如果SQL语句编写不当,可能会导致性能问题或者安全隐患。

  • 关联查询:相较于Hibernate等ORM框架,MyBatis在处理复杂的关联查询时不那么方便。

3.#{} 和 ${} 的区别是什么?

Mybatis 在处理 ${} 时,就是把 ${} 直接替换成变量的值。而 Mybatis 在处理 #{} 时,会对 sql 语句进行预处理,将 sql 中的 #{} 替换为 ? 号,调用 PreparedStatement 的 set 方法来赋值。因此,在安全性上, #{} 可以防止SQL注入, ${} 不可以。

4..xml 映射文件中,有哪些常见的标签?

除了常见的 select、insert、update、delete 标签之外

还有很多其他的标签,比如 <resultMap>、 <parameterMap>、 <sql>、 <include>、 <selectKey> ,加上动态 sql 的 9 个标签, trim、where、set、foreach、if、choose、when、otherwise、bind 等,其中 <sql> 为 sql 片段标签,通过 <include> 标签引入 sql 片段, <selectKey> 为不支持自增的主键生成策略标签。

5.MyBatis 的执行流程

  1. 首先,MyBatis 会读取核心配置文件(mybatis-config.xml),在这个文件中配置了映射文件、数据源、类型别名等信息。

  2. 其次,会构造一个会话工厂 SqlSessionFactory。

  3. 然后,通过会话工厂创建 SqlSession 对象,这里面就包含了执行 SQL 语句的所有方法

  4. 每一个 SqlSession 都会拥有一个 Executor 对象,通过 Executor 对象操作数据库的接口执行 SQL,同时负责查询缓存的维护,Executor 对象的执行方法中有一个 MappedStatement 类型的参数,封装了映射信息

  5. 最后关闭 SqlSession,释放资源

Note:在Spring Boot项目中虽然没有显示的核心配置文件,但实际上MyBatis在启动时仍然会去读取核心配置信息,只是这些信息是隐式的,通过Spring Boot的自动配置来实现。

MappedStatement 封装的映射信息

参数映射:封装了将 Java 对象映射到 SQL 语句中的参数。参数映射规定了如何将方法参数中的值传递给 SQL 语句中的占位符。

结果映射:封装了将 SQL 查询结果映射到 Java 对象的规则。结果映射指定了 SQL 查询返回结果的列与 Java 对象属性之间的对应关系。

e5958cecbee24351b8a235734b9808f0.png

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

真滴book理喻

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值