11 02Spring之依赖注入

Spring只有两大核心技术:Ioc和DI(控制反转和依赖注入)、AOP(面向切面编程)。

所谓的依赖注入指的就是利用配置文件的关系来决定类之间的引用关系以及数据的设置操作。

1 构造方法注入

默认情况下如果在applicationContext.xml文件之中配置的程序都可以自动的通过Spring容器加载时自动的进行对象的实例化操作。但是自动进行初始化的时候调用的是类中的无参构造方法,而且我们通过反射机制应该知道,如果类中提供有无参构造方法一定要比提供有有参构造的实例化更加的容易。

但是在Spring里面简化了反射的处理机制,也就是说利用Spring中的动态的特性可以直接明确调用构造方法传递参数。
范例:定义一个类

package org.lks.vo;

public class Department {

	private Integer did;
	private String dname;
	
	public Department(Integer did, String dname){
		this.did = did;
		this.dname = dname;
	}
}

此时类中没有提供有无参构造方法,所以配置在applicationContext.xml文件中的<bean>不可能正常使用。所以此时必须明确的调用类之中的有参构造方法。
范例:applicationContext.xml文件

<bean id="department" class="org.lks.vo.Department">
	<constructor-arg value="1001"/>
	<constructor-arg value="Software"/>
</bean>

但是在Spring配置里面它所支持的好处还远远不止这一点。如果用户有需要也可以根据参数的索引来进行设置。
范例:修改索引操作

<bean id="department" class="org.lks.vo.Department">
	<constructor-arg index="1" value="Software"/>
	<constructor-arg index="0" value="1001"/>
</bean>

虽然Spring在构造方法上考虑的还是挺多的,但是从本质上来讲以上的操作形式并不推荐。

但是很多人会对使用索引编号的方式产生反感。认为应该使用参数名称描述最好。
范例:修改Department.java类,设置参数名称

@ConstructorProperties(value={"paramDid", "paramDname"})
public Department(Integer did, String dname){
	this.did = did;
	this.dname = dname;
}

也就是说在设置的时候可以使用paramDid来代替索引0,使用paramDname来代替索引1
范例:利用参数名称进行设置

<bean id="department" class="org.lks.vo.Department">
	<constructor-arg name="paramDname" value="Software"/>
	<constructor-arg name="paramDid" value="1001"/>
</bean>

如果真使用构造方法,好像还是按照参数的类型和顺序编写会比较方便。

2 利用setter注入

在正常编写简单Java类的过程之中一定要提供有无参构造,甚至许多自己定义的工具类也都会提供有无参构造,所以使用构造方法的注入操作并不是我们所喜欢的方式。

实际上任何的类都会通过setter设置数据,这一点在简单Java类上表现特别明显。
范例:定义Department.java类

package org.lks.vo;

public class Department {

	private Integer did;
	private String dname;

	public Department() {
	}

	public Integer getDid() {
		return did;
	}

	public void setDid(Integer did) {
		this.did = did;
	}

	public String getDname() {
		return dname;
	}

	public void setDname(String dname) {
		this.dname = dname;
	}

	@Override
	public String toString() {
		return "Department [did=" + did + ", dname=" + dname + "]";
	}
}

在传统的操作之中,一定是首先实例化Department类对象,而后调用setter设置内容,但是现在可以利用Spring动态设置内容。
范例:观察Spring的注入操作

<bean id="department" class="org.lks.vo.Department">
	<property name="did" value="1001"/>
	<property name="dname" value="SOFTWARE"/>
</bean>

利用这种setter的设置才是在实际开发之中使用最多的操作情况。

但是整个利用setter注入其强大之处,还在于可以引用其它类型的Bean对象。
范例:定义Employee.java类

package org.lks.vo;

public class Employee {

