JavaWeb进阶修炼手册27---核心框架:spring(一)

一、今日内容

二、spring前言

1. 为什么要学习spring框架

传统web程序的问题:程序间耦合度高

其实,使用传统的servlet和jsp已经可以写出web程序了。但是,这样写出来的程序有一个问题,就是程序的耦合度太高,换句话说就是程序的各个模块不够独立,使得修改一个模块时,往往要修改与其相关的所有模块,这样导致程序在后期维护的时候成本特别高。

而spring主要就是为了解决这个问题的:降低程序的耦合度。

相关概念:
	1. 程序的耦合:指程序间的依赖关系。
		* 包括:类之间的依赖、方法之间的依赖。
	2. 解耦:降低程序间的依赖关系。
		* 特点:实际开发过程中应做到:编译器不依赖,运行时依赖
	3. 解耦的思路:
		* 步骤:
			1. 使用反射技术来创建对象,而避免使用new关键字。
			2. 通过读取配置文件来获取要创建的对象的全限定类名。

记住,解耦是一种思想,而不是一种技术。上面说的解耦的思路只是解耦的一种具体方式。下面我简单说一下,为什么避免new关键字就能减低程序的耦合度:

如果某个方法或者类中new了一个对象,那么这个方法或者类就与new产生了因果,因为这个new出来的对象是这段程序主动建立的。就好比我现在做了一道菜,因为菜是我做的,所以我与菜有关系,存在耦合。而现在,我请一个保姆来做菜,且这个保姆做很多菜,那么这些菜之间是没有关系,菜现在只与保姆有耦合。所以,在程序中,我们利用第三方来建立对象,不能直接在程序中new出对象,如何做到?用一个工厂,工厂负责利用反射技术创建对象,工厂可以理解为一个专门生产并存放对象的容器。而spring框架内部就有这个工厂。
上述过程可以用下面一张图描述:
在这里插入图片描述

注意,此时,创建的对象与工厂有耦合,是工厂创建的,以前是类或方法中的代码块主动建立的,创建对象的权力由程序主动建立交给了工厂建立,控制权发生了反转,这就是spring的核心,IOC控制反转,后面会讲到,现在有个了解就行。

2. 手动创建工厂之感受工厂

接下来,我们手动的建立一个简单的工厂,来体验一下解耦的感觉。

步骤:
	1. 需要一个配置文件来配置我们的service和dao的唯一标识:全限定类名
	2. 建立一个Bean工厂
		解释:
			1. Bean:在计算机英语中,是可重用组件的意思。
			2. javaBean:用java语言编写的可重用组件。 JavaBean包含实体类,JavaBean的范围更大。
	3. 在工厂中读取配置文件内容,反射创建对象。
  1. 建立工厂的配置文件factoryConfig.properties和其他的类文件:
    配置文件不一定是propertis的,框架使用更多的是xml格式的,这里用propertis做配置文件只是为了方便。

在这里插入图片描述
2. 建立一个工厂:BeanFactory.java类

package cn.wanghao.factory.myFactory;

import cn.wanghao.factory.service.impl.UserServiceImpl;

import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

public class BeanFactory {
    /* 存放单例对象的容器 */
    private static Map<String,Object> beans;

