Spring框架(上) 概述、IOC容器与Bean的配置

Spring框架(上) 概述、IOC容器与Bean的配置

(一)框架

1.定义

来自百度百科

image-20201012091904270

2.目标

image-20201012091937506

3.简单理解

  • 框架,即framework。其实就是某种应用的半成品,就是一组组件,供你选用完成你自己的系统。简单说就是使用别人搭好的舞台,你来做表演。而且,框架一般是成熟的,不断升级的软件。

(二)Spring概述

1.概述

  • Spring是一个开源框架

  • Spring为简化企业级开发而生,使用Spring,JavaBean就可以实现很多以前要靠EJB才能实现的功能。同样的功能,在EJB中要通过繁琐的配置和复杂的代码才能够实现,而在Spring中却非常的优雅和简洁。

  • Spring是一个IOC***(DI)和AOP*容器框架。

  • Spring的优良特性

    • 非侵入式:基于Spring开发的应用中的对象可以不依赖于Spring的API
    • 依赖注入:DI——Dependency Injection,反转控制(IOC)最经典的实现。
    • 面向切面编程:Aspect Oriented Programming——AOP
    • 容器:Spring是一个容器,因为它包含并且管理应用对象的生命周期
    • 组件化:Spring实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用XML和Java注解组合这些对象。
    • 一站式:在IOC和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上Spring 自身也提供了表述层的SpringMVC和持久层的Spring JDBC)。
  • Spring模块

    image-20201012092600930

2.搭建Spring运行时环境

  1. 加入JAR包

① Spring自身JAR包:spring-framework-4.0.0.RELEASE\libs目录下

spring-beans-4.0.0.RELEASE.jar

spring-context-4.0.0.RELE2ASE.jar

spring-core-4.0.0.RELEASE.jar

spring-expression-4.0.0.RELEASE.jar

② commons-logging-1.1.1.jar

2)创建Spring的配置文件

image-20201012092905257

3.第一个Spring程序

1.创建一个person类

package com.deserts.spring.mod;

/**
 * @ClassName Person
 * @Description TODO
 * @Author deserts
 * @Date 2020/10/4 22:54
 */
public class Person {
    private Integer id;
    private String name;

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Person(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    public Person() {
    }
}

2.以前创建对象的方式

	public class Test {
    public static void main(String[] args) {
        Person person = new Person(11, "deserts");
        System.out.println(person);
  }
}

3.使用Spring容器创建对象

package com.deserts.spring.mod;

import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @ClassName TestBySpring
 * @Description TODO
 * @Author deserts
 * @Date 2020/10/4 23:08
 */
public class TestBySpring {
    public static void main(String[] args) {
        //初始化容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        //通过getBean()获取对象
        Person person = (Person) ac.getBean("person");
        System.out.println(person);
    }
}

(三)IOC容器和Bean的配置

1.IOC和DI

1.1 IOC(Inversion Of Control):反转控制
  • 在应用程序中的组件需要获取资源时,传统的方式是组件主动的从容器中获取所需要的资源,在这样的模式下开发人员往往需要知道在具体容器中特定资源的获取方式,增加了学习成本,同时降低了开发效率。
  • 反转控制的思想完全颠覆了应用程序组件获取资源的传统方式:反转了资源的获取方向——改由容器主动的将资源推送给需要的组件,开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源的方式即可,极大的降低了学习成本,提高了开发的效率。这种行为也称为查找的被动形式。

传统方式:我想吃饭 我需要买菜做饭

反转控制: 我想吃饭 饭来张口

1.2 DI(Dependency Injection):依赖注入
  • IOC的另一种表述方式:即组件以一些预先定义好的方式(例如:setter 方法)接受来自于容器的资源注入。相对于IOC而言,这种表述更直接。

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

1.3 IOC容器在Spring中的实现
  • 前提: Spring中有IOC思想, IOC思想必须基于 IOC容器来完成, 而IOC容器在最底层实质上就是一个对象工厂
  • 在通过IOC容器读取Bean的实例之前,需要先将IOC容器本身实例化
  • Spring提供了IOC容器的两种实现方式
    1. BeanFactory:IOC容器的基本实现,是Spring内部的基础设施,是面向Spring本身的,不是提供给开发人员使用的。
    2. ApplicationContext:BeanFactory的子接口,提供了更多高级特性。面向Spring的使用者,几乎所有场合都使用ApplicationContext而不是底层的BeanFactory。
1.4 ApplicationContext的主要实现类
  1. ClassPathXmlApplicationContext:对应类路径下的XML格式的配置文件

  2. FileSystemXmlApplicationContext:对应文件系统中的XML格式的配置文件