	private Integer eid;
	private String ename;
	private Department department;

下面的重点内容在于配置文件来决定彼此的操作关系。
范例:编写applicationContext.xml文件

<bean id="department" class="org.lks.vo.Department">
	<property name="did" value="1001"/>
	<property name="dname" value="SOFTWARE"/>
</bean>
<bean id="employee" class="org.lks.vo.Employee">
	<property name="eid" value="3161"/>
	<property name="ename" value="lks"/>
	<property name="edepartment" ref="department"/>
</bean>

如果是具体的内容则使用value属性,如果要引用其它Bean对象,那么就使用ref属性。

在最早的时候所进行的全部结构的设置过程之中都是通过程序硬编码的形式实现的,但是现在可以利用配置文件的方式采用编码的形式完成,所有的操作类不再需要由用户负责实例化了,而全部由容器完成。

疑问?如果现在某个内容要是null怎么办呢?
现在有两种方式设置null;
方式一:不设置dname属性,内容就是null。

<bean id="department" class="org.lks.vo.Department">
	<property name="did" value="1001"/>
</bean>

方式二:明确的设置null

<bean id="department" class="org.lks.vo.Department">
	<property name="did" value="1001"/>
	<property name="dname"><null/></property>
</bean>

方式二只是明确的告诉用户现在要设置的内容是null值。

但是以上所进行的属性设置只是设置了常用的类型,例如:Integer、String、其它引用,可是在实际的开发之中还有可能设置布尔值。但是对于布尔值的设置,在Spring里面支持如下几种:true\false1\0on\offyes\no
范例:设置布尔型

public class Department {

	private Integer did;
	private String dname;
	private boolean close;

	public boolean isClose() {
		return close;
	}

	public void setClose(boolean close) {
		this.close = close;
	}
}
<bean id="department" class="org.lks.vo.Department">
	<property name="did" value="1001"/>
	<property name="dname" value="SOFTWARE"/>
	<property name="close" value="no"></property>
</bean>

如果在类中的属性是boolean型数据返回的时候一般都建议使用is开头,但是也可以使用getter命名。

3 注入集合数据

在之前都完成了常用的数据的注入过程,但是任何的开发之中都一定会存在有集合的操作,在Spring里面也支持集合数据的注入:数组、List、Set、Map、Properties。
范例:注入数组数据

package org.lks.vo;

public class Company {

	private String[] cmsg;
	private Integer[] cdata;
}

<bean id="company" class="org.lks.vo.Company">
	<property name="cdata">
		<array value-type="java.lang.Integer">
			<value>10</value>
			<value>20</value>
			<value>30</value>
		</array>
	</property>
	<property name="cmsg">
		<array>
			<value>hhy</value>
			<value>big</value>
			<value>fool!</value>
		</array>
	</property>
</bean>

在本类中可以接收两个数组的对象信息。现在可以发现,在使用数组数据的时候都设置了相应的数据类型。如果不写操作的类型,那么Spring会自动判断给出的数据类型,帮助用户自动转型。

但是需要清楚的是,数组一般不会在开发之中出现,如果真出现数组,使用List集合来横向代替数组。
范例:使用List集合操作

package org.lks.vo;

import java.util.List;

public class Company {

	private List<String> cmsg;

	public void setCmsg(List<String> cmsg) {
		this.cmsg = cmsg;
	}

	public List<String> getCmsg() {
		return cmsg;
	}

	@Override
	public String toString() {
		return "Company [cmsg=" + cmsg + "]";
	}

}

<bean id="company" class="org.lks.vo.Company">
	<property name="cmsg">
		<array>
			<value>hhy</value>
			<value>big</value>
			<value>fool!</value>
		</array>
	</property>
</bean>
<bean id="company" class="org.lks.vo.Company">
	<property name="cmsg">
		<list value-type="java.lang.String">
			<value>hhy</value>
			<value>big</value>
			<value>fool!</value>
		</list>
	</property>
</bean>

也就是说可以得出这样一种结论:List集合 = 数组。
范例:观察Set集合注入

package org.lks.vo;

import java.util.Set;

public class Company {

	private Set<String> cmsg;

	public void setCmsg(Set<String> cmsg) {
		this.cmsg = cmsg;
	}

	public Set<String> getCmsg() {
		return cmsg;
	}