    //初始化
    static {
        //1. 创建properties对象
        Properties properties = new Properties();
        //2. 读取配置文件
        InputStream is = BeanFactory.class.getClassLoader().getResourceAsStream("factoryConfig.properties");
        try {
            properties.load(is);
            //3. 创建实例对象们
            Enumeration keys = properties.keys();
            beans = new HashMap<String,Object>();
            while (keys.hasMoreElements()) {
                String key =  keys.nextElement().toString();
                //获取value
                String value = properties.getProperty(key);
                //反射创建对象
                Object instance = Class.forName(value).newInstance();
                //把key和value放入容器中
                beans.put(key, instance);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    public static Object getBeans(String beanName) {
        return beans.get(beanName);
    }
}
  1. 使用工厂创建对象,现在在servlet里面建立一个Demo类,来模拟servlet处理请求:

在这里插入图片描述
工厂利用反射创建对象,我使用的是单例模式,主要是因为单例模式节省资源。

三、spring学习

1. spring概述
1. spring:Spring是分层的 Java SE/EE应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control: 反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层 Spring MVC
           和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多 著名的第三方框架和类库,逐渐成为使用最多的Java EE 企业应用开源框架。 
2. 优点:
	1. 方便解耦,简化开发
		* 解释:通过 Spring提供的 IoC容器,可以将对象间的依赖关系交由 Spring进行控制,避免硬编码所造 成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
	2. AOP编程的支持 
		* 解释:通过 Spring的 AOP 功能,方便进行面向切面的编程,许多不容易用传统OOP 实现的功能可以通过 AOP 轻松应付。 
	3. 声明式事务的支持
		* 解释:可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理, 提高开发效率和质量。
	4. 方便程序的测试 
		* 解释:可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可 做的事情。 
	5. 方便集成各种优秀框架 
		* 解释:Spring可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz 等)的直接支持。
  • spring 的体系结构如下图所示
    在这里插入图片描述
2. spring的IOC解决程序耦合
1. 耦合:在软件工程中,耦合指的就是就是对象之间的依赖性。对象之间的耦合越高,维护成本越高。因此对象的设计应使类和构件之间的耦合最小。
         软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。划分模块的一个 准则就是高内聚低耦合。 

2. 内聚:内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事。通俗的讲,就是一个方法尽量只做一件事。

3. 程序设计原则:高内聚低耦合。如果模块间必须存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合。

4. IOC(控制反转):  把创建对象的权力交给框架,是框架的重要特征。他包括依赖注入(DI)和依赖查找(DL)。至于为什么叫控制反转,在前面自己创建工厂,降低耦合的过程中我已经讲了。

5. 明确IOC作用:削减计算机程序的耦合(解除我们代码中的依赖关系)。 

6. IOC容器是一个Map结构。

7. IOC创建的对象是实例对象,而不是代理对象。spring可以使用JDK动态代理和cglib动态代理,默认的是JDK动态代理,使用cglib代理需要用以下代码进行配置:
	<aop:aspectj-autoproxy  proxy-target-class="true"/>

8. 如果mybatis整合到spring中,则创建的代理对象也会交给IOC,且代理对象的名字和IOC实例化对象名字的规范一样,也是类名首字母小写;如果是JDK动态代理,则类型是其接口类型;如果是cglib动态代理,则类型是该类的类型。
3. Spring基于 XML 的 IOC

spring 中工厂的类结构图
在这里插入图片描述
要使用XML配置,首先需要在Resources目录下建立一个xml文件,假设我建立的是bean.xml。然后将下面的代码粘贴到文件中就启动了xml配置:

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">
        
    <bean id="..." class="...">  
        <!-- collaborators and configuration for this bean go here -->
    </bean>

</beans>

下面是关于如何创建对象的知识点:

1. BeanFactory和 ApplicationContext(了解)
	* 关系:BeanFactory是spring的顶层接口,ApplicationContext是他的一个子接口
	* 区别: 创建对象的时间点不一样
		1. ApplicationContext(常用): 采用立即加载的方式。只要一读取配置文件,默认情况下就会创建对象。 
		2. BeanFactory:采用延迟加载的方式。什么使用什么时候创建对象。 

		* 注意:从上面可以看出BeanFactory适用于多例模式,ApplicationContext适用于单例模式。但是,在实际的开发中,我们都会用ApplicationContext,
			    通过设置bean标签的属性设置是创建对象是单例还是多例,从而单例/多例的情况都可以照顾到。

2. ApplicationContext 接口的三个实现类:
	1. ClassPathXmlApplicationContext(常用):它是从类的根路径下加载配置文件(从Resources目录下加载)
		* 一般使用该实现类来创建工厂
		* 例如:
			ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
			ac.getBean("user", User.class);
	2. FileSystemXmlApplicationContext:它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。 
	3. AnnotationConfigApplicationContext: 当我们使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。 

3. IOC 中(即xml配置文件中) bean 标签和管理对象
	1. bean 标签 (用来创建对象)
		* 作用:用于配置对象让 spring 来创建的。 
		* 属性:
			1. id:给对象在容器中提供一个唯一标识。用于获取全限定类名。 
			2. class:指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数。 
			3. scope:指定对象的作用范围。 
				* scope属性值:
					1. singleton(常用) :默认值,单例的. 
					2. prototype(常用) :多例的.
					3. request :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中.    
					4. session :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中.    
					5. global session :WEB 项目中,应用在 Portlet 环境.如果没有 Portlet 环境那么 globalSession 相当于 session.
			4. factory-bean 属性:用于指定实例工厂 bean 的 id。 
			5. factory-method 属性:用于指定实例工厂中创建对象的方法。
			6. init-method:指定类中的初始化方法名称。 
			7. destroy-method:指定类中销毁方法名称。 
			* 注意:1、2、4、5结合起来设置实例化Bean的方式,3用来设置实例化对象的范围, 6、7与生命周期相关。

	2.  单例对象和多例的对象的生命周期
		1. 单例对象:scope="singleton" 
			* 特点:一个应用只有一个对象的实例。它的作用范围就是整个应用。 
			* 生命周期:
				对象出生:当应用加载,创建容器时,对象就被创建了。  
				 对象活着:只要容器在,对象一直活着。    
				 对象死亡:当应用卸载,销毁容器时,对象就被销毁了。
		2.  多例对象:scope="prototype" 
			* 特点:每次访问对象时,都会重新创建对象实例。 
			* 生命周期:
				对象出生:当使用对象时,创建新的对象实例。    
				对象活着:只要对象在使用中,就一直活着。    
				对象死亡:当对象长时间不用时,被 java 的垃圾回收器回收了。 

	3. 实例化 Bean 的三种方式
		1. 使用默认无参构造函数 
				<!--在默认情况下:  它会根据默认无参构造函数来创建类对象。如果 bean 中没有默认无参构造函数,将会创建失败。 --> 
				<bean id="id" class="要创建的对象的全限定类名"/> 
		2. 第二种方式:静态工厂的静态方法创建对象 (调用方法,返回对象)
			<bean id="id"  class="静态工厂类的全限定类名"     factory-method="静态工厂类的静态方法名"></bean> 
		3. 使用实例工厂的方法创建对象(调用方法,返回对象):
			<!-- 此种方式是:    先把工厂的创建交给 spring 来管理。然后在使用工厂的 bean 来调用里面的方法   
					factory-bean 属性:用于指定实例工厂 bean 的 id。factory-method 属性:用于指定实例工厂中创建对象的方法。  -->
			<bean id="uid" class="工厂的全限定类名"></bean>
			<bean id="id"  factory-bean="uid(上面工厂的id)"     factory-method="工厂类的方法名"></bean> 
4. spring基于XML的依赖注入
1. 依赖注入:Dependency Injection。
		说明:我们的程序在编写时,通过控制反转,把对象的创建交给了 spring,但是代码中不可能出现没有依赖的情况。 ioc 解耦只是降低他们的依赖关系,但不会消除。
			  比如,实例化对象时,如何给其类成员变量赋值?这些需要依赖的地方也交给spring来做。
2. 作用:设置一些必须的依赖关系。简单点说就是在spring创建对象时,给其传递创建对象的参数。

3. 能注入的内容:
		1. 基本类型和String
		2. 其他bean类型(在配置文件中或者注解配置过的bean)
		3. 复杂类型(集合类型)

4. 注入的方式:
		1.  使用构造函数
				* 使用标签:constructor-arg标签
				* 标签出现的位置:bean标签的内部
				* 标签中的属性:
							* type:指定参数在构造函数中的数据类型 
							* index:指定参数在构造函数参数列表的索引位置 ,从0开始
							* name(常用):指定参数在构造函数中的名称 
							=======上面三个都是找给谁赋值,下面两个指的是赋什么值的============== 
							* value:它能赋的值是基本数据类型和 String 类型 
							* ref:它能赋的值是其他 bean 类型,也就是说,必须得是在配置文件中配置过的 bean 
				* 优点:确保构造函数中设置的类成员变量一定有值,如果少参数,会无法创建对象。
				* 缺点:不够灵活,如果我们构造函数中设置的类成员变量,也必须提供所有的参数值。
		2.  使用set方法提供(要创建的对象的类中一定要有相关的setter方法)
					* 使用标签:property
					* 出现的位置:bean标签的内部
					* 标签的属性:
							* name:用于指定注入时所调用的set方法名词(即填类的属性,不是类成员变量)
							* value:它能赋的值是基本数据类型和 String 类型 
							* ref:它能赋的值是其他 bean 类型,也就是说,必须得是在配置文件中配置过的 bean 
					* 优点:创建对象时不需要指定所有的参数,可以设置想设置的,灵活。
					* 缺点:如果成员必须有值,可能出现没有设置值的情况。
		3.  使用注解提供

5. 注入依赖时,值为集合/复杂类型:
		* 说明:基本类型和String用构造函数或者set注入的value属性,bean类型用构造函数或者set注入的ref属性设置,而集合/复杂类型需要用新的标签
		* 标签位置:依赖注入的 constructor-arg标签/property标签 的内部。
		* 标签:
				1. array
					* 例如:
							<property name="myList">   
								<array>    
									<value>AAA</value>    
									<value>BBB</value>    
								</array>  
							</property> 
				2. list
					* 例如:
						<property name="mySet"> 
							<list>    
								<value>AAA</value>    
								<value>BBB</value>    
							</list>  
						</property>
				3. set
					* 例如:
						<property name="myStrs"> 
							<set> 
								<value>AAA</value>
								<value>BBB</value>    
							</set> 
						 </property> 
				4. map
					* 例如:
						<property name="myProps"> 
							<map>    
								<entry key="testA" value="aaa"></entry> 
								<entry key="testB"> 
									<value>bbb</value> 
								</entry>   
							</map>  
						</property>
				5. property
					* 例如:
						<property name="myMap">   
							<props>
								<prop key="testA">aaa</prop>
								<prop key="testB">bbb</prop>
						    </props> 
						 </property> 

比如:
在这里插入图片描述

5. Spring基于注解的 IOC 和其依赖注入

首先,要明确,基于注解配置的IOC与基于XML配置的IOC实现的功能都是一模一样的,都是IOC思想的具体实现。只是实现的方式不一样。关于实际的开发中到底使用xml还是注解,每家公司有着不同的使用习惯。所以这两种配置方式我们都需要掌握

要使用注解配置有两种方法:

  1. 方法一:首先需要在Resources目录下建立一个xml文件,假设我建立的是bean.xml。然后将下面的代码粘贴到文件中就启动了xml配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!--告知spring在创建容器时要扫描的包,配置所需要的标签不是在beans的约束中,而是一个名称为context名称空间和约束中-->
    <context:component-scan base-package="填项目的全限定类名,比如cn.wanghao.spring"></context:component-scan>
</beans>
  1. 方法二:创建一个类,在上面@Configuration和@ComponentScan(“填项目的全限定类名,比如cn.wanghao.spring”) 注释
    在这里插入图片描述

下面介绍注解配置的相关知识点:

* IOC 中注解方式管理对象

	1. 用于创建对象的:相当于:<bean id="" class="">  
		1. @Component
			* 作用:把资源让 spring 来管理。相当于在 xml 中配置一个 bean。
			* 属性:value:指定 bean 的 id。如果不指定 value 属性,默认 bean 的 id 是当前类的类名。首字母小写。 
			* 比如:@Component("user")
		2. @Controller:一般用于表现层
		3. @Service:一般用于业务层的注解
		4. @Repository:一般用于持久层的注解
		* 注意:@Component是@Controller、@Service、@Repository的父类,后三者只是为了规范化注解而产生的,所以用法和@Component一样

	2. 用于注入数据的 :相当于:<property name="" ref=""> 或<property name="" value=""> 
		1. @Autowired
			* 作用:在spring的IOC容器中找到数据类型与被@Autowired注释的属性类型相同的数据,如果该数据唯一则就是该数据。如果不唯一,则会根据属性名再在里面匹配,如果没有则报错。
		2. @Qualifier
			* 作用: 在自动按照类型注入的基础之上,再按照 Bean 的 id 注入。它在给字段注入时不能独立使用,必须和 @Autowire 一起使用;但是给方法参数注入时,可以独立使用。 
			* 属性: value:指定容器中bean 的 id。
			* 举例:
					@Autowired
					@Qualifier("${user1}")
		3. @Resource 
			* 作用:直接按照 Bean 的 id 注入。它也只能注入其他 bean 类型。 
			* 属性:name:指定 bean 的 id。 
		4. @Value 
			* 作用:注入基本数据类型和 String 类型数据的 
			* 属性:value:用于指定值 
			* eg:
				@value("${name}")
				private String name;

	3. 用于改变作用范围的: 相当于:<bean id="" class="" scope=""> 
		1. @Scope 
			* 作用:指定 bean 的作用范围。 
			* 属性:value取值:singleton  prototype request session globalsession 

	4. 和生命周期相关的: 相当于:<bean id="" class="" init-method="" destroy-method="" /> 
		1.  @PostConstruct 
			* 作用:指定初始化方法
		2. @PreDestroy 
			* 作用:指定销毁方法

	5. 新注解:取代xml配置的注解
		1. @Configuration
			* 作用:用于指定当前类是一个 spring 配置类,当创建容器时会从该类上加载注解。获取容器时需要使用 AnnotationApplicationContext(有@Configuration 注解的类.class)。 
			* 属性:value:用于指定配置类的字节码 

		2. @ComponentScan
			* 作用:用于指定 spring 在初始化容器时要扫描的包。作用和在 spring 的 xml 配置文件中的: <context:component-scan base-package="com.itheima"/>是一样的。 
			* 属性:basePackages:用于指定要扫描的全限定包。和该注解中的 value 属性作用一样。 
			* eg:
				@Configuration
				@ComponentScan("com.itheima") 
				public class SpringConfiguration { 
				}

		3. @Bean
			* 作用:该注解只能写在方法上,表明使用此方法创建一个对象,并且放入 spring 容器。 
			* 属性:name:给当前@Bean 注解方法创建的对象指定一个名称(即 bean 的 id)。 
			* eg:
				@Bean(name="dataSource")  
				public DataSource createDataSource() {
				   try {    
				   		ComboPooledDataSource ds = new ComboPooledDataSource();    
				   		ds.setUser("root");    ds.setPassword("1234");    
				   		ds.setDriverClass("com.mysql.jdbc.Driver");
				   		ds.setJdbcUrl("jdbc:mysql:///spring_day02");    
				   		return ds;   
				   } catch (Exception e) {
				       throw new RuntimeException(e);   
				   }  
				} 

		4. @PropertySource 
			* 作用:用于加载.properties 文件中的配置。例如我们配置数据源时,可以把连接数据库的信息写到 properties 配置文件中,就可以使用此注解指定 properties 配置文件的位置。 
			* value[]:用于指定 properties 文件位置。如果是在类路径下,需要写上 classpath: 
			* 有两种用法:
				1. 
						  @Configuration
						  @PropertySource(value = "classpath:application.properties",ignoreResourceNotFound = false)
						  public class SpringPropertysourceApplication {
						
						    @Resource
						    Environment environment;
						
						    @Bean
						    public TestBean testBean(){
						      TestBean testBean = new TestBean();
						      // 读取application.properties中的name
						      testBean.setName(environment.getProperty("com.spring.name"));
						      // 读取application.properties中的age
						      testBean.setAge(Integer.valueOf(environment.getProperty("com.spring.age")));
						      System.out.println("testBean = " + testBean);
						      return testBean;
						    }
						
						    public static void main(String[] args) {
						      ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringPropertysourceApplication.class);
						      TestBean testBean = (TestBean)applicationContext.getBean("testBean");
						
						    }
						  }
				2. 
						 @Component
						 @PropertySource("classpath:db.properties")
						  public class DBConnection {
						
