Spring核心特性 -- IOC(控制反转)

一、程序的耦合与解耦

1、耦合性(Coupling)

        在了解Spring IOC之前,我们需要先明白一个代码程序里的特点 -- 耦合性,也称为耦合度,是对模块间关联程度的度量。

        耦合度的强弱取决于模块间接口的复杂性、调用模块的方式,以及通过界面传输数据的多少。

        模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差(降低耦合度,可以提高独立性)。耦合性存在于各个区域,而非软件设计中独有。

        总结下:在软件工程中,耦合简单来说,即对象之间的依赖性。对象之间依赖的程度越高,耦合度就越高。对象之间的耦合越高,维护成本就越高。因此,对象的设计应该是类和组件之间松耦合的。

        那么,降低程序之间的依赖度,降低程序间的耦合度过程,就称之为解耦。

例如:有一个classA和classB,在A中如果需要使用B的方法,那么需要进行

Class A{

B b = new ClassB();

b.method();

}

        这段代码中,A这个类对象成为依赖于B类对象,假如B对象被更改或者替换,那么就会导致A的使用出现问题,要么需要维护代码,要么需要进行更换,这个称之为依赖程度很高 - 耦合性。

        例如:早期的JDBC操作中,在注册数据库驱动时,为什么采用的是Class.forName方式,而不是采用DriverManager.registerDriver的方式?

        

        早期的DriverManger.registerDriver(new Driver());会导致之后注册两次,并且,使用这种方式,JDBC程序就会依赖于数据库的驱动类(MySQL的Dirver类),如果后期程序因数据量和性能原因升级到Oracle数据库,就需要修改程序源代码 – 重新导入新的驱动类,这会增加很多不必要的麻烦!

        而使用Class.forName方式注册驱动,这样的好处是JDBC程序不再依赖具体的驱动类,即使删除(或不导入)MySQL驱动包,程序依然可以编译(当然运行失败,因为运行时必须依赖驱动)。

        此时类中仅仅是将mysql驱动类的全限定类名写死在程序中(只是一个字符串),可以将这个字符串提取到配置文件中,后期可以通过修改配置文件(而不用修改程序代码)轻松的替换数据库产品。

2、耦合度高的示例:

下面,我们通过一个简单的模拟添加员工的程序,去制造一个耦合度很高的项目:

新建一个web maven项目,右键该工程 – javaEE Tools – Generate Deployment Descriptor Stud:自动检查生成部署文件,pom.xml中引入junit、mybatis、log等相关坐标。

·pom配置:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.tedu</groupId>
  <artifactId>Spring01</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>
  
  	<!-- 导入Junit、mysql、mybatis相关包 -->
	<dependencies>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>8.0.21</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>3.4.6</version>
		</dependency>
		<!-- 加入log4j支持 -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.6.4</version>
		</dependency>


	</dependencies>
</project>

·新建dao层包:

·EmpDaoInter接口:

package com.tedu.dao;
//持久层的接口实现类
public class EmpDaoImpl implements EmpDaoInter{

	@Override
	public void addEmp() {
		// TODO Auto-generated method stub
		System.out.println("dao层的addEmp()方法执行了,成功保存了一条员工信息!");
	}
	
}

·EmpDaoImpl实现类:

package com.tedu.dao;
/**
 * 员工模块的Dao(持久层)接口
 * @author Administrator
 *
 */
public interface EmpDaoInter {
	//添加员工信息
	public void addEmp();
	
}

·新建service业务逻辑层包:

·新建EmpServiceInter接口:

package com.tedu.service;
/**
 * 员工模块业务逻辑层接口
 * @author Administrator
 *
 */
public interface EmpServiceInter {
	//添加员工信息
	public void addEmp();
	
}

·新建EmpServiceImpl实现类:

package com.tedu.service;

import com.tedu.dao.EmpDaoImpl;
import com.tedu.dao.EmpDaoInter;

//业务逻辑层的实现类
public class EmpServiceImpl implements EmpServiceInter{
	//获取Dao接口的子类实例,通过new对象的方式提升程序之间的耦合性
	private EmpDaoInter dao = new EmpDaoImpl();
	
	@Override
	public void addEmp() {
		// TODO Auto-generated method stub
		System.out.println("调用dao层的方法添加员工信息!");
		dao.addEmp();
	}
	
}

·新建controller控制层包:

·新建EmpController控制层(表现层)类:

