一些Spring的理解

说说你对Spring的理解

  • 首先Spring是一个生态:可以构建企业级应用程序所需的一切基础设施

  • 但是,通常Spring指的就是Spring Framework,它有两大核心:

  1. IOC和DI

    它的核心就是一个对象管理工厂容器,Spring工厂用于生产Bean,和管理Bean的生命周期,通过控制反转和依赖注入的方式实现系统的高内聚低耦合设计

    • 最小的代价和最小的侵入性使松散的耦合得以实现
    • Bean可以饿汉式和懒加载
    • 集中管理了对象

    实现机制:

    • 简单工厂设计模式:(BeanFactory.getBean())
    • 反射:使用反射创建的对象
  2. AOP

    1. 面向切面编程,允许我们将横切关注点从核心业务逻辑中分离出来,实现代码的模块化和重用。可以方便的实现对程序进行权限拦截、运行监控、日志记录等切面功能。

除了这两大核心还提供了丰富的功能和模块, 数据访问、事务管理、Web开发等。数据访问模块提供了对数据库的访问支持,可以方便地进行数据库操作。事务管理模块提供了对事务的管理支持,确保数据的一致性和完整性。Web开发模块则提供了构建Web应用程序的工具和框架,简化了Web开发的过程。

IOC的实现机制

  • Spring的IoC底层实现机制主要依赖于反射、配置元数据、Bean定义、Bean工厂和依赖注入等技术和组件。通过这些机制,Spring实现了Bean的创建、配置和管理,以及Bean之间的解耦和依赖注入

IOC的加载流程

本质就是Bean的创建过程,分为4个大阶段,Bean通过代码或者XML或者注解进行定义后,从BeanDefintion中加载到内存,通过BeanDefintion中的全类名反射获取到对象,然后会进行依赖的注入进行使用

  1. 在进行new AnnotationConfigApplicationContext()的时候开始加载
  2. 调用BeanFactoryPostProessors(Bean工厂后置处理器)进行扫描,循环将各种类信息转换为BeanDefintion
  3. 将BeanDefintion缓存到BeanDefintionMap中,方便后面实例化的时候使用
  4. BeanFactory通过加载BeanDefintion开始生产Bean,生产前会检查Bean是否合法:是否为单例的,是否为懒加载,是不是抽象的,如果完成了验证则需要推断构造方法,因为Spring的实例化是通过构造方法反射进行的
    • 然后找到存储Bean的各级缓存,通过类名去找,如果找到就不创建
    • 如果没有找到就通过反射进行实例化
  5. 然后判断是否需要属性注入,如果需要注入则将实例化的对象进行属性进行依赖注入(DI)
  6. 调用Aware回调,调用生命周期的回调,接着对类进行增强AOP等
  7. 然后将Bean放进单例池,存到容器中

AOP的加载流程

  1. 在Spring启动时,会通过AspectJ来解析切点表达式,然后根据目标是否实现接口来决定代理方式
  2. 在调用阶段,Spring会通过责任链模式来管理通知的执行顺序,通知包括,前置通知,后置通知,异常通知,最终通知和环绕通知,它们会按照配置的顺序来执行

解释Spring中bean的生命周期