	@Override
	public String toString() {
		return "Company [cmsg=" + cmsg + "]";
	}

}

<bean id="company" class="org.lks.vo.Company">
	<property name="cmsg">
		<set value-type="java.lang.String">
			<value>hhy</value>
			<value>big</value>
			<value>big</value>
			<value>fool!</value>
		</set>
	</property>
</bean>

此时不再出现重复数据,因为Set集合是不允许重复的。
范例:注入Map集合

package org.lks.vo;

import java.util.Map;

public class Company {

	private Map<Integer,String> cmsg;

	public void setCmsg(Map<Integer,String> cmsg) {
		this.cmsg = cmsg;
	}

	public Map<Integer,String> getCmsg() {
		return cmsg;
	}

	@Override
	public String toString() {
		return "Company [cmsg=" + cmsg + "]";
	}

}

<bean id="company" class="org.lks.vo.Company">
	<property name="cmsg">
		<map key-type="java.lang.Integer" value-type="java.lang.String">
			<entry key="1001" value="hhy"/>
			<entry key="1002" value="big"/>
			<entry key="1003" value="fool"/>
		</map>
	</property>
</bean>

以上的操作实际上在开发配置文件的编写过程之中见到的不多,而真正见到最多的是Properties类型。
范例:注入Properties

package org.lks.vo;

import java.util.Properties;

public class Company {

	private Properties cmsg;

	public void setCmsg(Properties cmsg) {
		this.cmsg = cmsg;
	}

	public Properties getCmsg() {
		return cmsg;
	}

	@Override
	public String toString() {
		return "Company [cmsg=" + cmsg + "]";
	}

}

<bean id="company" class="org.lks.vo.Company">
	<property name="cmsg">
		<props>
			<prop key="1001">hhy</prop>
			<prop key="1002">big</prop>
			<prop key="1003">fool</prop>
		</props>
	</property>
</bean>

在一些框架的整合开发之中,此类属性的设置是最为常见的。

现在所给出的实际上只是定义的一些数值,二最神奇的是它可以定义文件内部的引用关系。
范例:观察如下的结构

package org.lks.vo;

import java.util.List;

public class Department {

	private Integer did;
	private String dname;
	private List<Employee> demployees;
	
	public void setDemployees(List<Employee> demployees) {
		this.demployees = demployees;
	}
	
	public List<Employee> getDemployees() {
		return demployees;
	}
}
package org.lks.vo;

public class Employee {