  3. 在初始化时就创建单例的bean,也可以通过配置的方式指定创建的Bean是多实例的。

1.5 ConfigurableApplicationContext
  1. 是ApplicationContext的子接口,包含一些扩展方法

2)refresh()和close()让ApplicationContext具有启动、关闭和刷新上下文的能力。

1.6 WebApplicationContext

专门为WEB应用而准备的,它允许从相对于WEB根目录的路径中完成初始化工作

容器的结构图

image-20201012100921545

2.获取Bean的方式

  1. 通过id获取

     Person person = (Person) ac.getBean("person");
    
  2. 通过类类型获取

    Person person = ac.getBean(Person.class);
    

    这种方式配置文件可以不设置id,但这样只能管理一个对象

  3. 通过id和类类型获取

    Person person = ac.getBean("person", Person.class);
    

    推荐使用这种方式

Spring创建对象的方式:通过配置文件class的全类名使用反射调用newInstance方法创建对象

验证:

1.使用反射创建对象时,要求类要有空参构造方法,我们将Person类的空参构造方法注释掉

image-20201012102332202

2.此时运行时会报异常:java.lang.NoSuchMethodException

image-20201012102455922

3.给Bean的属性赋值

3.1 依赖注入的方式
通过Bean的setXxx()方法赋值(使用property标签)

image-20201012103814026

注意点:设置属性是通过set方法后面的名字去调用相应方法,如:name配置为id,会调用setId()方法,如果属性名不叫id也没事,不过一般相同。

通过Bean的构造器赋值

直接赋值,会自动匹配bean中的构造器

image-20201012105133839

但当有多个相同参数个数的构造器时,只会匹配从上至下第一个匹配的

image-20201012105608002

配置时,我们想要配置score的值,使用以上方式

image-20201012105720880

测试

image-20201012105740316

运行结果:

image-20201012105819616

可以看到,含有age四个参数的构造器比含有score四个参数的构造器先被匹配到,所以无法给score赋值

对此,我们可以在标签内加入index(从0开始,参数的位置)和type属性(参数类型)去进行匹配

image-20201012110320023

运行结果

image-20201012110341315

3.2 p命名空间
  • 为了简化XML文件的配置,越来越多的XML文件采用属性而非子元素配置信息。Spring 从2.5版本开始引入了一个新的p命名空间,可以通过元素属性的方式配置Bean的属性。

使用前应该在配置文件配置p命名空间

image-20201012111015223

p命名空间的使用

image-20201012111334802

运行结果:

image-20201012111359767

3.3 可以使用的值
1.字面量
  1. 可以使用字符串表示的值,可以通过value属性或value子节点的方式指定

使用子节点的方式:

image-20201012141716871

  1. 基本数据类型及其封装类、String等类型都可以采取字面值注入的方式

3)若字面值中包含特殊字符,可以使用<![CDATA[]]>把字面值包裹起来

2.null值
  <bean class="com.atguigu.spring.bean.Book" id="bookNull" >
           <property name= "bookId" value ="2000"/>
           <property name= "bookName">
               <null/>
           </property>
           <property name= "author" value ="nullAuthor"/>
           <property name= "price" value ="50"/>
  </bean >
3.外部已声明的bean、引用其它的bean

如在Student类中有Teacher属性,这时不能用字面量的形式使用value去赋值,需要使用ref去赋值

image-20201012141843129

运行结果:

image-20201012141904728

使用p命名空间的方式:

image-20201012142031329

4.给bean的级联属性赋值

级联:多级联动,比如Student类里有Teacher属性,Teacher类里由tid、tname属性

赋值:

image-20201013093932233

注意事项:使用级联属性进行赋值时,时修改对象属性的值,若对象还未创建,会报嵌套对象为空的异常

5.内部bean

当bean实例仅仅给一个特定的属性使用时,可以将其声明为内部bean。内部bean声明直接包含在<property>或<constructor-arg>元素里,不需要设置任何id或name属性

内部bean不能使用在任何其他地方

<bean id="shop2" class="com.atguigu.spring.bean.Shop" >
    <property name= "book">
        <bean class= "com.atguigu.spring.bean.Book" >
           <property name= "bookId" value ="1000"/>
           <property name= "bookName" value="innerBook" />
           <property name= "author" value="innerAuthor" />
           <property name= "price" value ="50"/>
        </bean>
    </property>
</bean >

4.集合属性

  • 在Spring中可以通过一组内置的XML标签来配置集合属性,例如:<list>,<set>或<map>。
4.1 数组和List

