spring初体验

spring初体验

关于spring在这里插入图片描述

在学习spring前,让我们带着这些着问题来学习spring吧。

第一个问题:什么是spring,有什么特点?

Spring是一个开放源代码的设计层面框架,他解决的是业务逻辑层和其他各层的松耦合问题,因此它将面向接口的编程思想贯穿整个系统应用。Spring是于2003年兴起的一个轻量级的Java 开发框架,由Rod Johnson创建。简单来说,Spring是一个分层的JavaSE/EE full-stack(一站式) 轻量级开源框架。(来源度娘)
重要思想:
控制反转——Spring通过一种称作控制反转(IoC)的技术促进了低耦合。当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。你可以认为IoC与JNDI相反——不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。(来源度娘)
面向切面——Spring提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计和事务管理)进行内聚性的开发。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。

第二个问题:为什么要使用spring?

1.因为控制反转,依赖注入:我喜欢这个大佬婚介所的例子:他把spring容器比作婚介所,然后每一个类比作想寻偶的人。当他想寻偶时,他需要向婚介所提出寻偶要求(向容器按条件请求对象),然后婚介所按照他的要求给他介绍了这样条件的人(就是返回按条件查找出来的对象)。其实婚介所里面有多少人(多少对象),他们什么时候加入的(创建),什么时候退出(销毁)的我们都不需要关心。我们只要和这个介绍过来的对象完成事务就好了。
http://www.php.cn/java-article-368875.html
2.,面向切面切面:

所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。

开发环境:

JDK 1.8.0,Eclipse oxygen,数据库 Mysql+Navicat

开始我们的spring工程

我们前部分是讲解控制反转(IOC),后面会提及到面向切面(aop)。

第一步:新建一个java web项目

工程架构如下:
项目结构图

第二步:导入jar包

接下来在把所有要用到的jar包复制到webContent-> WEB-INF->lib文件下(如果没有lib文件夹的童鞋就自己照着这个路径新建一个就好)。然后选中这些所有的包右键选择Build Path->add to Bulid path,这样你就导入所有的包啦。
本次spring体验要用到的jar包。

第三步:新建Spring配置的xml文件

这里我们使用官方的推荐,命名为applicationContext.xml,将它放在src文件下(如果放在WebContent下,会报找不到配置文件的错误,不过也有解决办法哟)。首先我们在新建的applicationContext.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:util="http://www.springframework.org/schema/util" 
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
</beans>

和上一节课配置mybatis中配置大同小异,当你还需要其他的标签时在其中的位置添加网站就好。

什么是bean?

https://www.cnblogs.com/wuchanming/p/5426746.html 里面图讲解非常好,大家可以看看。

第四步:怎么创建一个bean

在Spring中使用xml方式来配置bean的方法有三种:

第1种:使用无参构造函数创建bean:

我们在pojo类下新建一个PeoplePojo的类,他张这个样子:

public class PeoplePojo {
	private int cid;
	private String name;
	private int age;
	private String sex;
	public PeoplePojo() {
		System.out.println("大家好,我是PeoplePojo的无参构造方法,我被执行啦");
	}
	public int getCid() {
		return cid;
	}
	public void setCid(int cid) {
		this.cid = cid;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public String getSex() {
		return sex;
	}
	public void setSex(String sex) {
		this.sex = sex;
	}
	@Override
	public String toString() {
		return "PeoplePojo [cid=" + cid + ", name=" + name + ", age=" + age + ", sex=" + sex + "]";
	}
}

我们要用无参的构造函数来创建bean,下面我们去配置文件applicationContext.xml下的<beans></beans>中添加一个<bean><bean>标签或者<bean/>便签。现在我需要填写两个属性一个是id,另外一个是class。

<bean id="poeple" class="com.hg.pojo.PeoplePojo"/>

第一个id是一会儿我们通过bean创建对象是要用到的属性,class需要填写该类的所在路径(这里有个小窍门就是在java文件中,将光标放到类名上,右键 - Copy Qualified Name),自己填写的话会容易出错哟。
到这里我们的第一个spring的bean就配置好啦。我们来测试一下吧。
这次我们将会使用到junit测试,我在刚导包时已经导入了Junit的包了,所以我就简单讲讲他是怎么使用的吧。首先我们新建一个test的package包,在该包下新建一个test的类。接下来我们建立一个我们的测试方法,第一步我们要建一个public的方法,无返回值填写void,最后我们在写好的方法上加上一个@Test的注解,我们的测试方法就配置好了。