	private Integer eid;
	private String ename;
	private Department edepartment;
}

而后的关键就在于通过配置文件清楚的描述出以上的结构关系。

<bean id="department" class="org.lks.vo.Department">
	<property name="did" value="1001"/>
	<property name="dname" value="SOFTWARE"/>
	<property name="demployees">
		<list value-type="org.lks.vo.Employee">
			<ref bean="employeeA"/>
			<ref bean="employeeB"/>
			<ref bean="employeeC"/>
		</list>
	</property>
</bean>
<bean id="employeeA" class="org.lks.vo.Employee">
	<property name="eid" value="3161"/>
	<property name="ename" value="lks"/>
	<property name="edepartment" ref="department"/>
</bean>
<bean id="employeeB" class="org.lks.vo.Employee">
	<property name="eid" value="3162"/>
	<property name="ename"><null/></property>
	<property name="edepartment" ref="department"/>
</bean>
<bean id="employeeC" class="org.lks.vo.Employee">
	<property name="eid" value="3163"/>
	<property name="ename" value="hhy"/>
	<property name="edepartment" ref="department"/>
</bean>

所有可以在程序中配置的结构关系,现在完全可以通过配置文件横向替代了。

4 内部Bean配置

正常情况下肯定是在程序里面分别定义<bean>节点,而后利用<ref>进行关系的引用,但是如果有一些特殊需要也可以设置内部的bean配置。
范例:配置内部Bean

<bean id="employee" class="org.lks.vo.Employee">
	<property name="eid" value="3161"/>
	<property name="ename" value="lks"/>
	<property name="edepartment">
		<bean id="myDepartment" class="org.lks.vo.Department">
			<property name="did" value="1002"/>
			<property name="dname" value="Market"/>
		</bean>
	</property>
</bean>

如果真的从使用的角度来看,大部分情况下都会将其定义为公共Bean,供各种配置使用。

5 使用p命令空间

在之前使用依赖注入的操作模式是最为常见的操作模式,也是在实际开发之中使用最多的形式,但是从Spring 2.x版本之后开始增加了另外一种操作形式,称为p命令空间。

<beans
	xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
	http://www.springframework.org/schema/beans/spring-beans-4.1.xsd">

利用MyEclipse自动生成的项目里面支持了p命名空间,那么随后就可以使用p:属性或者p:属性-ref来进行操作的定义。

<bean id="employee" class="org.lks.vo.Employee" p:eid="1001" p:ename="lks" p:edepartment-ref="myDepartment"/>
<bean id="myDepartment" class="org.lks.vo.Department" p:did="1002" p:dname="mraket"/>

虽然此类的操作长度要简化,但是大部分情况下很少这样去处理,基本上还是愿意沿用原始的方式去处理。

6 自动装配

在之前使用的过程之中,对于要引用的属性都必须明确的写上名称。
范例:原始配置

<bean id="employee" class="org.lks.vo.Employee">
	<property name="eid" value="1001"/>
	<property name="ename" value="lks"/>
	<property name="edepartment" ref="department"/>
</bean>
<bean id="department" class="org.lks.vo.Department">
	<property name="did" value="2001"/>
	<property name="dname" value="market"/>
</bean>

当要在Employee对象里面去引用Department对象的时候,需要明确的使用ref属性去找到指定的名称,但是在这种操作中也可以使用类型的自动装配。
范例:实现自动转配

<bean id="employee" class="org.lks.vo.Employee" autowire="byType">
	<property name="eid" value="1001"/>
	<property name="ename" value="lks"/>
</bean>
<bean id="department" class="org.lks.vo.Department">
	<property name="did" value="2001"/>
	<property name="dname" value="market"/>
</bean>

此时的装配过程之中并没有明确的写上要引用的是Department的配置,但是却可以通过autowire自动的根据类型查找到所需要的bean对象并且自动引用。

但是这样的操作也会有一些问题,因为是按照类型的关系引用,如果现在出现了两个同类型的操作呢?
范例:观察问题

<bean id="employee" class="org.lks.vo.Employee" autowire="byType">
	<property name="eid" value="1001"/>
	<property name="ename" value="lks"/>
</bean>
<bean id="departmentA" class="org.lks.vo.Department">
	<property name="did" value="2001"/>
	<property name="dname" value="market"/>
</bean>
<bean id="departmentB" class="org.lks.vo.Department">
	<property name="did" value="2002"/>
	<property name="dname" value="software"/>
</bean>

以上的代码根本就不可能实现自动的匹配操作,所以需要有人推荐或者是有人退出。
范例:设置推荐选择

<bean id="departmentA" class="org.lks.vo.Department" primary="true">
	<property name="did" value="2001"/>
	<property name="dname" value="market"/>
</bean>

范例:自动退出

<bean id="departmentA" class="org.lks.vo.Department" autowire-candidate="false">
	<property name="did" value="2001"/>
	<property name="dname" value="market"/>
</bean>

意味着如果进行类型匹配的过程之中,不会再去考虑此Bean的情况。

以上的操作都是利用了setter进行的自动装配,但是也可以利用构造方法进行自动装配。
范例:观察构造方法的配置

package org.lks.vo;

public class Employee {

	private Integer eid;
	private String ename;
	private Department edepartment;

