spring--IOC详解

1. IOC容器

1.1 IOC思想

在IOC模式中,控制权被反转,也就是说,由框架来控制对象的生命周期和获取依赖的过程。它通过一个容器来管理应用程序中所有的组件,将组件之间的依赖性转移给容器来管理。这使得组件可以被更加灵活地创建、更新和替换,也可以极大地提高应用程序的可测试性和可扩展性。

  • 控制反转,把对象创建和对象之间的调用过程,交给Spring进行管理
  • 目的:为了降低耦合度

DI:Dependency Injection,翻译过来是依赖注入

DI 是 IOC 的另一种表述方式:即组件以一些预先定义好的方式(例如:setter 方法)接受来自于容器

的资源注入。相对于IOC而言,这种表述更直接。

所以结论是:IOC 就是一种反转控制的思想, 而 DI 是对 IOC 的一种具体实现

1.2 IOC容器在Spring中的实现

Spring 的 IOC 容器就是 IOC 思想的一个落地的产品实现。IOC 容器中管理的组件也叫做 bean。在创建bean 之前,首先需要创建 IOC 容器。Spring 提供了 IOC 容器的两种实现方式:

BeanFactory

这是 IOC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用。

ApplicationContext

BeanFactory 的子接口,提供了更多高级特性。面向 Spring 的使用者,几乎所有场合都使用

ApplicationContext 而不是底层的 BeanFactory。

ApplicationContext的主要实现类
类型简介
ClassPathXmlApplicationContext通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象
FileSystemXmlApplicationContext 通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象
ConfigurableApplicationContextApplicationContext 的子接口,包含一些扩展方法refresh() 和 close() ,让 ApplicationContext 具有启动、关闭和刷新上下文的能力。
WebApplicationContext专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域中。

2. 基于XML管理bean

2.1 入门案例

第一步 引入依赖

 <!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.1</version>
        </dependency>
        <!-- junit测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

第二步 创建HelloWorld类

public class HelloWorld {
	public void sayHello(){
		System.out.println("helloworld");
	}
}

第三步 创建spring的配置文件 默认名称applicationContext.xml

<!--
配置HelloWorld所对应的bean,即将HelloWorld的对象交给Spring的IOC容器管理
通过bean标签配置IOC容器所管理的bean
属性:
id:设置bean的唯一标识
class:设置bean所对应类型的全类名
-->
<bean id="helloworld" class="com.cqy.spring.bean.HelloWorld"></bean>

第四步 创建测试类

@Test
public void testHelloWorld(){
	ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
	HelloWorld helloworld = (HelloWorld) ac.getBean("helloworld");
	helloworld.sayHello();
}

2.2 获取bean

使用getBean()方法获取Bean,常用的有三种:

方式一 根据id获取
@Test
public void testHelloWorld(){
	ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    //传入bean标签的id
	HelloWorld helloworld = (HelloWorld) ac.getBean("helloworld");
	helloworld.sayHello();
}
方式二 根据类型获取
@Test
public void testHelloWorld(){
	ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    //传入bean类型
	HelloWorld helloworld = (HelloWorld) ac.getBean(HelloWorld.class);
	helloworld.sayHello();
}
方式三 根据id和类型获取
@Test
public void testHelloWorld(){
	ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    //传入bean标签的id,和Bean类型
	HelloWorld helloworld = (HelloWorld) ac.getBean("helloworld", HelloWorld.class);
	helloworld.sayHello();
}
注意事项

当根据类型获取bean时,要求IOC容器中指定类型的bean有且只能有一个,当IOC容器中一共配置了两个相同的bean时会报错:

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean
of type
扩展

如果组件类实现了接口,根据接口类型是否可以获取bean

可以,前提 bean唯一

如果一个接口有多个实现类,这些实现类都配置了 bean,根据接口类型可以获取 bean 吗?

不行,因为bean不唯一

结论

根据类型来获取bean时,在满足bean唯一性的前提下,其实只是看:『对象 instanceof 指定的类型』的返回结果,只要返回的是true就可以认定为和类型匹配,能够获取到。

2.3 依赖注入

setter注入
<bean id="studentOne" class="com.atguigu.spring.bean.Student">
<!-- property标签:通过组件类的setXxx()方法给组件对象设置属性 -->
<!-- name属性:指定属性名(这个属性名是getXxx()、setXxx()方法定义的,和成员变量无关)
-->
<!-- value属性:指定属性值 -->
	<property name="id" value="1001"></property>
	<property name="name" value="张三"></property>
	<property name="age" value="23"></property>
	<property name="sex" value=""></property>