package com.tedu.controller;
/**
 * 模拟表现层
 * controller --> service --> dao
 * @author Administrator
 *
 */

import org.junit.jupiter.api.Test;

import com.tedu.service.EmpServiceImpl;
import com.tedu.service.EmpServiceInter;

public class EmpController {
	//通过new对象的形式,使得程序的耦合性提高
	private EmpServiceInter service = new EmpServiceImpl();
	
	@Test
	public void testAddEmp() {
		System.out.println("调用service层的方式添加员工信息!");
		service.addEmp();
	}
}

3、工厂模式解耦

        在上述的程序中,我们可以看到,除了dao层之外的其他业务层,在调用dao的实现方法时,都是通过new对象的方式直接取得对象方法,那么这种方式,非常依赖dao层中的类,假如在dao层中的类被删除掉,那么其他的业务层将会需要进行大量的代码修改,需要花费大量的时间去维护,这就是耦合!

        所以,这个时候,为了方便我们的程序运行,使得程序更加独立且高效,维护方便,下面演示一次工厂模式的解耦方式。

在演示前,我们需要知道几个概念:

Bean:

Bean在计算机语言中,意思是可重用的组件;

而在java中,javaBean:使用java语言/程序编写的可重用组件,例如 – service/dao/pojo

业务bean:指用于处理业务逻辑的bean – service/dao;

实体bean:指用于封装数据的bean,例如之前我们用于封装员工信息的Emp类,就是实体bean。

而这些bean,最终理念都是为了增加效率,通过技术手段(例如配置文件调用)实现了重复的使用,减少了一些重复的代码以及解除了程序的耦合度。

工厂解耦流程:
  1. 我们创建一个配置文件,把所有的需要实例化的bean,以key=value的形式,存放在配置文件中。命名可以为config.properties;
  2. 建立工厂类,通过该类建立工厂对象,读取配置文件。
配置文件:

注意:配置文件中无法使用中文,所有注释都使用英文。

#key = value

#service implements

EmpService=com.tedu.service.EmpServiceImpl

#dao implements

EmpDao=com.tedu.dao.EmpDaoImpl

其他的代码层,我们沿用一下spring01项目中的代码:

Dao层和service层都没有变化, 但是我们修改controller层中取得实例对象的代码。

修改前,我们先去建立BeanFactory工厂类:

新建BeanFactory类:
package com.tedu.factory;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
 * 工厂类
 * @author Administrator
 *Bean:计算机语言中,bean的意思是可重用的组件
 *JavaBean:使用java语言	/程序变编写的可重用组件-service/dao/pojo
 *	业务bean:用于处理业务逻辑 - service/dao
 *	实体bean:用于封装数据,例如:为了封装员工信息而提供的emp类,就是实体bean
 *---------------------------------------------------------------------
 *1、需要提供配置文件,在配置文件中配置service层和dao层的实现类
 *	配置内容为:key(唯一标识) = value(实现类的全限定类名)
 *			EmpServiceInter=com.tedu.service.EmpServiceImpl
 *			EmpDaoInter=com.tedu.dao.EmpDaoImpl
 *
 *2、通过工厂读取配置文件中配置的全限定类名,利用反射创建类的实例(对象)
 */


public class BeanFactory {
	//声明一个properties对象,在静态代码块中对其进行初始化
	private static Properties prop = null;
	//为prop进行初始化
	static {
		try {
		prop = new Properties();
		/**
		 * 获取指向config.properties文件的流对象
		 * 1)如果需要获取到一个配置文件的对象,我们可以先在当前类中,获得当前类的加载器,
		 * 	类加载器,可以加载类文件,也可以加载配置文件。
		 * 	类加载器的取得方法:
		 * 	类.class.getClassLoader(),返回的是一个加载器对象
		 */
		ClassLoader classLoader = BeanFactory.class.getClassLoader();
		
		/**
		 * 2)通过类加载器去到类目录下加载我们需要使用的config配置文件,方法:
		 * 	加载器.getResourceAsStream("配置文件config.properties"),返回一个流对象InputStrem
		 */
		InputStream in = classLoader.getResourceAsStream("config.properties");
		
		//然后,我们可以通过开始的Properties配置文件对象,加载传入取得到的配置文件流对象
		
			prop.load(in);
		//prop对象加载流对象后,就可以执行getProperty方法去取得这个key值对应的bean对象,我们打印输出看看
			//System.out.println(prop.getProperty("EmpService"));
			//System.out.println(prop.getProperty("EmpDao"));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			System.out.println("初始化Properties对象失败!");
		}

	}
	
