spring (1) IOC控制反转

IOC控制反转

  • spring概念介绍
  • IOC (重要,必须掌握)
  • spring快速入门
  • spring相关API介绍
  • Spring配置文件 (重点)
  • DBUtil

以上6部分都是基于xml进行开发

  • spring注解开发
  • spring整合Junit

Spring概述

Spring是分层的 Java SE/EE应用 full-stack(全栈式) 轻量级开源框架

两大核心:以IOC(Inverse Of Control:控制反转)和** AOP**(Aspect Oriented Programming:面向切面编程)

耦合:程序间的依赖关系
解耦:降低程序间的依赖关系(编译器不依赖,运行期才依赖) 思路:去掉程序间的new关键字
解耦思路:配置文件+反射,自定义IOC的时候就是这种思路

优势

方便解耦,简化开发 Spring就是一个容器,可以将所有对象创建和关系维护交给Spring管理 什么是耦合度?对象之间的关系,通常说当一个模块(对象)更改时也需要更改其他模块(对象),这就是 耦合,耦合度过高会使代码的维护成本增加。要尽量解耦

体系结构

在这里插入图片描述
ioc容器的概念:spring创建出来的对象都放在ioc容器中

IOC

控制反转(Inverse Of Control)不是什么技术,而是一种设计思想。它的目的是指导我们设计出更
加松耦合的程序
控制:在java中指的是对象的控制权限(创建、销毁)
反转:指的是对象控制权由原来 由开发者在类中手动控制 反转到 由Spring容器控制
实际上就是把对象的创建权给spring

  • 传统方式 之前我们需要一个userDao实例,需要开发者自己手动创建 new UserDao();
  • IOC方式 现在我们需要一个userDao实例,直接从spring的IOC容器获得,对象的创建权交给了spring控制

自定义IOC容器

因为SpringIOC容器的思想是类似的,所以这里自定义一个看看原理
需求:实现service层与dao层代码解耦合

先用传统方式实现service层与dao层的调用:

  1. 创建java项目,导入自定义IOC相关坐标
  2. 编写Dao接口和实现类
  3. 编写Service接口和实现类
  4. 编写测试代码

1 创建java项目,导入自定义IOC相关坐标

<dependencies>
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.1.6</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    </dependencies>

dom4j用于读写XML文件,jaxen是一个Java编写的开源的XPath库,junit是一个Java语言的单元测试框架

2.编写Dao接口和实现类

接口

public interface IUserDao {
    public void save();
}

实现类

public class UserDaoImpl implements IUserDao {
    @Override
    public void save() {
        System.out.println("dao被调用了,保存成功");
    }
}

3编写service层接口和实现类

接口

public interface IUserService {
    public void save();
}

实现类

public class UserServiceImpl implements IUserService {
    @Override
    public void save() {
        //调用dao层方法 传统方式
        IUserDao userDao = new UserDaoImpl();

        userDao.save();

    }
}

4 编写测试代码

@Test
    public void test1(){
        //获取业务层对象
        IUserService userService = new UserServiceImpl();
        //调用save方法
        userService.save();
    }

在这里插入图片描述
这是在传统方式下,调用dao成功了
因为是直接new出来,所以会存在编译期依赖,耦合重
当前service对象和dao对象耦合度太高,而且每次new的都是一个新的对象,导致服务器压力过大。解耦合的原则是编译期不依赖,而运行期依赖就行了

** 下面开始去掉编译期依赖**
步骤:

  1. 准备一个配置文件beans.xml 把要创建对象的类路径封装进去
  2. 编写一个工厂工具类,工厂类中使用dom4j来解析配置文件,获取到类全路径
  3. 使用反射生成对应类的实例对象,存到map中(IOC容器)

编写beans.xml

采用反射技术来代替new对象
把所有需要创建对象的信息定义在配置文件中
这个beans.xml就是配置文件

<?xml version="1.0" encoding="UTF-8" ?> 
<beans> 
	<!--id:标识  class:存的是要生成实例的类的全路径-->
	<bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl"></bean> 
</beans>

编写BeanFactory工具类

public class BeanFactory {