</bean>
构造器注入
<bean id="studentTwo" class="com.atguigu.spring.bean.Student">
    <constructor-arg value="1002"></constructor-arg>
    <constructor-arg value="李四"></constructor-arg>
    <constructor-arg value="33"></constructor-arg>
    <constructor-arg value=""></constructor-arg>
</bean>

注意

constructor-arg标签还有两个属性可以进一步描述构造器参数:

index属性:指定参数所在位置的索引(从0开始)

name属性:指定参数名

2.4 特殊值处理

字面量

什么是字面量?

int a = 10;

声明一个变量a,初始化为10,此时a就不代表字母a了,而是作为一个变量的名字。当我们引用a

的时候,我们实际上拿到的值是10。

而如果a是带引号的:‘a’,那么它现在不是一个变量,它就是代表a这个字母本身,这就是字面

量。所以字面量没有引申含义,就是我们看到的这个数据本身。

<!-- 使用value属性给bean的属性赋值时,Spring会把value属性的值看做字面量 -->
<property name="name" value="张三"/>
null

使用null标签,不能直接对value属性赋值为null,此时只是字符串null

<property name="name">
	<null />
</property>
XML实体
<!-- 小于号在XML文档中用来定义标签的开始,不能随便使用 -->
<!-- 解决方案一:使用XML实体来代替 -->
<property name="expression" value="a &lt; b"/>
CDATA区
<property name="expression">
<!-- 解决方案二:使用CDATA节 -->
<!-- CDATA中的C代表Character,是文本、字符的含义,CDATA就表示纯文本数据 -->
<!-- XML解析器看到CDATA节就知道这里是纯文本,就不会当作XML标签或属性来解析 -->
<!-- 所以CDATA节中写什么符号都随意 -->
	<value><![CDATA[a < b]]></value>
</property>

2.5 为属性赋值

2.5.1 为类类型 属性赋值
方式一 使用内部bean
<bean id="studentFour" class="com.cqy.spring.bean.Student">
	<property name="id" value="1004"></property>
	<property name="name" value="赵六"></property>
    <property name="age" value="26"></property>
    <property name="sex" value=""></property>
    <property name="clazz">
        <!-- 在一个bean中再声明一个bean就是内部bean -->
        <!-- 内部bean只能用于给属性赋值,不能在外部通过IOC容器获取,因此可以省略id属性 -->
        <bean id="clazzInner" class="com.atguigu.spring.bean.Clazz">
            <property name="clazzId" value="2222"></property>
            <property name="clazzName" value="远大前程班"></property>
        </bean>
    </property>
</bean>
方式二 使用外部bean
<!-- 使用外部bean为属性赋值 -->
<bean id="clazzOne" class="com.cqy.spring.bean.Clazz">
    <property name="clazzId" value="1111"></property>
    <property name="clazzName" value="财源滚滚班"></property>
</bean>

<bean id="studentFour" class="com.atguigu.spring.bean.Student">
    <property name="id" value="1004"></property>
    <property name="name" value="赵六"></property>
    <property name="age" value="26"></property>
    <property name="sex" value=""></property>
    <!-- ref属性:引用IOC容器中某个bean的id,将所对应的bean为属性赋值 -->
    <property name="clazz" ref="clazzOne"></property>
</bean>
方式三 级联赋值 不常用

前提 一定先引用某个bean为属性赋值,才可以使用级联方式更新属性

<bean id="studentFour" class="com.cqy.spring.bean.Student">
    <property name="id" value="1004"></property>
    <property name="name" value="赵六"></property>
    <property name="age" value="26"></property>
    <property name="sex" value=""></property>
    <!-- 一定先引用某个bean为属性赋值,才可以使用级联方式更新属性 -->
    <property name="clazz" ref="clazzOne"></property>
    <property name="clazz.clazzId" value="3333"></property>
    <property name="clazz.clazzName" value="最强王者班"></property>
</bean>
2.5.2 为数组类型属性赋值

使用标签 <array>标签

<bean id="studentFour" class="com.cqy.spring.bean.Student">
    <property name="id" value="1004"></property>
    <property name="name" value="赵六"></property>
    <property name="age" value="26"></property>
    <property name="sex" value=""></property>
    <!-- ref属性:引用IOC容器中某个bean的id,将所对应的bean为属性赋值 -->
    <property name="clazz" ref="clazzOne"></property>
    <property name="hobbies">
        <array>
            <value>抽烟</value>
            <value>喝酒</value>
            <value>烫头</value>
        </array>
    </property>