	@Test
	public void 方法名() {
	}

接下来可开始我们的测试,由于加载配置文件在 每次测试都会用到,所以我把它用静态的方法加载在类中了。

	public class MyTest {
	// 加载Spring配置文件,把配置文件中的对象进行创建
	private static ApplicationContext context = null;
	static {
		context = new ClassPathXmlApplicationContext("applicationContext.xml");
	}
	// 1.创建对象
	// 1-11.使用无参构造函数,创建对象
	@Test
	public void test1() {
		// 根据配置文件的id得到PeoplePojo对象
		PeoplePojo peoplePojo = (PeoplePojo) context.getBean("poeple");
		System.out.println(peoplePojo.toString());
	}
}

步骤是不是很简单,只需要一个context,getbean(“刚刚在bean的id名”),我们就能获取到对象,不过需要强制转化下。

思考:context.getBean()获得对象是属于什么类呢?

因为我们刚刚在无参构造函数中加了一句:大家好,我是PeoplePojo的无参构造方法,我被执行啦,所以执行结果就是:
在这里插入图片描述

第2种:使用静态工厂(了解)

首先我们还是新建一个类,名字就叫AClass::

	public class AClass {
	public AClass() {
		System.out.println("大家好,我是AClass的无参构造方法,我被执行啦");
	}
}

既然使用的静态工厂的方法来创建类,那么我们在factoryd的包下新建一个MyFactory的类:

	public class MyFactory {
		public static AClass getAClass()
		{
			System.out.println("我使用了静态工厂的方法创建了AClass");
			return new AClass();
		}
	}

注意这里MyFactory 中方法一定是加上static的静态方法。最后我们回到配置文件中添加上bean是我们的AClass:

	<bean id="Aclass" class="com.hg.factory.MyFactory" factory-method="getAClass"/>

最后回到我们的测试类中:

	// 1-12.使用静态工厂(了解)
	@Test
	public void test2() {
		AClass aClass = (AClass) context.getBean("Aclass");
		System.out.println(aClass.toString());
	}

结果:在这里插入图片描述

思考:这个也用了new class()的方法,spring这样设计的目的是?

第3种:使用实例工厂(了解)

哈哈还是新建一个BClass类:

	public class BClass {
	public BClass() {
		System.out.println("大家好,我是BClass的无参构造方法,我被执行啦");
	}
}

然后创建一个工厂,这次工厂创建对象不需要静态方法了:

	public class MyFactory1 {
	public BClass getBClass()
	{
		System.out.println("我使用实例工厂的方法创建了BClass");
		return new BClass();		
	}
}

最后回到配置文件中,与刚刚创建静态方法有点点不同:这次需要配置两个bean分别是factory的bean和我们BClass的bean;

	<!-- 1先创建工厂对象 -->
	<bean id="MyFactory1" class="com.hg.factory.MyFactory1"/>
	<!-- 2再使用工厂对象创建BClass对象 -->
	<bean id="BClass" factory-bean="MyFactory1" factory-method="getBClass"/>

看看我们的测试吧:

	// 1-13.使用实例工厂(了解)
	@Test
	public void test3() {
		BClass bClass = (BClass) context.getBean("BClass");
		System.out.println(bClass.toString());
	}

测试结果为:
在这里插入图片描述

思考:相信聪明到这已经发现了一个问题,我们在运行test3()的是否发现运行台输出的结果是:

在这里插入图片描述
为什么即使我不实例化一个类,也会创建一个类,在下面我们会找到答案的。

第五步: Spring中Bean的属性注入

这你也同样有三种方法可以供我们使用,方别是构造方法的方式注入属性,set方法的方式注入属性,对象类型的注入。

第一种:构造方法的方式注入属性

首先我们新建一个DClass,代码如下:

	public class DClass {
	private int id;
	public DClass() {
		System.out.println("大家好,我是DClass的无参构造方法,我被执行啦");
	}
	public DClass(int id) {
		super();
		System.out.println("大家好,我是DClass的有参构造方法,我的id是" + id);
		this.id = id;
	}
}

在applicationContext.xml中需要这么配置:

	<bean id="DClass" class="com.hg.pojo.DClass">
		<constructor-arg name="id" value="123456"/>
	</bean>

测试:

	@Test
	public void test5() {
		DClass dClass = (DClass) context.getBean("DClass");
		System.out.println(dClass.toString());
	}

结果:
在这里插入图片描述

第二种:set方法的方式注入属性

新建一个EClass类,代码如下:

	public class EClass {
	private int id;
	public EClass() {
		System.out.println("大家好,我是EClass的无参构造方法,我被执行啦");
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
}

在applicationContext.xml中需要这么配置:

	<bean id="EClass" class="com.hg.pojo.EClass">
		<property name="id" value="01234"></property>
	</bean>

测试代码:

	@Test
	public void test6() {
		EClass eClass = (EClass) context.getBean("EClass");
		System.out.println(eClass.getId());
	}

结果:
在这里插入图片描述

第三种:对象类型的注入

这个实力我们就来模拟一下实际工程中的dao层和service层的工作原理吧。首先我们新建一个实体类StudentPojo:

	public class StudentPojo{
	private int uid;
	private String username;
	private String psw;
	public int getUid() {
		return uid;
	}
	public void setUid(int uid) {
		this.uid = uid;
	}
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public String getPsw() {
		return psw;
	}
	public void setPsw(String psw) {
		this.psw = psw;
	}
	@Override
	public String toString() {
		return "StudentPojo [uid=" + uid + ", username=" + username + ", psw=" + psw + "]";
	}
}

然后我们模拟操作他的数据操作层(dao层),在dao的package中新建一个StudentDao的类:

	public class StudentDao {
	public void insertStudent(StudentPojo pojo) {
		System.out.println("我现在在dao层,向学生表中插入一名学生!");
	}
}

接下来我们实现一个StudentService的接口,代码如下:

	public interface StudentService{
	public void add(StudentPojo StudentPojo);
}

好了再来一个实现类:

	public class StudentServiceImpl implements StudentService{
	private StudentDao dao;
	public StudentDao getDao() {
		return dao;
	}
	public void setDao(StudentDao dao) {
		this.dao = dao;
	}
	public void add(StudentPojo StudentPojo) {
		System.out.println("我现在在serviceImpl层,执行dao层的insert操作!");
		dao.insertStudent(StudentPojo);
	}
}
}

到现在我们就可以去测试了。现在假设我们已经从前端拿到了一个关于要插入的学生信息了。现在我们把信息交到了StudentService的add中来处理,在add方法中我们再调用了dao层的StudentDao 中的insertStudent。好啦至此一个“简单”的数据插入工作就完成了。配置文件如下:

	<!--2-13   1.注册studentPojo-->
	<bean name="StudentPojo" class="com.hg.pojo.StudentPojo" />
	<!--2-13   2.注册student dao层-->
	<bean id="StudentDao" class="com.hg.dao.StudentDao"/>
	<!--2-13   1.注册student serviceImpl层-->
	<bean id="StudentServiceImpl"  class="com.hg.service.impl.StudentServiceImpl">
		<property name="dao" ref="StudentDao"></property>
	</bean>

看到了吗我们在StudentServiceImpl中加入一个关于dao层中的set注入,所以我们在bean中加入了<property name=“dao” ref=“StudentDao”>,我们使用ref引用已经创建的bean,这个才是我们真正要演示的效果 。好啦回到我们模拟的学生插入效果的测试中:

	// 2-13对象类型的注入
	@Test
	public void test7() {
		StudentPojo studentPojo = (StudentPojo) context.getBean("StudentPojo");
		StudentService service = (StudentServiceImpl) context.getBean("StudentServiceImpl");
		service.add(studentPojo);
	}

结果:
在这里插入图片描述

思考:其实结果对我们并不重要,大家有发现这个问题吗?StudentService service = (StudentServiceImpl) context.getBean(“StudentServiceImpl”);我们用一个接口去实现了一个对象的实例化,java允许吗?其实java不仅允许,而且在条件允许的情况下,java鼓励大家这么做。

哈哈哈想知道原因的同学可以到这两个博客中找原因Java中到底是应该用接口类型还是实现类的类类型去引用对象?+面向对象之多态(向上转型与向下转型)
其实相比于传统的xml配置,现在使用注解的方式更加简便和大大减少了代码量。为什么我们还要费劲的学习以上的内容,可能是出于我的个人经验吧,在一开始就使用注解的我,在练习的过程中对很多东西到搞不懂,所以我觉得是有必要学习一下xml配置再去学习简单的注解方式。好啦废话不多说继续我们的spring注解的开发中,你会从中真正感受到矿建注解提供的便利性:

spring注解

这次我决定换个方式来演示一下使用注解的过程。
我们新建一个工程,然后还是像上面一样新建一个applicationContext.xml。
整个工程如下:
在这里插入图片描述
注意到了吗这次我们多导入了一个包,好啦让我们看看applicationContext.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:util="http://www.springframework.org/schema/util"
    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/util http://www.springframework.org/schema/util/spring-util.xsd
         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> 
       <context:component-scan base-package="com.hg.pojo"></context:component-scan>
</beans>

我们先来看看配置文件中多了两句话
在这里插入图片描述
因为添加这两句话,我们就可以使用下面的标签了。这句话的意思是扫描com.hg.pojo下所有类型。这样就可以让pojo包下的类都注册成为bean了吗?还差一步就可以了,我们需要在要成为bean还要类前加入一个@Component的标签就像这样:

@Component("peo")
public class PeoplePojo {
	private String name;
	private String sex;
	private int age;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getSex() {
		return sex;
	}
	public void setSex(String sex) {
		this.sex = sex;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public PeoplePojo() {
		System.out.println("我是人类");
	}
}

在这里提醒一下大家,我在@Component(“peo”)指定了一个id,当然也允许不指定id.那么默认的id就是类的首字母小写。在后面引用的对象名要和id一致(不过我测试别的注解时就没有怎么严格要求那,如果有明白的提醒麻烦告诉我下是怎么回事)。
测试类代码:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:applicationContext.xml"})
public class MyTest {
	@Resource
	private PeoplePojo peo;
	
	@Test
	public void test1()
	{
		peo.setAge(10);
		System.out.println(peo.getAge());
	}
}

我们看到了在测试类前加入两个新的注解,RunWith意思使用所有注释前必须使用@RunWith(SpringJUnit4ClassRunner.class),让测试运行于Spring测试环境,ContextConfiguration的意思是加载applicationContext.xml文件。对于想了解classpath的同学可以点开链接看看。我们在测试类中私有化了一个PeoplePojo 的对象,我们在前面加入了一个@Resource,其实如果我们使用@Autowired也可以达到目的,想知道有什么区别的的同学可以点开链接看看区别。让我们来看看测试结果吧:
在这里插入图片描述
好了简单的注释加载bean就完成了,可能有的同学有几个问题。让我来猜猜大家想问些什么。

第一个问题:只能扫描pojo包,怎么把其他包一起扫描了?

当然可以扫描所有的包,我们只要轻轻的改动一下配置文件就可以:

	<context:component-scan base-package="com.hg"/>

这样就可以扫描到所有的包啦。

第二个问题:我们现在扫描的包要注册的类太多了,会不会不利于开发?

对的,如果我们都只用@Component注解,其实对项目的结构层次还是很模糊的。其实还有@Controller,@Controller,@ Repository等等注解,这几个最常用的注解。他们的作用都是一样的,spring用他们来区分每个模块间的作用:
@Component:是所有受Spring 管理组件的通用形式,@Component注解可以放在类的头上,@Component不推荐使用
@Controller:@Controller对应表现层的Bean,可以用是我们接下来要学的spring mvc。对于spring mvc,我的理解就是传统的servlet。
@ Service:是业务层,处理业务。
@ Repository:可以使用在数据访问层dao层。

第三个问题:我们在每个类中都要加载配置文件吗?

答案是不需要,仅仅在测试类中,我们需要加载配置文件。在其他类中我们已经使用了扫描器了,所有添加了类的注解的类会被扫描进spring容器中,我们只要在test以外的包以外使用,不需要加载配置文件。

第四个问题:为什么注解可以直接声明对象,怎么实现的?

@Autowired注解就是spring可以自动帮你把bean里面引用的对象的setter/getter方法省略,它会自动帮你set/get对象。当你在一个类中引用时一个对象时,其实和我们在对象类型的注入时的例子相似。

	public class StudentServiceImpl implements StudentService{
	private StudentDao dao;
	public StudentDao getDao() {
		return dao;
	}
	public void setDao(StudentDao dao) {
		this.dao = dao;
	}
}

	<bean id="StudentServiceImpl"  class="com.hg.service.impl.StudentServiceImpl">
		<property name="dao" ref="StudentDao"></property>
	</bean>

等于