    private static Map<String, Object> ioc = new HashMap<>();
    //程序启动时,初始化对象实例
    static {
        //1.读取配置文件
        InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");

        // 2.解析xml(dom4j)
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(resourceAsStream);

            //3.编写xpath表达式
            String xpath="//bean";

            //4.获取所有的bean标签
            List<Element> list = document.selectNodes(xpath);

            //5.遍历并使用反射创建对象实例,设置到map集合(ioc容器)中
            for(Element element:list){
                String id = element.attributeValue("id");
                String className = element.attributeValue("class");
                //使用反射生成实例对象
                Object object = Class.forName(className).newInstance();
                //存到map中 key-value格式:id-object
                ioc.put(id,object);
            }
        } catch (DocumentException | ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    // 获取指定id的对象实例 
    public static Object getBean(String beandId) { 
        return ioc.get(beandId); 
    }
}

List集合中的每个element对应着一个bean标签

修改UserServiceImpl实现类

@Override
    public void save() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        //调用dao层方法 传统方式
        //IUserDao userDao = new UserDaoImpl();

        //反射
        //IUserDao userDao = (IUserDao) Class.forName("com.lagou.dao.impl.UserDaoImpl").newInstance();
        IUserDao userDao = (IUserDao) BeanFactory.getBean("userDao");
        userDao.save();

    }

就完成了

Spring快速入门

需求:借助spring的IOC实现service层与dao层代码解耦合
步骤:

  1. 创建java项目,导入spring开发基本坐标
  2. 编写Dao接口和实现类
  3. 创建spring核心配置文件
  4. 在spring配置文件中配置 UserDaoImpl
  5. 使用spring相关API获得Bean实例

代码实现

1.创建java项目,导入spring开发基本坐标依赖

<dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

2.编写Dao接口和实现类

接口

public interface IUserDao {
    public void save();
}

实现类

public class UserDaoImpl implements IUserDao {
    public void save() { 
        System.out.println("dao被调用了..."); 
    }
}

3.创建spring核心配置文件

在文件创建在resources里
在这里插入图片描述

<?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">

</beans>

4.在spring配置文件中配置 UserDaoImpl

<!--在spring配置文件中配置 UserDaoImpl
        id:唯一标识
        class:类全路径
    -->
    <bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl"></bean>

5.使用spring相关API获得Bean实例

@Test
    public void testSave() throws Exception {
        //获取到了spring上下文对象,借助上下文对象可以获取到IOC容器中的bean对象
        // 这行代码执行就会加载applicationContext.xml 并会获取到class的值
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");

        //为了拿到实例对象,用key值去取
        //这一步是使用上下文对象从IOC容器中获取到了bean对象
        IUserDao userDao = (IUserDao) applicationContext.getBean("userDao");

        //调用方法
        userDao.save();
    }

在这里插入图片描述


ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
执行完(加载的同时就创建了bean对象存到容器中
spring的IOC容器里面就有了key(userDao)-value(com.lagou.dao.impl.UserDaoImpl)这样的实例对象了
applicationContext.getBean("userDao");调用getBean方法时其实就是从容器中根据userDao这个key取出对应的实例对象
小结:

  1. 导入坐标
  2. 创建Bean
  3. 创建applicationContext.xml
  4. 在配置文件中进行Bean配置
  5. 创建ApplicationContext对象,执行getBean

这样就成功完成了dao层和service层的解耦

Spring相关API

在这里插入图片描述

BeanFactory

BeanFactory是 IOC 容器的核心接口,它定义了IOC的基本功能
BeanFactory也可以完成配置文件的加载

BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));

并有如下特点:
在第一次调用getBean()方法时,才创建指定对象的实例

@Test
    public void test2() throws Exception {
        //核心接口 不会创建bean对象存到容器中
        BeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
        
        //getBean的时候才真正创建Bean对象存到容器中,当然同时又getBean从容器中取出了这个对象
        IUserDao userDao = (IUserDao)xmlBeanFactory.getBean("userDao");
        
        userDao.save();
    }

ApplicationContext和BeanFactory的区别:Bean对象的创建时机不同
BeanFactory:当使用getBean方法时才会真正创建Bean对象
ApplicationContext:创建实现类对象的时候就已经创建了Bean对象存到容器中

ApplicationContext