</bean>
2.5.3为集合类型属性赋值
list集合
<bean id="clazzTwo" class="com.cqy.spring.bean.Clazz">
    <property name="clazzId" value="4444"></property>
    <property name="clazzName" value="Javaee0222"></property>
    <property name="students">
        <list>
            <ref bean="studentOne"></ref>
            <ref bean="studentTwo"></ref>
            <ref bean="studentThree"></ref>
        </list>
    </property>
</bean>

若为Set集合类型属性赋值,只需要将其中的list标签改为set标签即可

Map集合
<bean id="teacherOne" class="com.atguigu.spring.bean.Teacher">
    <property name="teacherId" value="10010"></property>
    <property name="teacherName" value="大宝"></property>
</bean>
<bean id="teacherTwo" class="com.atguigu.spring.bean.Teacher">
    <property name="teacherId" value="10086"></property>
    <property name="teacherName" value="二宝"></property>
</bean>
<bean id="studentFour" class="com.atguigu.spring.bean.Student">
    <property name="id" value="1004"></property>
    <property name="name" value="赵六"></property>
    <property name="age" value="26"></property>
    <property name="sex" value=""></property>
    <!-- ref属性:引用IOC容器中某个bean的id,将所对应的bean为属性赋值 -->
    <property name="clazz" ref="clazzOne"></property>
    <property name="hobbies">
        <array>
        <value>抽烟</value>
        <value>喝酒</value>
        <value>烫头</value>
        </array>
    </property>
    <property name="teacherMap">
        <map>
            <entry>
                <key>
                    <value>10010</value>
                </key>
                <ref bean="teacherOne"></ref>
            </entry>
            <entry>
                <key>
                <value>10086</value>
                </key>
                <ref bean="teacherTwo"></ref>
            </entry>
        </map>
    </property>
</bean>
引用集合类型的bean

使用util:listutil:map标签必须引入相应的命名空间,可以通过idea的提示功能选择

<!--list集合类型的bean-->
<util:list id="students">
    <ref bean="studentOne"></ref>
    <ref bean="studentTwo"></ref>
    <ref bean="studentThree"></ref>
</util:list>
<!--map集合类型的bean-->
<util:map id="teacherMap">
    <entry>
        <key>
        <value>10010</value>
        </key>
        <ref bean="teacherOne"></ref>
    </entry>
    <entry>
        <key>
        <value>10086</value>
        </key>
        <ref bean="teacherTwo"></ref>
    </entry>
</util:map>
<bean id="clazzTwo" class="com.atguigu.spring.bean.Clazz">
    <property name="clazzId" value="4444"></property>
    <property name="clazzName" value="Javaee0222"></property>
    <property name="students" ref="students"></property>
</bean>
<bean id="studentFour" class="com.atguigu.spring.bean.Student">
    <property name="id" value="1004"></property>
    <property name="name" value="赵六"></property>
    <property name="age" value="26"></property>
    <property name="sex" value=""></property>
    <!-- ref属性:引用IOC容器中某个bean的id,将所对应的bean为属性赋值 -->
    <property name="clazz" ref="clazzOne"></property>
    <property name="hobbies">
        <array>
            <value>抽烟</value>
            <value>喝酒</value>
            <value>烫头</value>
        </array>
    </property>
    <property name="teacherMap" ref="teacherMap"></property>
</bean>

2.6 P 命名空间

引入p命名空间后,可以通过以下方式为bean的各个属性赋值

<bean id="studentSix" class="com.atguigu.spring.bean.Student"
p:id="1006" p:name="小明" p:clazz-ref="clazzOne" p:teacherMap-ref="teacherMap"></bean>

2.7 引入外部属性文件

加入依赖
<!-- MySQL驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.7.42</version>
</dependency>
<!-- 数据源 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.0.31</version>
</dependency>
创建jdbc.properties
jdbc.user=root
jdbc.password=cqy
jdbc.url=jdbc:mysql://localhost:3306/ssm
jdbc.driver=com.mysql.jdbc.Driver
引入外部文件
<!-- 引入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>

<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="url" value="${jdbc.url}"/>
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="username" value="${jdbc.user}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

2.8 bean的作用域

概念

在spring中可以通过配置bean标签的scope属性来指定bean的作用域范围,各取值含义参加下表:

取值含义创建对象的时机
singleton(默认)在IOC容器中,这个bean的对象始终为单实例IOC容器初始化时
prototype这个bean在IOC容器中有多个实例获取bean时

若是在WebApplicationContext环境下还会有另外两个作用域(不常用)

取值含义
request在一个请求范围内有效
session在一个会话范围内有效