	/*
	 * 上面的测试如果没有问题,下面我们就可以通过一个方法,去取得工厂对象获取到的配置文件中配置了的实例bean,
	 * 通过类名.方法就可以直接调用这个方法,在其他的业务逻辑处理中,直接取得想要的接口对象实例,
	 * 那么,之后如果bean类有发生更改,只需要直接去修改配置文件的value值,不需要进行代码的大修。
	 * 对象实例,返回的是一个Object对象
	 */
	/**
	 * 这个方法,是根据config.porperties文件中的key(接口名)获取对应的子类实例
	 * @param interName
	 * @return	子类实例
	 * 	EmpServiceInter=com.tedu.service.EmpServiceImpl
	 *	EmpDaoInter=com.tedu.dao.EmpDaoImpl
	 */
	
	public static Object getBean(String interName) {
		Object obj = null;
		try {
			//获取当前接口对应的实现类的全限定类名
			String className = prop.getProperty(interName);
			/**
			 * 这里,通过Class.forName(传入类名)可以获得字节码对象,
			 * 然后再通过newInstance()方法可以创建实例,
			 * 这个是反射的应用。最终取得了Obj这个实例对象
			 */
			obj = Class.forName(className).newInstance();
			
		}catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}

		return obj;
	}
	
}

修改controller业务控制层:
package com.tedu.controller;
/**
 * 模拟表现层
 * controller --> service --> dao
 * @author Administrator
 *
 */

import org.junit.jupiter.api.Test;

import com.tedu.factory.BeanFactory;
import com.tedu.service.EmpServiceImpl;
import com.tedu.service.EmpServiceInter;

public class EmpController {
	//通过new对象的形式,使得程序的耦合性提高
	//private EmpServiceInter service = new EmpServiceImpl();
	
	//通过工厂bean取得对象实例
	private EmpServiceInter service = (EmpServiceInter) BeanFactory.getBean("EmpServiceInter");
	
	@Test
	public void testAddEmp() {
		System.out.println("调用service层的方式添加员工信息!");
		service.addEmp();
	}
}

测试结果:

可以看到,通过工厂bean对象取得的实例,执行方法成功。

4、总结:

在第2、点中我们通过普通的业务层次,去运行程序,会发现,假如直接通过new实例这种方式执行,会导致耦合度很高,要修改某一个bean的时候,就会导致需要更改很大的代码片段,在实际开发中,修改代码,会导致项目需要重新发布!

所以,在第3、点中,我们通过新建工厂类,增加一个配置文件,配置文件中通过key=value的形式,让接口类=实现类,然后在工厂类中,建立Properties对象,加载配置文件的流对象,通过反射,得到这个key值下的对象名,然后通过forName字节码实例化这个对象,使得bean的实例化不需要通过new形式执行,在controller控制层中,直接调用getBean方法,传入你想要的接口key值,就可以获得对应的实现类对象。

这么做,如果你的实现类发生替换修改,我们可以直接去配置文件中修改value值(通常key值不太可能会修改,因为key值的命名,说白了就是根据业务开发情况去新建的接口的名称),而修改配置文件,不需要重新发布项目。这个对于实际应用是有很大帮助的,减少不必要的损失。

二、springIOC

1、概念:

IOC(Inverse Of Control)控制反转,即,把创建对象的权利交给框架

也就是指将对象的创建、对象的存储、对象的管理交给了spring容器。

(spring容器是spring中的一个核心模块,用于管理对象,底层可以理解为是一个map集合)

2、通过spring框架的IOC控制反转解除耦合入门案例:项目spring03

·引入spring框架的jar包

在pom文件中直接使用坐标引入:

<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>4.3.25.RELEASE</version>
		</dependency>

·创建spring核心配置文件:applicationContext.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"

xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"

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-4.0.xsd

http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd

http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd

http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">

 
 <!--  其他配置  -->
 <!-- 将EmpServiceInter接口的子类声明到spring容器中,让spring容器创建该类的实例;
 	id值是唯一的,在实例化的时候使用id进行指定。class值是你需要配置的类的全限定类名。
  -->
	<bean id="empService" class="com.tedu.service.EmpServiceImpl"></bean>
 
 <!-- 将EmpDaoInter接口的子类声明到容器中 -->
 	<bean id="empDao" class="com.tedu.dao.EmpDaoImpl"></bean>
 