ApplicationContext代表应用上下文对象,可以获得spring中IOC容器的Bean对象
特点:在spring容器启动时,加载并创建所有对象的实例存到容器中了
常用实现类:

1. ClassPathXmlApplicationContext 
它是从类的根路径下加载配置文件 推荐使用这种。 

2.FileSystemXmlApplicationContext 
它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。C盘D盘都无所谓

3. AnnotationConfigApplicationContext 
当使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。

常用方法:

1. Object getBean(String name); 
	根据Bean的id从容器中获得Bean实例,返回是Object,需要强转。

2. <T> T getBean(Class<T> requiredType); 
	根据类型从容器中匹配Bean实例,当容器中相同类型的Bean有多个时,则此方法会报错。 

3. <T> T getBean(String name,Class<T> requiredType); 
	根据Bean的id和类型获得Bean实例,解决容器中相同类型Bean有多个情况。

2.根据类型从容器中查找和匹配Bean实例

@Test
    public void testSave() throws Exception {
        //获取到了spring上下文对象,借助上下文对象可以获取到IOC容器中的bean对象
        // 这行代码执行就会加载applicationContext.xml 并会获取到class的值
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");

        //为了拿到实例对象,用key值去取
        //这一步是使用上下文对象从IOC容器中获取到了bean对象
        //1.根据beanid在容器中找对应的bean对象
        //IUserDao userDao = (IUserDao) applicationContext.getBean("userDao");
        //2.根据类型在容器中进行查询 有可能报错情况:根据当前类型匹配到多个实例
        IUserDao userDao = applicationContext.getBean(IUserDao.class);

        //调用方法
        userDao.save();
    }

Spring配置文件

Bean标签的基本配置

<bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl"></bean>

详解:

<bean id="" class=""></bean> 

* 用于配置对象交由Spring来创建。 
* 基本属性: 
	id:Bean实例在Spring容器中的唯一标识 
	class:Bean的全限定名 (Spring的底层是用反射生成该类的实例对象存到容器中,Spring要根据全限定名来使用反射进行创建)
	
* 默认情况下它调用的是类中的 无参构造函数,如果没有无参构造函数则不能创建成功。

一个bean标签代表一个交由Spring创建的对象

Bean标签的作用范围配置

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

scope属性指对象的作用范围,取值如下:

  • singleton 默认值,单例的(只有一个共享的实例存在,所有对这个bean的请求都会返回这个唯一的实例)
  • prototype 多例的(每次从容器中取出改对象,都会重新创建该对象的实例,类似于new)
  • request: WEB项目中,Spring创建一个Bean的对象,也会将对象存入到request域中一份
  • session: WEB项目中,Spring创建一个Bean的对象,也会将对象存入到session域中
  • global session:WEB项目中,应用在Portlet环境,如果没有Portlet环境那么globalSession 相当
    于 session

应用一下<bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl" scope="singleton"></bean>:
测试,比较创建对象的地址值

/**
     *测试scope属性 scope="singleton"的效果
     */
    @Test
    public void test3() throws Exception {

        ApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");

        IUserDao userDao = (IUserDao)classPathXmlApplicationContext.getBean("userDao");

        IUserDao userDao2 = (IUserDao)classPathXmlApplicationContext.getBean("userDao");

        System.out.println(userDao);
        System.out.println(userDao2);
    }

在这里插入图片描述
说明是单例的
测试<bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl" scope="prototype"></bean>
再查看其地址是不一样的在这里插入图片描述

Bean生命周期

1. 当scope的取值为singleton时 
	Bean的实例化个数:1个 
	Bean的实例化时机:当Spring核心配置文件被加载时,实例化配置的Bean实例 
	Bean的生命周期: 
		对象创建:当应用加载,创建容器时,对象就被创建了 
		对象运行:只要容器在,对象一直活着 
		对象销毁:当应用卸载,销毁容器时,对象就被销毁了 


2. 当scope的取值为prototype时 
	Bean的实例化个数:多个 
	Bean的实例化时机:当调用getBean()方法时实例化Bean 
	Bean的生命周期: 
		对象创建:当使用对象时,创建新的对象实例 
		对象运行:只要对象在使用中,就一直活着 
		对象销毁:当对象长时间不用时,被 Java 的垃圾回收器回收了