List:需要指定<list>标签,在标签里包含一些元素这些标签。可以通过<value>指定简单的常量值,通过<ref>指定对其他Bean的引用。通过<bean>指定内置bean定义。通过<null/>指定空元素。甚至可以内嵌其他集合。

Teacher类:

image-20201012152107366

配置String类型的List:

image-20201012152143305

配置Student类型的List:

image-20201012152327724

数组的定义和List一样,都使用<list>元素。

配置java.util.Set需要使用<set>标签,定义的方法与List一样。

4.2 Map

Java.util.Map通过<map>标签定义,<map>标签里可以使用多个<entry>作为子标签。每个条目包含一个键和一个值。

​ 必须在<key>标签里定义键。

​ 因为键和值的类型没有限制,所以可以自由地为它们指定<value>、<ref>、<bean>或<null/>元素。

​ 可以将Map的键和值作为<entry>的属性定义:简单常量使用key和value来定义;bean引用通过key-ref和value-ref属性定义。

配置Map:

image-20201012164502096

4.3 集合类型的bean

使用前,需要引入util命名空间

image-20201013093435704

List使用举例:

image-20201013093532359

Map使用举例:

image-20201013093754921

5.工厂方法创建bean

5.1 静态工厂方式创建

静态工厂与实例工厂:

image-20201018141701181

以飞机类为例:

image-20201018141441474

静态工厂:

image-20201018141511235

配置xml:factory-method指定静态工厂的方法,constructor-arg设置方法的具体参数

image-20201018141536295

测试:

image-20201018142009488

5.2 使用实例工厂创建bean

仍使用上面的飞机类

实例工厂:

image-20201018142519222

配置xml:先配置好工厂bean,再配置plane的bean,并指定工厂bean和工厂方法,使用constructor-arg设置方法的具体参数

image-20201018142639796

测试:

image-20201018143044903

5.3FactoryBean
  • Spring中有两种类型的bean,一种是普通bean,另一种是工厂bean,即FactoryBean。

  • 工厂bean跟普通bean不同,其返回的对象不是指定类的一个实例,其返回的是该工厂bean的getObject方法所返回的对象。

  • 工厂bean必须实现org.springframework.beans.factory.FactoryBean接口。

测试FactoryBean:

1.先建立Car类

public class Car {
    private String brand;
    private Double price;

    public Car() {
    }

    public Car(String brand, Double price) {
        this.brand = brand;
        this.price = price;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Car{" +
                "brand='" + brand + '\'' +
                ", price=" + price +
                '}';
    }
}

2.建立CarFactory类去实现FactoryBean接口,并重写方法:

public class CarFactory implements FactoryBean<Car> {
    @Override
    public Car getObject() throws Exception {
        Car car = new Car("奥迪", 200000.0);
        return car;
    }

    @Override
    public Class<?> getObjectType() {
        return Car.class;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }
}

3.创建配置文件配置CarFactory:

image-20201013101316288

4.创建测试类:

public class Test {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("factoryBean.xml");
        Object object = ac.getBean("carFactory");
        System.out.println(object);

    }
}

5.结果:

image-20201013101414812

分析:可以看到,我们配置的是CarFactory,但返回的对象却是Car对象

两个参数时:

image-20201013101703803

6.bean的作用域

  • 在Spring中,可以在<bean>元素的scope属性里设置bean的作用域,以决定这个bean是单实例的还是多实例的。

  • 默认情况下,Spring只为每个在IOC容器里声明的bean创建唯一一个实例,整个IOC容器范围内都能共享该实例:所有后续的getBean()调用和bean引用都将返回这个唯一的bean实例。该作用域被称为singleton,它是所有bean的默认作用域。

image-20201013110029431

  • 当bean的作用域为单例时,Spring会在IOC容器对象创建时就创建bean的对象实例。而当bean的作用域为prototype时,IOC容器在获取bean的实例时创建bean的实例对象。

一、测试多例:

1.创建Student类

package com.deserts.scope;

/**
 * @ClassName Student
 * @Description TODO
 * @Author deserts
 * @Date 2020/10/13 10:58
 */
public class Student {
    private String name;
    private Integer id;

    public Student() {
        System.out.println("Student构造方法");
    }

    public Student(String name, Integer id) {
        this.name = name;
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }
}

2.配置测试多例:

image-20201013110626376

3.创建测试类:

image-20201013110854077

4.输出结果:

image-20201013110920186

5.分析:可以看成,多例下是每次创建对象时调用构造方法

二、测试单例模式

1.修改配置文件

image-20201013111108257

2.测试类不变,查看结果:

image-20201013111210928

3.由此可知,只调用了一次的构造方法,并且是同一个对象

4.修改测试类:

image-20201013111326564

5.运行结果:

image-20201013111353157

6.由此可知,单例模式下在初始化容器时就创建了对象,使用getBean()方法获取对象时直接从容器中获取就可以了。

7.bean的生命周期

7.1 概述
  • Spring IOC容器可以管理bean的生命周期,Spring允许在bean生命周期内特定的时间点执行指定的任务。
7.2 Spring IOC容器对bean的生命周期进行管理的过程

​ ① 通过构造器或工厂方法创建bean实例

​ ② 为bean的属性设置值和对其他bean的引用

​ ③ 调用bean的初始化方法

​ ④ bean可以使用了

​ ⑤ 当容器关闭时,调用bean的销毁方法

在配置bean时,通过init-method和destroy-method 属性为bean指定初始化和销毁方法

测试整个过程:

1.创建Person类进行模拟:

package com.deserts.life;

/**
 * @ClassName Person
 * @Description TODO
 * @Author deserts
 * @Date 2020/10/13 11:19
 */
public class Person {
    private String name;
    private Integer id;

    public Person() {
        System.out.println("one: 创建");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        System.out.println("two: 依赖注入");
        this.name = name;
    }

    public void init(){
        System.out.println("three: 初始化");
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "four: 使用bean";
    }

    public void destroy(){
        System.out.println("five: 销毁");
    }
}

2.创建配置文件,配置初始化和销毁方法:

image-20201013112720308

3.创建测试类:

image-20201013112932063

4.运行结果:

image-20201013113002522

7.3 bean的后置处理器
简介

① bean后置处理器允许在调用初始化方法前后对bean进行额外的处理

② bean后置处理器对IOC容器里的所有bean实例逐一处理,而非单一实例。其典型应用是:检查bean属性的正确性或根据特定的标准更改bean的属性。

③ bean后置处理器需要实现接口:org.springframework.beans.factory.config.BeanPostProcessor。

在初始化方法被调用前后,Spring将把每个bean实例分别传递给上述接口的以下两个方法:

●postProcessBeforeInitialization(Object, String)

●postProcessAfterInitialization(Object, String)

使用

1.创建AfterHandler去实现BeanPostProcesor接口,并重写两个方法去处理初始化前后的Bean对象:

public class AfterHandler implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        Person person = (Person)bean;
        if("deserts".equals(person.getName())){
            person.setName("张悬");
        }else {
            person.setName("安溥");
        }
        return person;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

2.配置AfterHandler类,使后置处理器在整个容器生效

image-20201013131703061

3.运行结果:

image-20201013131757435

使用后置处理器后的生命周期

①通过构造器或工厂方法创建bean实例

②为bean的属性设置值和对其他bean的引用

③将bean实例传递给bean后置处理器的postProcessBeforeInitialization()方法

④调用bean的初始化方法

⑤将bean实例传递给bean后置处理器的postProcessAfterInitialization()方法

⑥bean可以使用了

⑦当容器关闭时调用bean的销毁方法

8.引用外部属性文件

8.1 直接配置

image-20201013143732347

8.2 引入外部属性文件

1.创建properties属性文件

image-20201013143828279

2.引入context命名空间

image-20201013143936573

3.指定properties属性文件的位置

<context:property-placeholder location="jdbc.properties"/>

4.从properties属性文件引入属性值

image-20201013144051089

5.测试

image-20201013144114836

9.自动装配

9.1 概念
  • 手动装配:以value或ref的方式明确指定属性值都是手动装配。
  • 自动装配:根据指定的装配规则,不需要明确指定,Spring自动将匹配的属性值注入bean中。
9.2 装配模式
  1. 根据类型自动装配:将类型匹配的bean作为属性注入到另一个bean中。若IOC容器中有多个与目标bean类型一致的bean,Spring将无法判定哪个bean最合适该属性,所以不能执行自动装配

  2. 根据名称自动装配:必须将目标bean的名称和属性名设置的完全相同

  3. 通过构造器自动装配:当bean中存在多个构造器时,此种自动装配方式将会很复杂。不推荐使用。

示例:

image-20201013152533895

9.3 选用建议

​ 相对于使用注解的方式实现的自动装配,在XML文档中进行的自动装配略显笨拙,在项目中更多的使用注解的方式实现。

10.通过注解配置bean

10.1 概述

相对于XML方式而言,通过注解的方式配置bean更加简洁和优雅,而且和MVC组件化开发的理念十分契合,是开发中常用的使用方式。