</beans>

·创建测试类:
package com.tedu.controller;

import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.tedu.dao.EmpDaoInter;
import com.tedu.service.EmpServiceInter;

public class TestSpring {
	//获取spring容器对象
	private static ClassPathXmlApplicationContext ac;
	static {
		//对ac进行初始化,在classPathXmlApplicationContext中传入核心配置文件名称
		ac = new ClassPathXmlApplicationContext("applicationContext.xml");
	}
	
	//通过spring容器创建EmpServiceInter和EmpDaoInter接口的子类示例
	@Test
	public void testSpringIoc() {
		EmpServiceInter es = (EmpServiceInter) ac.getBean("empService");
		System.out.println("如果打印对象地址值说明获取成功:"+es);
		
		EmpDaoInter ed = (EmpDaoInter) ac.getBean("empDao");
		System.out.println("本次打印的是empDao的地址:"+ed);
	}
}
·运行结果:

3、spring DI(dependency injection依赖注入)

DI(Dependency Injection)依赖注入

依赖注入,即组件之间的依赖关系由容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中。

简单来说,所谓的依赖注入其实就是在创建对象的同时或之后,如何给对象的属性赋值。

如果对象由我们自己创建,这一切都变得很简单,例如;

User user = new User();

user.setName(“刘亦菲”);

user.setAge(18);

或者:

User user = new User(“刘亦菲”,18);

那么如果对象是由spring创建,那么spring是怎么给属性赋值的呢?

Spring主要提供了2种方式为属性赋值:

一是.set方法注入

二是.构造方法注入(spring一般会使用这种方式)

1).set方法注入:给属性赋值

Set方法注入:是通过对象setXxxx方法给对象的xxx属性赋值。这种赋值方式,既可以给属性赋值,也可以给对象赋值。下面我们先看看给属性赋值的用法。

A、给pojo类准备参数并且提供好构造方法和getter、setter方法
package com.tedu.pojo;
/**
 * 用于封装用户信息
 * @author Administrator
 *
 */
public class User {
	private String name;
	private int age;
	private UserInfo info;
	
	//提供无参构造方法
	public User() {	}
	
	//提供带参构造
	public User(String name, int age, UserInfo info) {
		super();
		this.name = name;
		this.age = age;
		this.info = info;
	}
	
	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 UserInfo getInfo() {
		return info;
	}
	public void setInfo(UserInfo info) {
		this.info = info;
	}
	
	//提供toString
	@Override
	public String toString() {
		return "User [name=" + name + ", age=" + age + ", info=" + info + ", getName()=" + getName() + ", getAge()="
				+ getAge() + ", getInfo()=" + getInfo() + ", getClass()=" + getClass() + ", hashCode()=" + hashCode()
				+ ", toString()=" + super.toString() + "]";
	}
	
	
	
}
B、额外提供一个UserInfo类:

这个类是包装类,在User类中使用到,并且有可能需要使用到这个类的实例,我们也需要去spring中配置好这个类的声明:

package com.tedu.pojo;

public class UserInfo {
	
}

	<!-- 将userInfo类声明到容器中 -->
 	<bean id="userInfo" class="com.tedu.pojo.UserInfo"></bean>

C、在User的spring配置中赋值:
 <!-- 将User类声明到spring容器中,由spring容器创建该类的对象 -->
 	<bean id="user" class="com.tedu.pojo.User" scope="prototype">
 		<!-- 通过set方法为user对象的属性赋值,name属性指代User类中的属性,value属性用于赋值 -->
 		<property name="name" value="刘亦菲"></property>
 		<property name="age" value="18"></property>
 	</bean>

如果需要给bean中的实例对象属性赋值,可以在核心配置文件中使用属性property给这个需要赋值的属性配置,比如上面我们需要给User类中的name赋值,那么在property这个标签的name属性中,注明name(这个是User的name属性),然后使用value赋值为刘亦菲,age赋值为18,看看下面的测试效果:

D、测试类:
	/**
	 * 测试sprig依赖注入:
	 * 1、set方法注入
	 */
	@Test
	public void testDi() {
		User user = (User)ac.getBean("user");
		System.out.println(user);
	}
	

另外,我们可以在setName方法中打印语句证明这种方式是使用了set方法:

2)set方法给对象赋值:使用ref属性

在上面,我们通过在property中设置属性对应的value值,相当于用set方法给属性赋值,那么如果要给对象赋值,使用的配置方式一般会使用ref属性:

A、核心配置文件:
<!-- 将User类声明到spring容器中,由spring容器创建该类的对象 -->
 	<bean id="user" class="com.tedu.pojo.User" scope="prototype">
 		<!-- 通过set方法为user对象的属性赋值,name属性指代User类中的属性,value属性用于赋值 -->
 		<property name="name" value="刘亦菲"></property>
 		<property name="age" value="18"></property>
 		<!-- 给类对象赋值,name的定义一样,但是,使用的不是value值,而是使用ref值,
 		如果该类对象已经声明到了容器中,那么ref值指向容器中的该id -->
 		<property name="info" ref="userInfo"></property>
 	</bean>
 	
B、配置完之后,继续使用刚才的方法测试:

可以看到此时的info这个对象不再是null,同样道理,我们在通过配置info的get、set方法以及核心配置文件中的value值,可以同样在这一个方法中给info这个对象赋值。

3)spring ID构造方法注入(constructor标签)

我们沿用上面的pojo类进行构造方法注入的方式演示:

构造方法注入,在bean中使用的是<constructor-arg>标签,name+value属性给对应的属性赋值,name+ref属性给对象赋值。

A、核心配置文件:
<!-- 将User类声明到spring容器中,由spring容器创建该类的对象 -->
 	<bean id="user" class="com.tedu.pojo.User" scope="prototype">
		<!-- 通过构造方法为user对象赋值,所赋值是通过形参构造方法传入的形参对应的name赋值 -->
 		<constructor-arg name="nameCon" value="不知火舞"/>
 		<constructor-arg name="ageCon" value="22" />
 		<constructor-arg name="infoCon" ref="userInfo"/>
 	</bean>

B、pojo类:
package com.tedu.pojo;
/**
 * 用于封装用户信息
 * @author Administrator
 *
 */
public class User {
	private String name;
	private int age;
	private UserInfo info;
	
	//提供无参构造方法
	public User() {	}
	
	//提供带参构造
	public User(String nameCon, int ageCon, UserInfo infoCon) {
		super();
		
		this.name = nameCon;
		this.age = ageCon;
		this.info = infoCon;
		System.out.println("带参构造方法被执行了!");
	}
	
	public String getName() {
		return name;
	}
	

	public void setName(String name) {
		System.out.println("setName方法执了");
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public UserInfo getInfo() {
		return info;
	}
	public void setInfo(UserInfo info) {
		this.info = info;
	}
	
	//提供toString
	@Override
	public String toString() {
		return "User [name=" + name + ", age=" + age + ", info=" + info + ", getName()=" + getName() + ", getAge()="
				+ getAge() + ", getInfo()=" + getInfo() + ", getClass()=" + getClass() + ", hashCode()=" + hashCode()
				+ ", toString()=" + super.toString() + "]";
	}
	
	
	
}

测试结果:

  • 19
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring-IOCSpring框架的核心部分之一,它是一种设计模式,全称为Inversion of Control(控制反转)。它通过将对象的创建、依赖关系的管理和对象的生命周期交给Spring容器来实现,从而降低了组件之间的耦合度,提高了代码的可重用性和可维护性。Spring-IOC的实现主要依靠Spring容器,Spring容器是Spring框架的核心,它负责创建、管理和装配Bean对象,其中Bean是Spring框架中最基本的组件。 Spring-IOC的实现主要有两种方式:BeanFactory和ApplicationContext。其中,BeanFactory是Spring-IOC的基本实现,而ApplicationContext是BeanFactory的子接口,提供了更多高级特性。ApplicationContext是Spring框架中最常用的IOC容器,它除了提供BeanFactory的所有功能外,还提供了更多的企业级特性,例如AOP、事务管理、国际化、事件传播等。 下面是一个简单的Spring-IOC的例子,假设我们有一个UserService接口和一个UserServiceImpl实现类,我们可以通过Spring-IOC容器来创建和管理UserServiceImpl对象: 1.定义UserService接口和UserServiceImpl实现类 ```java public interface UserService { void addUser(User user); } @Service public class UserServiceImpl implements UserService { @Override public void addUser(User user) { // 添加用户的具体实现 } } ``` 2.在Spring配置文件中配置UserService实例 ```xml <bean id="userService" class="com.example.service.UserServiceImpl"/> ``` 3.在代码中获取UserService实例并使用 ```java ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = context.getBean("userService", UserService.class); User user = new User(); userService.addUser(user); ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值