Bean生命周期配置

<bean id="" class="" scope="" init-method="" destroy-method=""></bean> 

* init-method:指定类中的初始化方法名称 

* destroy-method:指定类中销毁方法名称

在UserDaoImpl中创建两个方法

public void init(){
        System.out.println("初始化方法执行了。。。");
    }
    
    public void destory(){
        System.out.println("销毁方法执行了。。。。");
    }

然后对这两个方法进行配置

<bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl" init-method="init" destroy-method="destory"></bean>

并执行测试方法

@Test
    public void test4() throws Exception {

        ApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");

        IUserDao userDao = (IUserDao)classPathXmlApplicationContext.getBean("userDao");

        IUserDao userDao2 = (IUserDao)classPathXmlApplicationContext.getBean("userDao");

        System.out.println(userDao);
        System.out.println(userDao2);
    }

在这里插入图片描述
因为是@Test,所以没来得及打印destory方法就已经被销毁了,如果在Tomcat中是可以打印出来的

Bean实例化三种方式

无参构造方法实例化(上面用的就是这种)(最常用)
工厂静态方法实例化
工厂普通方法实例化

无参构造方法实例化

它会根据默认无参构造方法来创建类对象,如果bean中没有默认无参构造函数,将会创建失败

<bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl"/>

工厂静态方法实例化

依赖的jar包中有个A类,A类中有个静态方法m1,m1方法的返回值是一个B对象。如果我们频繁使用
B对象,此时我们可以将B对象的创建权交给spring的IOC容器,以后我们在使用B对象时,无需调用A类中的m1方法,直接从IOC容器获得

在这里插入图片描述

依赖注入概念

依赖注入 DI(Dependency Injection):它是 Spring 框架核心 IOC 的具体实现
在编写程序时,通过控制反转,把对象的创建交给了 Spring,但是代码中不可能出现没有依赖的情
况。IOC 解耦只是降低他们的依赖关系,但不会消除
例如业务层依旧会调用持久层中的方法来对数据库进行操作

  • 这种业务层和持久层的依赖关系,在使用 Spring 之后,就让 Spring 来维护了。简单的说,就是通
    过框架把持久层对象传入业务层,而不用我们自己去获取

先写一个普通的业务层

用无参构造方法创建Dao

	<bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl" ></bean>

如下

public interface IUserService {
    public void save();
}
public class UserServiceImpl implements IUserService {
    @Override
    public void save() {
        //调用dao层的save方法
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        IUserDao userDao = (IUserDao)classPathXmlApplicationContext.getBean("userDao");
        userDao.save();
    }
}

写一个测试方法当作是web层

@Test
    public void test5(){
        IUserService userService = new UserServiceImpl();
        userService.save();
    }

在这里插入图片描述

将UserService也交给Spring管理

简单的说,就是通过框架把持久层对象传入业务层,而不用我们自己去获取
减少new的出现
在applicationContext.xml中配置

<bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl" ></bean>
    
    <!--配置UserService-->
    <bean id="userService" class="com.lagou.service.impl.UserServiceImpl"></bean>

那么
再写一个测试方法当作是web层

@Test
    public void test5(){
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        IUserService userService = (IUserService)classPathXmlApplicationContext.getBean("userService");
        userService.save();
    }

在这里插入图片描述
但是现在Service层和Dao层还是存在依赖关系,Service层中还是要去获取到dao对象
在这里插入图片描述
这里是手动维护了UserService和UserDao的关系
现在依赖注入就是借助Spring框架来维护依赖关系

Bean依赖注入方式

实际上就是对上述代码进行改造
总共三种:

  1. 构造方法注入
  2. set方法注入
  3. P命名空间注入

构造方法注入

首先对applicationContext.xml中的bean标签进行改造(原来是直接调用无参构造)

  • <constructor-arg这个标签就是提醒spring要使用有参的方式进构造
  • index="0"代表UserServiceImpl类中的第一个参数进行赋值
  • type=“com.lagou.dao.IUserDao” 代表当前第一个参数的实例类型
  • ref=“userDao” 前面已经配置好了userDao的实例存到IOC容器中,ref代表引用IOC中的userDao对象把该对象注入到第一个参数中