10.2 使用注解标识组件
  1. 普通组件:@Component;标识一个受Spring IOC容器管理的组件

  2. 持久化层组件:@Repository;标识一个受Spring IOC容器管理的持久化层组件

  3. 业务逻辑层组件:@Service;标识一个受Spring IOC容器管理的业务逻辑层组件

  4. 表述层控制器组件:@Controller;标识一个受Spring IOC容器管理的表述层控制器组件

  5. 组件命名规则

​ ①默认情况:使用组件的简单类名首字母小写后得到的字符串作为bean的id

​ ②使用组件注解的value属性指定bean的id

​ 注意:事实上Spring并没有能力识别一个组件到底是不是它所标记的类型,即使将@Respository注解用在一个表述层控制器组件上面也不会产生任何错误,所以@Respository、@Service、@Controller这几个注解仅仅是为了让开发人员自己明确当前的组件扮演的角色

10.3 使用过程举例

1.创建类和包,在UserController、UserDaoImpl、UserServiceImpl三个类分别加上注解@Controller、@Repository、@Service

image-20201013162845435

2.在前面jar包的基础上加上jar包:spring-aop-4.0.0.RELEASE.jar

3.创建配置文件,在配置文件中创建context命名空间和标签<context:component-scan base-package=“com.deserts.usermod”/>,指定要扫描的包名

4.创建测试类进行测试:

image-20201013163241424

5.运行结果:

image-20201013163352723

6.我们可以知道:在初始化容器时,就会进行扫描,默认为单例,id默认为类名首字母小写。

10.4 扫描组件
  • 组件被上述注解标识后还需要通过Spring进行扫描才能够侦测到。
1.指定要被扫描的包:
<context:component-scan base-package="com.deserts.usermod"/>
2.说明:
  • 使用base-package属性指定一个需要扫描的基类包,Spring容器将会扫描这个基类包及其子包中的所有类。

  • 当需要扫描多个包时可以使用逗号分隔。

  • 如果仅希望扫描特定的类而非基包下的所有类,可使用resource-pattern属性过滤特定的类,示例:

    <context:component-scan 
    	base-package="com.atguigu.component" 
    	resource-pattern="autowire/*.class"/>
    
3.包含与排除

1)使用包含:<context:include-filter>子节点表示要包含的目标类,使用时需要在<context:include-filter>标签里设置属性use-default-filter = false,表示不使用默认的过滤器(默认过滤器默认不过滤,包含包下所有类),在所有类中进行挑选并包含。

2)使用排除:<context:include-filter>子节点表示要包含的目标类,使用时需要在<context:include-filter>标签里设置属性use-default-filter = true,表示使用默认的过滤器(默认过滤器默认不过滤,包含包下所有类),在所有类中进行排除。

3)包含与排除的过滤表达式类型:

类别示例说明
annotationorg.springframework.stereotype.Controller过滤所有注解类型为Controller的类。这个规则根据目标组件是否标注了指定类型的注解进行过滤
assignablecom.deserts.usermod.dao.UserDaoImpl过滤UserDaoImpl类。这个规则根据目标组件是否是指定类型的子类的方式进行过滤。
aspectjcom.atguigu.*Service+所有类名是以Service结束的,或这样的类的子类。这个规则根据AspectJ表达式进行过滤。
regexcom.atguigu.anno.*所有com.atguigu.anno包下的类。这个规则根据正则表达式匹配到的类名进行过滤。
customcom.atguigu.XxxTypeFilter使用XxxTypeFilter类通过编码的方式自定义过滤规则。该类必须实现org.springframework.core.type.filter.TypeFilter接口

4)使用举例:

a.包含举例:

image-20201013173139094

b.排除举例:

image-20201013173223258

5)注意事项:包含和排除是两个矛盾的标签,不能同时使用。

10.5 组件装配
@Autowired注解

1.根据类型实现自动装配。

image-20201014185624365

2.默认情况下,当IOC容器里存在多个类型兼容的bean时,Spring会尝试匹配bean 的id值是否与变量名相同,如果相同则进行装配。如果bean的id值不相同,通过类型的自动装配将无法工作。此时可以在@Qualifier注解里提供bean的名称。

image-20201014190131472

Spring甚至允许在方法的形参上标注@Qualifiter注解以指定注入bean的名称。

image-20201014185654728

3.若某一属性允许不被设置,可以设置@Autowired注解的required属性为 false.

image-20201014185732001

@Resource

@Resource注解要求提供一个bean名称的属性,若该属性为空,则自动采用标注处的变量或方法名作为bean的名称。

@Inject

@Inject和@Autowired注解一样也是按类型注入匹配的bean,但没有reqired属性。

使用注解标识组件给bean设置id的方法

直接在注解后面加括号(value = “xxx”)或(“xxx”),如:@Service(value = “userService”)或@Controller(“userController”)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值