						    @Value("${DB_DRIVER_CLASS}")
						    private String driverClass;
						
						    @Value("${DB_URL}")
						    private String dbUrl;
						
						    @Value("${DB_USERNAME}")
						    private String userName;
						
						    @Value("${DB_PASSWORD}")
						    private String password;
						
						    public DBConnection(){}
						
						    public void printDBConfigs(){
						      System.out.println("Db Driver Class = " + driverClass);
						      System.out.println("Db url = " + dbUrl);
						      System.out.println("Db username = " + userName);
						      System.out.println("Db password = " + password);
						    }
						  }
		5. @Import 
			* 作用:用于导入其他配置类,在引入其他配置类时,可以不用再写@Configuration 注解。当然,写上也没问 题。
			* 属性: value[]:用于指定其他配置类的字节码。
			* eg:
					@Configuration
					@ComponentScan(basePackages = "com.itheima.spring") 
					@Import({ JdbcConfig.class}) 
					public class SpringConfiguration { } 

					@Configuration 
					@PropertySource("classpath:jdbc.properties") 
					public class JdbcConfig{ } 

	6. 获取容器:
		ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class); 

	7. 单元测试时:
		@RunWith(SpringJUnit4ClassRunner.class)
		@ContextConfiguration(locations= {"classpath:bean.xml"}) 
		public class AccountServiceTest { } 

将上面的xml配置的例子改为注解配置:
在这里插入图片描述

6. 关于 Spring IOC依赖注入的几种方式对比

在这里插入图片描述
由于使用SSM框架,常用的是xml和注解的方式注入,所以下面详细对比一下这两种使用方法:

1. 注解优势(一般用来创建自定义类的对象):配置简单,维护方便(我们找到类,就相当于找到了对应的配置)。 
2. XML(一般用于创建第三方类的对象):修改时,不用改源码。不涉及重新编译和部署

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

ElegantCodingWH

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

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

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

打赏作者

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

抵扣说明:

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

余额充值