	public Employee(Department edepartment) {
		this.edepartment = edepartment;
	}
}
<bean id="employee" class="org.lks.vo.Employee" autowire="constructor">
	<property name="eid" value="1001"/>
	<property name="ename" value="lks"/>
</bean>

总结:这种自动的配置模式比较麻烦,如果可能尽量还是明确的去引用一个名称会更加合理。

7 Bean的其它配置

首先需要明确一个问题:默认情况下只要在applicationContext.xml文件里面配置的<bean>都会在Spring启动的时候自动进行构造方法初始化,但是用户也可以实现自己的配置,让其在第一次使用的时候再进行初始化,这种操作称为延迟加载。
范例:延迟加载

package org.lks.vo;

public class Employee {

	private Integer eid;
	private String ename;
	private Department edepartment;

	public Employee() {
		System.out.println("############");
	}
}
<bean id="employee" class="org.lks.vo.Employee" lazy-init="true">
	<property name="eid" value="1001"/>
	<property name="ename" value="lks"/>
</bean>

此时就表示Employee这个对象进行了延迟加载,当第一次使用这个bean的时候再进行加载。

那么除了这一特征之外,在Spring处理过程之中还可以进行自定义的初始化和销毁方法操作。例如,现在有一个类,可以在类实例化对象的时候自动执行一个方法进行特定的初始化调用,或者在这个类对象不再需要的时候自动执行一个销毁方法进行资源的释放。
范例:观察初始化和销毁

package org.lks.vo;

public class Employee {

	private Integer eid;
	private String ename;
	private Department edepartment;

	public void init(){
		System.out.println("init ...");
	}
	
	public void destroy(){
		System.out.println("destory ...");
	}
}

实际上以上设计方法只是在Spring中才可以使用的,而在实际的Java运行里面初始化会依靠构造方法,销毁会依靠finalize()方法。
范例:配置applicationContext.xml文件——需要明确的指定初始化与销毁的方法。

<bean id="employee" class="org.lks.vo.Employee" init-method="init" destroy-method="destroy">
	<property name="eid" value="1001"/>
	<property name="ename" value="lks"/>
</bean>

默认情况下初始化的操作一定会默认的自动出现,但是销毁的操作必须明确处理。
范例:调用过程

package org.lks.test;

import org.lks.vo.Employee;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestEmployee {

	public static void main(String[] args) {
		ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
		Employee employee = ctx.getBean("employee", Employee.class);
		System.out.println(employee);
		ctx.destroy();//触发销毁
	}
}

这样的销毁操作在一段时间之内成为了Spring整合开发的关键,用于进行数据库连接的关闭。

8 利用Annotation配置注入关系

为了更好的解释此类操作存在的意义,下面通过一种习惯性的开发进行问题的描述,例如,现在有一个IAdminService服务层,这个服务层要调用IAdminDAO和IRoleDAO两个数据层操作,于是现在的编写如下。
范例:定义数据层操作

package org.lks.dao;

public interface IAdminDAO {

	public boolean findLogin();
}
package org.lks.dao.impl;

import org.lks.dao.IAdminDAO;

public class AdminDAOImple implements IAdminDAO {

	@Override
	public boolean findLogin() {
		System.out.println("public boolean findLogin()");
		return true;
	}

}

package org.lks.dao;

public interface IRoleDAO {

	public void findAll();
}

package org.lks.dao.impl;

import org.lks.dao.IRoleDAO;

public class RoleDAOImpl implements IRoleDAO {

	@Override
	public void findAll() {
		System.out.println("public void findAll()");
	}

}

最早的时候这两个数据层的类一定要编写工厂类,但是现在不再编写工厂类,下面直接在applicationContext.xml文件里面定义。

<bean id="roleDAOImpl" class="org.lks.dao.impl.RoleDAOImpl" />
<bean id="adminDAOImpl" class="org.lks.dao.impl.AdminDAOImpl" />

随后所有的数据层一定要交给数据层进行调用,那么下面定义业务层的操作:

package org.lks.service;

public interface IAdminService {

	public void login();
}

package org.lks.service.impl;

import org.lks.dao.IAdminDAO;
import org.lks.dao.IRoleDAO;
import org.lks.service.IAdminService;

public class AdminServiceImpl implements IAdminService{