2.9 bean的生命周期

  1. bean对象创建(调用无参构造器)
  2. 给bean对象设置属性
  3. bean对象初始化之前操作(由bean的后置处理器负责)
  4. bean对象初始化(需在配置bean时指定初始化方法)
  5. bean对象初始化之后操作(由bean的后置处理器负责)
  6. bean对象就绪可以使用
  7. bean对象销毁(需在配置bean时指定销毁方法)
  8. IOC容器关闭
创建User类
public class User {
    private Integer id;
    private String username;
    private String password;
    private Integer age;
    public User() {
    	System.out.println("生命周期:1、创建对象");
    }
    public User(Integer id, String username, String password, Integer age) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.age = age;
    }
    public Integer getId() {
   		return id;
    }
    public void setId(Integer id) {
        System.out.println("生命周期:2、依赖注入");
        this.id = id;
    }
    public String getUsername() {
    	return username;
    }
    public void setUsername(String username) {
    	this.username = username;
    }
    public String getPassword() {
    	return password;
    }
    public void setPassword(String password) {
    	this.password = password;
    }
    public Integer getAge() {
    	return age;
    }
    public void setAge(Integer age) {
    	this.age = age;
    }
    public void initMethod(){
    	System.out.println("生命周期:3、初始化");
    }
    public void destroyMethod(){
    	System.out.println("生命周期:5、销毁");
    }
    @Override
    public String toString() {
        return "User{" +
            "id=" + id +
            ", username='" + username + '\'' +
            ", password='" + password + '\'' +
            ", age=" + age +
            '}';
        }
}

注意其中的initMethod()和destroyMethod(),可以通过配置bean指定为初始化和销毁的方法

配置bean
<!-- 使用init-method属性指定初始化方法 -->
<!-- 使用destroy-method属性指定销毁方法 -->
<bean class="com.atguigu.bean.User" scope="prototype" init-method="initMethod" destroy-method="destroyMethod">
    <property name="id" value="1001"></property>
    <property name="username" value="admin"></property>
    <property name="password" value="123456"></property>
    <property name="age" value="23"></property>
</bean>
bean的后置处理器

bean的后置处理器会在生命周期的初始化前后添加额外的操作,需要实现BeanPostProcessor接口,且配置到IOC容器中,需要注意的是,bean后置处理器不是单独针对某一个bean生效,而是针对IOC容器中所有bean都会执行创建bean的后置处理器:

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBeanProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("☆☆☆" + beanName + " = " + bean);
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("★★★" + beanName + " = " + bean);
        return bean;
    }
}
在IOC容器中配置后置处理器
<!-- bean的后置处理器要放入IOC容器才能生效 -->
<bean id="myBeanProcessor" class="com.atguigu.spring.process.MyBeanProcessor"/>

2.10 FactoryBean

简介

FactoryBean是Spring提供的一种整合第三方框架的常用机制。和普通的bean不同,配置一个FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是getObject()方法的返回值。通过这种机制,Spring可以帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来,只把最简洁的使用界面展示给我们。将来我们整合Mybatis时,Spring就是通过FactoryBean机制来帮我们创建SqlSessionFactory对象的。

示例

创建UserFactoryBean

public class UserFactoryBean implements FactoryBean<User> {
    @Override
    public User getObject() throws Exception {
    	return new User();
    }
    @Override
    public Class<?> getObjectType() {
    	return User.class;
    }
}

配置bean

<bean id="user" class="com.cqy.bean.UserFactoryBean"></bean>

测试

@Test
public void testUserFactoryBean(){
    //获取IOC容器
    ApplicationContext ac = new ClassPathXmlApplicationContext("springfactorybean.xml");
    //得到的是getObject方法中返回的User对象
    User user = (User) ac.getBean("user");
    System.out.println(user);
}

2.11 基于XML的自动装配

根据指定的策略,在IOC容器中匹配某一个bean,自动为指定的bean中所依赖的类类型或接口类型属性赋值

使用bean标签的autowire属性设置自动装配效果

自动装配方式:

第一种 byType,根据类型匹配IOC容器中的某个兼容类型的bean,为属性自动赋值

  • 若在IOC中,没有任何一个兼容类型的bean能够为属性赋值,则该属性不装配,即值为默认值null

  • 若在IOC中,有多个兼容类型的bean能够为属性赋值,则抛出异常NoUniqueBeanDefinitionException

<bean id="userController" class="com.cqy.autowire.xml.controller.UserController" autowire="byType">
</bean>
<bean id="userService" class="com.cqy.autowire.xml.service.impl.UserServiceImpl" autowire="byType">
</bean>