Bean生命周期:指定的就是Bean从创建到销毁的整个过程: 分4大步:

  1. 实例化
    a. 通过反射去推断构造函数进行实例化
    b. 实例工厂、 静态工厂
  2. 依赖注入(DI)
    a. 解析自动装配(byname bytype constractor none @Autowired
  3. 初始化
    a. 调用很多Aware回调方法
    b. 调用BeanPostProcessor.postProcessBeforeInitialization
    c. 调用生命周期回调初始化方法
    d. 调用BeanPostProcessor.postProcessAfterInitialization, 如果bean实现aop则会在这里创建动态代理
  4. 销毁
    a. 在spring容器关闭的时候进行调用
    b. 调用生命周期回调销毁方法

Spring的三级缓存

  1. singletonObjects缓存:这是一级缓存,也被称为“单例池”,用于存储完全初始化完成的单例Bean的实例对象。当Bean被完全初始化并创建后,它会被放入这个缓存中,供后续请求直接获取,避免重复创建。
  2. earlySingletonObjects缓存:这是二级缓存,用于存储尚未完全初始化完成的单例Bean的早期引用。在某些情况下,当一个Bean在创建过程中需要引用另一个尚未完全初始化的Bean时,这个尚未完全初始化的Bean的早期引用就会被放入这个缓存中,以解决循环依赖的问题。
  3. singletonFactories缓存:这是三级缓存,也被称为“单例工厂池”,用于存储用于创建单例Bean的ObjectFactory。在创建单例Bean时,如果发现该Bean依赖于其他的Bean,则需要先创建该依赖的Bean实例,此时Spring会将用于创建该依赖的ObjectFactory保存到三级缓存中。

在Bean的创建过程中,Spring会首先检查三级缓存中是否存在对应的BeanDefinition对象。如果存在,则直接使用该对象来创建Bean实例;如果不存在,则会继续从二级缓存、一级缓存以及父容器中查找

ApplicationContext

  • Spring是Spring的上下文,本质是一个容器,实现了BeanFactory
  • 不会主动生产Bean,而是通知BeanFactory去进行生产
  • 能加载环境变量
  • 支持多语言
  • 实现事件的监听

BeanDefintion

存储的临时的Bean的定义信息,将Bean的定义信息解析后传递给BeanFactory进行对象的生产

BeanFactory 和 FactroyBean 的区别

  1. BeanFactory:

    • 是Spring框架的核心接口之一,用于管理和获取Bean实例。

    • 是一个容器,负责实例化、配置和管理Bean对象。

    • 提供了一系列的方法用于获取Bean,如根据名称获取Bean、根据类型获取Bean等。

    • 可以延迟加载Bean实例,只在使用时才进行实例化。

  2. FactoryBean:

    • 是一个特殊的Bean,实现了FactoryBean接口。

    • 用于自定义Bean的实例化逻辑,可以通过FactoryBean创建复杂的Bean实例。

    • FactoryBean的实现类可以定义自己的逻辑来创建和管理Bean对象,并将其作为一个普通Bean注册到Spring容器中。

    • 在从容器中获取FactoryBean时,实际上获取到的是FactoryBean创建的对象,而不是FactoryBean本身。

Autowired 和 Resource 的区别

@Autowired和@Resource都是用于依赖注入的注解,用于将其他组件或资源自动注入到目标对象中。它们在使用方式和功能上有一些区别:

  1. @Autowired:

    • 是Spring框架的注解,基于类型进行依赖注入。

    • 默认按照类型进行匹配注入,如果存在多个匹配类型的Bean,则根据属性名称进行匹配。

    • 可以用于构造器、字段、方法或者参数上。

    • 支持通过@Qualifier注解指定具体的Bean名称进行注入。

  2. @Resource:

    • 是Java EE规范中定义的注解,在Java 6及以上版本中可用。

    • 基于名称进行依赖注入,先按照名称查找,再按照类型匹配。

    • 默认按照属性名称进行查找,也可以通过name属性指定具体的Bean名称进行注入。

    • 只能用于字段、setter方法或者参数上。

Spring 用到了哪些设计模式

  1. 单例模式:Spring默认使用单例模式来管理和创建Bean对象,确保在整个应用中只有一个实例存在。
  2. 工厂模式:Spring使用工厂模式来创建和管理Bean实例,通过BeanFactory或ApplicationContext等容器来托管和管理Bean的生命周期。
  3. 代理模式:Spring AOP 基于动态代理技术,通过代理对象对目标对象进行增强,实现横切关注点的模块化处理。
  4. 观察者模式:Spring的事件驱动机制基于观察者模式,通过发布-订阅模型实现不同组件之间的解耦和通信。
  5. 模板方法模式:Spring的JdbcTemplate等模板类使用了模板方法模式,将通用的业务逻辑封装在抽象类中,子类可以通过重写特定的方法来实现自己的逻辑。
  6. 策略模式:Spring的事务管理中使用了策略模式,通过配置不同的事务管理策略来适应不同的事务需求。
  7. 装饰器模式:Spring的装饰模式被应用在AOP中,通过动态代理和装饰器模式实现对目标对象的增强

设计模式的单例和Bean单例有什么区别

单例模式主要作用是为了让Java应用程序中,Class有且仅有一个,它的好处是可以避免不必要的重复操作,比如数据库连接,或者工具的初始化等,Spring资源管理器常将一些资源管理器设计为单例模式。

区别

运行环境不一样:设计模式运行在Java程序中,而Bean运行在容器中,一个程序可以有很多个容器,但是一个程序只能有一个JVM,如果

Spring中的单例Bean是线程安全的么

spring只是帮我们创建和管理bean,并不能保证线程安全,如果bean中存在成员变量不能被修改(无状态)是安全的,如果成员变量能被修改(有状态)这个时候不安全,可以使用线程安全的方法处理,如原子类和Threadlocal的方法保证线程安全

为了确保单例Bean的线程安全性,可以采取以下几种方式:

  1. 避免在单例Bean中使用可变的实例变量,或者确保对这些变量的访问是线程安全的,例如使用同步机制(如synchronized关键字)或使用线程安全的数据结构。
  2. 尽量避免在单例Bean中使用共享的外部资源,如数据库连接、文件等。如果必须使用共享资源,需要确保对这些资源的访问是线程安全的(如使用ThreadLocal等)来保证线程安全。
  3. 使用无状态的单例Bean。无状态的单例Bean不包含任何实例变量,只包含方法和局部变量,因此不会有线程安全问题。
  4. 采用

Spring的事务隔离级别

总体来说跟数据库的一致,只是多了一个默认

  1. DEFAULT(默认):使用数据库默认的事务隔离级别。通常为数据库的默认隔离级别,如Oracle为READ COMMITTED,MySQL为REPEATABLE READ。
  2. READ_UNCOMMITTED:最低的隔离级别,允许读取未提交的数据。事务可以读取其他事务未提交的数据,可能会导致脏读、不可重复读和幻读的问题。
  3. READ_COMMITTED:保证一个事务只能读取到已提交的数据。事务读取的数据是其他事务已经提交的数据,避免了脏读的问题。但可能会出现不可重复读和幻读的问题。
  4. REPEATABLE_READ:保证一个事务在同一个查询中多次读取的数据是一致的。事务期间,其他事务对数据的修改不可见,避免了脏读和不可重复读的问题。但可能会出现幻读的问题。
  5. SERIALIZABLE:最高的隔离级别,保证事务串行执行,避免了脏读、不可重复读和幻读的问题。但会降低并发性能,因为事务需要串行执行。

通过@Transactional注解的isolation属性来指定事务隔离级别

@Transactional(readOnly=true) 真的是提高性能的灵丹妙药吗?

  1. 通过执行 SET TRANSACTION READ ONLY,将当前事务设置为只读事务。这意味着在此事务内部,任何修改数据的操作(如 INSERTUPDATEDELETE)都将被禁止,只能执行读取操作(如 SELECT)。
  2. 只读事务依然会运用上隔离级别(MVCC),需要事务隔离级别需要一定性能开销。

如果应用需要保证数据的一致性和隔离性,那么开启只读事务是必要的;如果不需要这些保证,并且追求查询操作的性能优化,那么不开启事务可能更为合适。

Spring的事务传播机制

  1. REQUIRED:如果当前存在事务,则加入该事务,如果当前没有事务,则创建一个新的事务。这是最常用的传播行为,也是默认的,适用于大多数情况。
  2. REQUIRES_NEW:无论当前是否存在事务,都创建一个新的事务。如果当前存在事务,则将当前事务挂起。适用于需要独立事务执行的场景,不受外部事务的影响。
  3. SUPPORTS:如果当前存在事务,则加入该事务,如果当前没有事务,则以非事务方式执行。适用于不需要强制事务的场景,可以与其他事务方法共享事务。
  4. NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,则将当前事务挂起。适用于不需要事务支持的场景,可以在方法执行期间暂时禁用事务。
  5. MANDATORY:如果当前存在事务,则加入该事务,如果当前没有事务,则抛出异常。适用于必须在事务中执行的场景,如果没有事务则会抛出异常。
  6. NESTED:如果当前存在事务,则在嵌套事务中执行,如果当前没有事务,则创建一个新的事务。嵌套事务是外部事务的一部分,可以独立提交或回滚。适用于需要在嵌套事务中执行的场景。
  7. NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。适用于不允许在事务中执行的场景,如果存在事务则会抛出异常。

通过@Transactional注解的propagation属性来指定事务传播行为 。

Spring AOP和 AspectJ AOP

是两种不同的 AOP 实现方式,它们在实现上有一些区别。下面是它们之间的主要区别:

  1. 基于代理 vs 字节码增强:
    • Spring AOP: Spring AOP 是基于代理的 AOP 实现。它利用动态代理技术(JDK 动态代理和 CGLIB)来实现 AOP 横切逻辑的织入。对于基于接口的目标类,Spring 使用 JDK 动态代理;对于没有实现接口的目标类,Spring 使用 CGLIB 生成子类来实现代理。
    • AspectJ AOP: AspectJ AOP 是基于字节码增强的 AOP 实现。它通过在编译期或者类加载期修改目标类的字节码来实现 AOP 横切逻辑的织入。AspectJ 提供了更强大和灵活的 AOP 功能,可以进行更细粒度的控制和切入。
  2. 织入时机:
    • Spring AOP: Spring AOP 在运行时动态地将切面织入到目标类中。因为使用代理,Spring AOP 只能对 Spring 管理的 Bean 进行 AOP 操作,且只能针对 Spring AOP 支持的切点表达式进行织入。
    • AspectJ AOP: AspectJ AOP 可以在编译期或者类加载期静态地将切面织入到目标类中,也可以在运行时动态织入。AspectJ 提供了更多的织入时机和灵活性,可以对任意的 Java 类进行 AOP 操作,并支持更强大的切入点表达式。
  3. 性能:
    • Spring AOP: Spring AOP 的性能相对较高,因为它基于代理,对目标类的影响比较小。但是,Spring AOP 对于复杂的切面和大规模的系统可能会有一定的性能影响。
    • AspectJ AOP: AspectJ AOP 的性能一般比 Spring AOP 略低,因为它对字节码进行了修改和增强,对目标类的影响更大。但是 AspectJ 提供了更强大的功能和更高的灵活性。
  4. 功能和表达式支持:
    • Spring AOP: Spring AOP 提供了一些常用的 AOP 功能,如前置通知、后置通知、环绕通知等,同时支持基于切点表达式的切入。
    • AspectJ AOP: AspectJ 提供了更丰富的 AOP 功能,如引入(introduction)、静态初始化块织入等,并且支持更强大的切点表达式和切面定义方式。
      综上所述,Spring AOP 和 AspectJ AOP 在实现方式、织入时机、性能和功能支持等方面有一定的区别。选择合适的 AOP 实现方式取决于项目的需求和复杂度。如果需要更强大的功能和更高的灵活性,可以考虑使用 AspectJ AOP;如果对性能要求较高,且功能需求较简单,则可以使用 Spring AOP。在实际项目中,也可以结合两者使用,根据需求灵活选择。

Spring自动装配Bean的方式

  1. ByName 按名字自动装配
  2. ByType 按类型自动装配
  3. 构造器装配
  4. @Autowired 注解,使用@Order或者使用@Qualifier注解去提高优先级或指定实现类

拦截器和过滤器的区别

  1. 拦截器是基于Spring的,过滤器是基于Servlet的

  2. 拦截器拦截的是Action路径(dispatcher Servlet 配置的)下的请求,过滤器几乎可以拦截所有的请求

  3. 拦截器可以访问容器中的Bean , 而过滤器不可以,但是如果过滤器是基于Spring注入的也可以访问

image-20240422210411797

Spring事件监听的核心机制是什么?

观察者模式: 它允许一个对象(称为主题或被观察者)维护一组依赖于它的对象(称为观察者),并在主题状态发生变化时通知观察者。

它包含三个核心:

  1. 事件: 事件是观察者模式中的主题状态变化的具体表示,它封装了事件发生时的信息。在Spring中,事件通常是普通的Java对象,用于传递数据或上下文信息。
  2. 事件发布者: 在Spring中,事件发布者充当主题的角色,负责触发并发布事件。它通常实现了ApplicationEventPublisher接口或使用注解@Autowired来获得事件发布功能。
  3. 事件监听器: 事件监听器充当观察者的角色,负责监听并响应事件的发生。它实现了ApplicationListener接口,通过onApplicationEvent()方法来处理事件。

Spring事务的失效原因?

  1. 方法是private也会失效,解决:改成public: Spring的事务代理通常是通过Java动态代理或CGLIB动态代理生成的,这些代理要求目标方法是公开可访问的(public)。私有方法无法被代理,因此事务将无效。解决方法是将目标方法改为public或protected。
  2. 目标类没有配置为Bean也会失效,解决:配置为Bean: Spring的事务管理需要在Spring容器中配置的Bean上才能生效。如果目标类没有被配置为Spring Bean,那么事务将无法被应用。解决方法是确保目标类被正确配置为Spring Bean。
  3. 自己捕获了异常,解决:不要捕获处理: Spring事务管理通常依赖于抛出未捕获的运行时异常来触发事务回滚。如果您在方法内部捕获了异常并处理了它,事务将不会回滚。解决方法是让异常在方法内部被抛出,以触发事务回滚。
  4. 使用CGLIB动态代理,但@Transactional声明在接口上: 默认情况下,Spring的事务代理使用基于接口的JDK动态代理。如果您将**@Transactional注解声明在接口上,而目标类是使用CGLIB代理的,事务将不会生效。解决方法是将@Transactional**注解移到目标类的方法上,或者配置Spring以使用CGLIB代理接口。
  5. 跨越多个线程的事务管理,解决:使用编程式事务或分布式事务: 如果您的应用程序在多个线程之间共享数据库连接和事务上下文,事务可能会失效,除非适当地配置事务传播属性。
  6. 事务传播属性或捕获异常等设置不正确: 事务传播属性定义了事务如何传播到嵌套方法或外部方法。如果事务传播属性设置不正确,可能会导致事务失效或不符合预期的行为。

SpringBoot的启动原理?

  1. 运行Main方法: 应用程序启动始于Main方法的执行。在Main方法中,创建了一个SpringApplication实例,用于引导应用程序的启动。同时,SpringApplication会根据spring.factories文件加载并注册监听器、ApplicationContextInitializer等扩展接口实现。
  2. 运行run方法: 运行SpringApplication的run方法是应用程序启动的入口。在这一步,Spring Boot会 启动Spring进而创建内置tomcat,进去run方法后还做了很多其他事:
  3. Spring Boot会读取和解析环境变量、配置文件(如application.properties或application.yml)等,以获取应用程序的配置信息。
  4. 之后再创建ApplicationContext也就是我们熟知的Spring上下文: 在这一步,Spring Boot会根据应用程序的类型(例如,Web应用程序)创建相应的ApplicationContext。对于Web应用程序,通常创建的是ServletWebServerApplicationContext。
  5. 预初始化上下文: Spring Boot会将启动类作为配置类,读取并注册为BeanDefinition,这使得Spring容器可以识别应用程序的配置
  6. 调用refresh: 此时,Spring Boot调用了refresh方法来加载和初始化Spring容器。在这一过程中,会执行一系列操作,包括解析@Import注解以加载自动配置类,创建和注册BeanDefinition等。
  7. 创建内置servlet容器: 如果应用程序是一个Web应用程序,Spring Boot会在这一步创建内置的servlet容器(例如Tomcat),以便应用程序可以接受HTTP请求。这个容器将被Spring Boot自动配置,并且可以通过配置进行自定义。
  8. 监听器和扩展点: 在整个启动过程中,Spring Boot会调用各种监听器和扩展点,这些组件可以用来对应用程序进行扩展和定制。例如,您可以使用监听器来处理应用程序启动和关闭事件,或者使用ApplicationContextInitializer来自定义ApplicationContext的初始化。

分布式事务详解

分布式事务五种方案及两个流行框架 - 螺旋自嘲 - 博客园 (cnblogs.com)

2PC

2PC(Two-phase commit protocol),中文叫二阶段提交。二阶段提交是一种强一致性设计,2PC 引入一个事务协调者的角色来协调管理各参与者(也可称之为各本地资源)的提交和回滚,二阶段分别指的是准备(投票)和提交两个阶段。

当第一阶段参与者都准备成功了,协调者就会发送提交事务的请求。反之如果有一个参与者准备失败则发送回滚事物的请求

img

img

2PC是一个同步阻塞协议,只有第一阶段所有参与者都响应了,才会执行第二阶段。在第一阶段中,协调者有超时机制,当因为网络问题或者参与者执行超时和参与者不在线的情况,协调者会向所有参与者发送回滚命令。但是第二阶段是没有超时的,只能不断地重试提交或回滚

2PC 是一种尽量保证强一致性的分布式事务,因此它是同步阻塞的,而同步阻塞就导致长久的资源锁定问题,总体而言效率低,并且存在单点故障问题,在极端条件下存在数据不一致的风险

3PC

3PC相比于2PC多了一个预提交阶段的步骤,准备阶段预提交阶段提交阶段

预提交是把2PC的提交阶段拆成两次去做,相应的性能会差一点,而且绝大部分情况下资源应该都是可用的,等同于明知故问。2PC有的问题3PC也有存在,所以3PC应用的很少,不展开讨论了。

TCC

TCC 指的是Try - Confirm - Cancel

  • Try 指的是预留,即资源的预留和锁定。
  • Confirm 指的是确认操作,这一步其实就是真正的执行了。
  • Cancel 指的是撤销操作,可以理解为把预留阶段的动作撤销了。

一个分布式的全局事务,整体是 两阶段提交 的模型。TCC也是一样的,单相对应2PC和3PC来说,把事务管理从数据库层面,提取到了业务层面

TCC第一阶段:try行为,试探性操作

TCC第二阶段:confirm或cancel操作,第一阶段成功就会调用confirm,失败就会cancel操作

TCC还需要一个全局事务管理者的角色,用来记录TCC全局事务状态并提交或回滚事务

img

由于TCC每一步操作都需要业务上的定义,对于一个操作都要写三个方法对应try、confirm、cancel。

因此TCC对业务的侵入性比较大和业务是紧耦合的关系。

但是因为TCC是业务上的实现,所以他的适用范围更广,可以跨数据库、跨不同的业务系统来实现事务。

事务控制代码都需要开发去写,所以需要做好异常控制:

  1. 空回滚允许

    现象是try没有被执行,就调用了cancel。

    出现原因:

    • Try超时(丢包)
    • 分布式事务回滚,触发cancel
    • 未收到try,收到cancel

    解决办法:让cancel能够识别出这是一个空回滚,可以记录事务执行状态,cancel中判断try是否执行了。

  2. 幂等控制

    事务协调着会重复调用,try、confirm、cancel三个方法都需要实现幂等控制

    解决办法:记录事务执行状态,如果执行过了,就不再执行。

  3. 防悬挂控制

    Cancel比Try先执行

    出现原因:

    • Try超时(拥堵)
    • 分布式事务回滚,出发cancel
    • 拥堵的Try到达

    解决办法:记录事务执行状态,try执行时判断cancel是否执行了。

  4. 并发控制

优点

  1. 可靠性高
  2. 实时性高

缺点

  1. 开发复杂度高
  2. 因为事务状态管理,需要多次DB操作,性能有一定损耗

本地消息列表

此方案的核心是将需要分布式处理的任务通过消息日志的方式来异步执行。消息日志可以存储到本地文本、数据库或消息队列,再通过业务规则自动或人工发起重试。人工重试更多的是应用于支付场景,通过对账系统对事后问题的处理。

消息事务

如果说本地消息表方案是消息+本地表实现的消息的可靠性和事务的,那消息事务方案就是通过消息中间件本身的事务体系,解决了本地事务表对业务的耦合和定时任务扫描的痛点。

  • 事务消息:消息队列RocketMQ版提供类似XA或Open XA的分布式事务功能,通过消息队列RocketMQ版事务消息能达到分布式事务的最终一致。
  • 半事务消息:暂不能投递的消息,发送方已经成功地将消息发送到了消息队列RocketMQ版服务端,但是服务端未收到生产者对该消息的二次确认,此时该消息被标记成“暂不能投递”状态,处于该种状态下的消息即半事务消息。
  • 消息回查:由于网络闪断、生产者应用重启等原因,导致某条事务消息的二次确认丢失,消息队列RocketMQ版服务端通过扫描发现某条消息长期处于“半事务消息”时,需要主动向消息生产者询问该消息的最终状态(Commit或是Rollback),该询问过程即消息回查。

Seata事务框架

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值