	private IAdminDAO adminDAOImpl;
	private IRoleDAO roleDAOImpl;
	
	public void setAdminDAOImpl(IAdminDAO adminDAOImpl) {
		this.adminDAOImpl = adminDAOImpl;
	}
	
	public void setRoleDAOImpl(IRoleDAO roleDAOImpl) {
		this.roleDAOImpl = roleDAOImpl;
	}
	
	@Override
	public void login() {
		this.adminDAOImpl.findLogin();
		this.roleDAOImpl.findAll();
	}

}

类编写完成之后下面需要定义applicationContext.xml文件,在这里面配置彼此的关系。

<bean id="adminServiceImpl" class="org.lks.service.impl.AdminServiceImpl">
	<property name="adminDAOImpl" ref="adminDAOImpl"/>
	<property name="roleDAOImpl" ref="roleDAOImpl"/>
</bean>

代码写到此处可以说最原始的操作实现完成了。所有的关系都通过applicationContext.xml文件完成了。

此时最大的直观感受就是避免了工厂类的编写操作,的确是节约了代码,但是反过来想,问题又会出现了,如果一个项目之中存在有几百个DAO或Service,这样的写法太累。

在Spring之中可以利用Annotation完全简化以上的操作。
范例:增加新的命名空间

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
	http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
		http://www.springframework.org/schema/context 
		http://www.springframework.org/schema/context/spring-context-4.1.xsd">

范例:设置Annotation的支持包

<context:annotation-config/>
<context:component-scan base-package="org.lks"/>

现在表示在org.lks下的所有程序类都支持Annotation的配置,而在Spring里面针对于组件的Annotation的配置只提供有三个注解定义(这三个注解定义的作用都一样,只是单词不同):
(1)@Component:主要用于定义组件,一般都在DAO上使用;
(2)@Service:主要用于定义组件,一般都在Service上使用;
(3)@Repository:主要用于定义组件,一般都在Action上使用;
范例:修改XxxDAOImpl.java类

package org.lks.dao.impl;

import org.lks.dao.IAdminDAO;
import org.springframework.stereotype.Component;

@Component
public class AdminDAOImpl implements IAdminDAO {

	@Override
	public boolean findLogin() {
		System.out.println("public boolean findLogin()");
		return true;
	}

}
package org.lks.dao.impl;

import org.lks.dao.IRoleDAO;
import org.springframework.stereotype.Component;

@Component
public class RoleDAOImpl implements IRoleDAO {

	@Override
	public void findAll() {
		System.out.println("public void findAll()");
	}

}

现在如果使用了注解定义组件。那么名称默认情况下就是类名称的结构形式:
(1)AdminDAOImpl,则访问此组件的名称就是adminDAOImpl;
(2)RoleDAOImpl,则访问此组件的名称就是roleDAOImpl。
范例:在Service层上使用注解

package org.lks.service.impl;

import javax.annotation.Resource;

import org.lks.dao.IAdminDAO;
import org.lks.dao.IRoleDAO;
import org.lks.service.IAdminService;
import org.springframework.stereotype.Service;

@Service
public class AdminServiceImpl implements IAdminService{

	@Resource
	private IAdminDAO adminDAOImpl;
	@Resource
	private IRoleDAO roleDAOImpl;
	
	@Override
	public void login() {
		this.adminDAOImpl.findLogin();
		this.roleDAOImpl.findAll();
	}
}


@Resource(name="roleDAOImpl")
public void setRoleDAOImpl(IRoleDAO roleDAOImpl) {
	this.roleDAOImpl = roleDAOImpl;
}

此时的AdminServiceImpl类的引用是adminServiceImpl。
范例:测试操作

package org.lks.test;

import org.lks.service.impl.AdminServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestAdminServiceImpl {

	public static void main(String[] args) {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
		AdminServiceImpl service = ctx.getBean("adminServiceImpl", AdminServiceImpl.class);
		service.login();
	}

}

现在发现利用Annotation实现的注入操作,整个操作的流程都是非常的简化的,以后的开发都采用此类模式进行。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值