第二种 byName:将自动装配的属性的属性名,作为bean的id在IOC容器中匹配相对应的bean进行赋值

<bean id="userController" class="com.cqy.autowire.xml.controller.UserController" autowire="byName"> </bean>
<bean id="userService" class="com.cqy.autowire.xml.service.impl.UserServiceImpl" autowire="byName"> </bean>
<bean id="userServiceImpl" class="com.cqy.autowire.xml.service.impl.UserServiceImpl" autowire="byName"> </bean>
<bean id="userDao" class="com.cqy.autowire.xml.dao.impl.UserDaoImpl"></bean>
<bean id="userDaoImpl" class="com.cqy.autowire.xml.dao.impl.UserDaoImpl"> </bean>

3. 基于注解管理bean

3.1 标记与扫描

3.1.1注解

和XML 配置文件一样,注解本身并不能执行,注解本身仅仅只是做一个标记,具体的功能是框架检测到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作。

本质上:所有一切的操作都是Java代码来完成的,XML和注解只是告诉框架中的Java代码如何执行。

3.1.2扫描

Spring 为了知道程序员在哪些地方标记了什么注解,就需要通过扫描的方式,来进行检测。然后根据注解进行后续操作。

3.1.3标识组件的常用注解
  • @Component:将类标识为普通组件

  • @Controller:将类标识为控制层组件

  • @Service:将类标识为业务层组件

  • @Repository:将类标识为持久层组件

3.1.4XML中配置扫描组件
情况一 最基本的扫描方式

扫描指定包中的所有类

<context:component-scan base-package="com.cqy"> </context:component-scan>
情况二 指定要排除的组件
<context:component-scan base-package="com.atguigu">
<!-- context:exclude-filter标签:指定排除规则 -->
<!--
    type:设置排除或包含的依据
    type="annotation",根据注解排除,expression中设置要排除的注解的全类名
    type="assignable",根据类型排除,expression中设置要排除的类型的全类名
-->
	<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<!--<context:exclude-filter type="assignable" expression="com.cqy.controller.UserController"/>-->
</context:component-scan>

情况三 仅扫描指定组件 不常用

<context:component-scan base-package="com.atguigu" use-default-filters="false">
    <!-- context:include-filter标签:指定在原有扫描规则的基础上追加的规则 -->
    <!-- use-default-filters属性:取值false表示关闭默认扫描规则 -->
    <!-- 此时必须设置use-default-filters="false",因为默认规则即扫描指定包下所有类 -->
    <!--
        type:设置排除或包含的依据
        type="annotation",根据注解排除,expression中设置要排除的注解的全类名
        type="assignable",根据类型排除,expression中设置要排除的类型的全类名
    -->
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
	<!--<context:include-filter type="assignable" expression="com.atguigu.controller.UserController"/>-->
</context:component-scan>
3.1.5组件所对应的id

在我们使用XML方式管理bean的时候,每个bean都有一个唯一标识,便于在其他地方引用。现在使用注解后,每个组件仍然应该有一个唯一标识。

  • 默认情况

    • 类名首字母小写就是bean的id。例如:UserController类对应的bean的id就是userController。
  • 自定义bean的id

    • 可通过标识组件的注解的value属性设置自定义的bean的id
@Service("userService")//默认为userServiceImpl public class UserServiceImpl implements
UserService {}

3.2基于注解的自动装配

3.2.1 @Autowired注解标记在成员变量

在成员变量上直接标记@Autowired注解即可完成自动装配,不需要提供setXxx()方法。以后我们在项目中的正式用法就是这样。

3.2.2 @Autowired注解可以标记在构造器和set方法上

可以,但不常用,开发中基本使用第一种

3.2.3 @Autowired工作流程

首先根据所需要的组件类型到IOC容器中查找

  • 能够找到唯一的bean:直接执行装配

  • 如果完全找不到匹配这个类型的bean:装配失败

  • 和所需类型匹配的bean不止一个

    • 没有@Qualifier注解:根据@Autowired标记位置成员变量的变量名作为bean的id进行匹配

      • 能够找到:执行装配

      • 找不到:装配失败

    • 使用@Qualifier注解:根据@Qualifier注解中指定的名称作为bean的id进行匹配

      • 能够找到:执行装配

      • 找不到:装配失败

3.2.4 @Autowired的属性 不常用
  • 属性required
    • 默认值为true,因此在自动装配无法找到相应的bean时,会装配失败
    • false,则表示能装就装,装不上就不装,此时自动装配的属性为默认值

但是实际开发时,基本上所有需要装配组件的地方都是必须装配的,用不上这个属性

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值