<?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">

    <!--在spring配置文件中配置 UserDaoImpl
        id:唯一标识
        class:类全路径
    -->
    <bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl" ></bean>

    <!--配置UserService-->
    <bean id="userService" class="com.lagou.service.impl.UserServiceImpl">
        <constructor-arg index="0" type="com.lagou.dao.IUserDao" ref="userDao"/>
    </bean>

</beans>

可以简化为

<!--配置UserService-->
    <bean id="userService" class="com.lagou.service.impl.UserServiceImpl">
        <!--<constructor-arg index="0" type="com.lagou.dao.IUserDao" ref="userDao"/>-->
        <constructor-arg name="userDao" ref="userDao"/>
    </bean>

name="userDao"代表要给UserServiceImpl中userDao这个值注入对象

public class UserServiceImpl implements IUserService {

    private IUserDao userDao;//接受注入进来的userDao实例,当前还是空的

    public UserServiceImpl(IUserDao userDao) {
        this.userDao = userDao;//把接收到的参数值赋值给成员变量
    }//对有参构造进行生成

    @Override
    public void save() {
        userDao.save();
    }
}

测试一下

@Test
    public void test5(){
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        IUserService userService = (IUserService)classPathXmlApplicationContext.getBean("userService");
        userService.save();
    }

在这里插入图片描述

set方式注入

在UserServiceImpl中创建set方法

public class UserServiceImpl implements IUserService {

    private IUserDao userDao;//接受注入进来的userDao实例,当前还是空的

    public void setUserDao(IUserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void save() {
        userDao.save();
    }
}

在applicationContext.xml中配置Spring容器调用set方法进行注入

<!--采用set方法完成依赖注入-->
        <property name="userDao" ref="userDao"></property>

ref配置的是引用对象,把容器中userDao这个对象注入给name="userDao"这个属性

set方法比构造方法更经常使用,这两种方法都要求掌握

Bean依赖注入的数据类型

上面操作,都是注入Bean对象,除了对象的引用可以注入,普通数据类型和集合都可以在容器中进
行注入。注入数据的三种数据类型

  1. 普通数据类型
  2. 引用数据类型
  3. 集合数据类型

其中引用数据类型,之前的操作都是对UserDao对象的引用进行注入的。下面以set方法注入为例,演示普通数据类型和集合数据类型的注入

public class UserDaoImpl implements IUserDao {

    private String username;
    private Integer age;

    public void setUsername(String username) {
        this.username = username;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public void save() {

        System.out.println("dao被调用了...");
        System.out.println(username);
        System.out.println(age);
    }
}

在applicationContext中的相应配置:

<bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl" >
        <!--ref用于引用数据类型的注入,value用于普通数据类型的注入-->
        <property name="username" value="zimu"></property>
        <property name="age" value="18"></property>
    </bean>

在这里插入图片描述
就完成了普通数据类型的注入

注入集合数据类型

public class UserDaoImpl implements UserDao { 
	private List<Object> list; 
	public void save() { 
		System.out.println(list); 
		System.out.println("保存成功了..."); 
		} 
	}

这里list集合的类型是Object,所以是可以存引用对象的
这里先创建一个User类
在这里插入图片描述
并在applicationContext中进行配置

<!--配置user对象-->
    <bean id="user" class="com.lagou.domain.User">
        <property name="username" value="刘岩"/>
        <property name="age" value="18"/>
    </bean>

<bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl" >

        <!--集合类型的注入-->
        <property name="list">
            <list>
                <value>aaa</value>
                <ref bean="user"></ref>
            </list>
        </property>
    </bean>

配置文件模块化

Spring的配置内容非常多,这就导致Spring配置很繁杂且体积很大,所以,可以将部分
配置拆解到其他配置文件中,也就是所谓的配置文件模块化
原则:

  1. 按层进行拆分
  2. 按业务模块进行拆分
    两种方法
    1)并列的多个配置文件
	ApplicationContext act = new ClassPathXmlApplicationContext("beans1.xml","beans2.xml","...");

2)主从配置文件

<import resource="applicationContext-xxx.xml"/>

注意:
同一个xml中不能出现相同名称的bean,如果出现会报错
多个xml如果出现相同名称的bean,不会报错,但是后加载的会覆盖前加载的bean

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值