	public class StudentServiceImpl implements StudentService{
	@Autowired
	private StudentDao dao;
	}

第五个问题:spring的作用域怎么划定?

参见Scope的作用域:https://www.cnblogs.com/lonecloud/p/5745902.html

第六个问题:spring是怎么实现控制反转的,为什么这样设计?

这里会有你的答案:https://www.cnblogs.com/czhfuture/p/3572334.html+https://blog.csdn.net/weixin_42072322/article/details/80254143

我把这次spring体验的两个项目都上传到了github上了,如果有想了解源码或者下载jar包的同学可以去下载。第一个使用xml配置spring的项目:https://github.com/tony-bryant/MySpring01,第二个使用的注解spring项目地址:https://github.com/tony-bryant/MySpring02
(多啰嗦一句,我在第二个项目中不仅仅是应用注解使用spring,更是增加了一些我对多态的一些理解,以及正规开发的一些流程。再多啰嗦一句,在我以往几乎没用到过继承和接口,即使在用,也不是很清楚其中的原理是什么。在编写这个项目时,我开始思考:在我们设计一个类时,为什么要把所有的属性私有了,但是提供公共的set/get方法来操作属性。比如为什么要向上转型等等:通过查阅发现这其中的答案要用到java面向对象的思想,java的处理机制,设计模式原理等等学科知识,才发现原来一个java中的知识浩如烟海,自己还要不停的学习下去才对)。
好了相信你对spring的反转控制和依赖注入有了一定的理解了,

让我们来看看spring的AOP(Aspect Oriented Programming),即面向切面编程:

如果你和我一样对aop有什么用存在疑问,那么推荐你去看看https://blog.csdn.net/baidu_33403616/article/details/70304051
advice一共有五种:

从大神那转的

有了原先的spring基础我们在这就可以提速了哟,这次我们先做一个只有前置通知和后置通知的小项目。

添加上这次桃用的jar包:

在这里插入图片描述

再来到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:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
       <bean id="UserDao" class="com.hg.dao.UserDao"/>
       <bean id="TimeHandler" class="com.hg.aspect.TimeHandler"/>
</beans>

可以看到增加了aop的标签,然后我们这次模拟我们在dao层进行操作时,在开始操作前计时,在完成计时的小功能。

新建一个dao层的UserDao

	public class UserDao {
	public void insertUser()
	{
		System.out.println("插入一个学生");
	}
	
	public void deleteUser()
	{
		System.out.println("删除一个学生");
	}
}

好了再来设置一下我要做的aop的advice.

新建一个TimeHandler代码如下:

	public class TimeHandler {
	public void beforeprintTime()
    {
        System.out.println("开始时间 = " + System.currentTimeMillis());
    }
	public void afterprintTime()
    {
        System.out.println("结束时间 = " + System.currentTimeMillis());
    }
}

最后回到配置文件中,添加如下代码:

	<aop:config>
       		<aop:aspect id="time" ref="TimeHandler">
       			<aop:pointcut id="addAllMethod" expression="execution(* com.hg.dao.UserDao.*(..))" />
                <aop:before method="beforeprintTime" pointcut-ref="addAllMethod" />
                <aop:after method="afterprintTime" pointcut-ref="addAllMethod" />
       		</aop:aspect>
   </aop:config>

添加我们的测试:

	public class MyTest {
	static ApplicationContext context;
	static {
		context=new ClassPathXmlApplicationContext("AopTest.xml");
	}
	 @Test
	 public void test1() 
	 {
		 UserDao dao=(UserDao)context.getBean("UserDao");
		 dao.deleteUser();
		 dao.insertUser();
	 }
}

测试结果如下:
在这里插入图片描述
到此我也不想在赘述spring的注解实现aop的方法了,如果用注解的话只会更简单。感兴趣的同学可以去我的github下载查看。使用xml配置文件+使用注解实现AOP.

总结

通过这次对spring的复习,总结。让我收获了spring的使用有了更多的心得,一开始就使用注解开发的话,容易在注解之中忽略到spring真正的工作原理。通过xml的配置让我了解了其中的一些原理(并不是全部。。。)。还有一个最大的收获是我想我该重新学习一下关于面向对象的思想,感觉自己的基础还是很不好。所以在理解框架时什么的,并没有一种茅塞顿开的感觉,也没有惊叹到框架设计的精妙之处,反而总感觉有一种多此一举的感觉。童年过这次让我整理spring的知识,同时也了解到了面向对象的精妙之处(下去好好学习学习)。
如果在文中如果有什么错误希望大家也可以及时帮我指出来,讲的不好的地方还望大家多多海涵,谢谢。
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值