spring

目录

概述​

IOC、IOC容器、Bean、DI 

IOC(Inversion of Control)控制反转:

bean的实例化

bean生命周期

setter注入

构造器注入

依赖自动装配

集合注入

配置管理第三方bean

核心容器

注解开发

注解开发bean作用范围与生命周期管理 

注解开发依赖注入

注解管理第三方bean

Spring与Mybatis及Junit的整合开发

AOP

AOP(Aspect Oriented Programming)

AOP工作流程

AOP切入点表达式

AOP通知类型

 AOP通知获取切入点方法的参数、返回值、异常信息

AOP事务管理

事务配置

事务传播行为


概述

spring家族官网:https://spring.io,包括springFramework,spring boot,springcloud,SpringData,SpringSecurity等技术,可以开发web、微服务以及分布式系统,技术优势:

Spring 可以 简化开发 ,降低企业级开发的复杂性,使开发变得更简单快捷
  • IOC
  • AOP
  • 事务处理
Spring 可以 框架整合 ,高效整合其他技术,提高企业级应用开发与运行效率
  • MyBatis、MyBatis-plus、Struts、Struts2、Hibernate

Spring 发展史:

Spring Framework是Spring生态圈中最基础的项目,是其他项目的根基,Spring Framework的版本的变更史

目前最新的4版本架构图 

  • 核心层 Core Container:核心容器,这个模块是Spring最核心的模块,其他的都需要依赖该模块
  • AOP:面向切面编程,它依赖核心层容器,目的是在不改变原有代码的前提下对其进行功能增强
  • Aspects:AOP是思想,Aspects是对AOP思想的具体实现
  • Data Access:数据访问,Spring全家桶中有对数据访问的具体实现技术
  • Data Integration:数据集成,Spring支持整合其他的数据层解决方案,比如Mybatis
  • Transactions:事务,Spring中事务管理是Spring AOP的一个具体实现
  • Web层:这一层主要是SpringMVC框架
  • Test层:Spring主要整合了Junit来完成单元测试和集成测试

IOCIOC容器、BeanDI 

IOCInversion of Control)控制反转:

使用对象时,由主动 new 产生对象转换为由 外部 提供对象,此过程中对象创建控制权由程序转移到
外部,此思想称为控制反转;Spring 提供了一个容器,称为 IOC容器 ,用来充当 IOC 思想中的 " 外部";IOC容器负责对象的创建、初始化等一系列工作,其中包含了数据层和业务层的类对象,被创建或被管理的对象在 IOC 容器中统称为 Bean 业务层需要依赖数据层, 建立 对象与对象之间的依赖关系就叫依赖注入 (dependency Injection),这一堆概念的 最终目标就是 : 充分解耦,
使用对象时不仅可以直接从 IOC 容器中获取,并且获取到的 bean已经绑定了所有的依赖关系

具体实现步骤:

  1. 容器管什么?主要管理项目中所使用到的类对象,比如(Service和Dao)
  2.  如何将被管理的对象告知IOC容器?使用配置文件applicationContext.xml
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"/>

  3. 如何获取到IOC容器从而获取对象?Spring框架提供相应的接口
    public class App {
        public static void main(String[] args) {
        //获取IOC容器
        ApplicationContext ctx = new
        ClassPathXmlApplicationContext("applicationContext.xml");
        }
    }

  4. 如何从容器中获取bean?调用Spring框架提供对应接口中的方法
    BookService bookService = (BookService) ctx.getBean("bookService");
    bookService.save();

  5. 使用Spring导入哪些坐标?在pom.xml添加对应的依赖spring-context
  6.  如何实现依赖注入?
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
        <!--配置server与dao的关系-->
        <!--property标签表示配置当前bean的属性
            name属性表示配置哪一个具体的属性
            ref属性表示参照哪一个bean
        -->
        <property name="bookDao" ref="bookDao"/>
    </bean>

  7. 如何设置bean的作用范围?Spring创建的bean对象默认都是单例的,prototype为非单例

    <bean id="bookDao" name="dao" class="com.itheima.dao.impl.BookDaoImpl"
    scope="singleton"/>

  8. 哪些bean对象适合交给容器进行管理?表现层对象,业务层对象,数据层对象,工具对象
  9. 哪些 bean 对象不适合交给容器进行管理?封装实例的域对象,因为会引发线程安全问题,所以不适合

bean的实例化

容器是如何来创建对象的呢 ?
实例化 bean 的三种方式,构造方法(常用, 每一个类默认都会提供一个无参构造函 ), 静态工厂 实例工厂
//静态工厂实例化,class:工厂类的类全名 factory-mehod:具体工厂类中创建对象的方法名
<bean id="orderDao" class="com.itheima.factory.OrderDaoFactory" factory-method="getOrderDao"/>
实例工厂实例化
<bean id="userFactory" class="com.itheima.factory.UserDaoFactory"/>
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>

bean生命周期

  • 初始化容器:1.创建对象(内存分配) 2.执行构造方法 3.执行属性注入(set操作) 4.执行bean初始化方法
  • 使用bean 执行业务操作
  • 关闭/销毁容器:执行bean销毁方法

 方式一:添加初始化和销毁方法后配置生命周期,销毁之前运行destroy方法,需要将ApplicationContext更换成ClassPathXmlApplicationContext后调用close()方法或registerShutdownHook()方法关闭容器

<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" init-method="init"
destroy-method="destory"/>

方式二: 实现接口InitializingBeanDisposableBean并实现两个方法afterPropertiesSetdestroy

setter注入

BookServiceImpl声明userDao属性和set方法,applicationContext.xml配置文件中使用property标签注入引用数据类型和简单数据类型

<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
    <property name="databaseName" value="mysql"/>
    <property name="connectionNum" value="10"/>
</bean>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
    <property name="bookDao" ref="bookDao"/>
    <property name="userDao" ref="userDao"/>
</bean>

构造器注入

BookServiceImpl 类中将 bookDao setter 方法删除掉 , 并添加带有 bookDao参数的构造方法,在 applicationContext.xml中配置 构造方式注入
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
    <constructor-arg name="databaseName" value="mysql"/>
    <constructor-arg name="connectionNum" value="666"/>
</bean>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
    <constructor-arg name="bookDao" ref="bookDao"/>
    <constructor-arg name="userDao" ref="userDao"/>
</bean>
name属性对应的值为构造函数中方法形参的参数名,必须要保持一致。
ref属性指向的是spring的IOC容器中其他bean对象。

依赖自动装配

IoC 容器根据 bean 所依赖的资源在容器中自动查找并注入到 bean中的过程称为自动装配,自动装配优先级低于 setter 注入与构造器注入,同时出现时自动装配配置失效
  • 按类型(常用)
  • 按名称
  • 按构造方法
  • 不启用自动装配
    <bean class="com.itheima.dao.impl.BookDaoImpl"/>
    <!--autowire属性:开启自动装配,通常使用按类型装配-->
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"
autowire="byType"/>

一个类型在IOC中有多个对象,还想要注入成功,这个时候就需要按照名称注入,按照名称注入只找其指定名称对应的bean对象,配置方式为:

    <bean class="com.itheima.dao.impl.BookDaoImpl"/>
    <!--autowire属性:开启自动装配,通常使用按类型装配-->
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"
autowire="byName"/>

集合注入

 在标签内部写<array><list><set><map><props>标签

<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
        <!--数组注入-->
        <property name="array">
            <array>
                <value>100</value>
                <value>200</value>
                <value>300</value>
            </array>
        </property>
        <!--list集合注入-->
        <property name="list">
            <list>
                <value>itcast</value>
                <value>itheima</value>
                <value>boxuegu</value>
                <value>chuanzhihui</value>
            </list>
        </property>
        <!--set集合注入-->
        <property name="set">
            <set>
                <value>itcast</value>
                <value>itheima</value>
                <value>boxuegu</value>
                <value>boxuegu</value>
            </set>
        </property>
        <!--map集合注入-->
        <property name="map">
            <map>
                <entry key="country" value="china"/>
                <entry key="province" value="henan"/>
                <entry key="city" value="kaifeng"/>
            </map>
        </property>
        <!--Properties注入-->
        <property name="properties">
            <props>
                <prop key="country">china</prop>
                <prop key="province">henan</prop>
                <prop key="city">kaifeng</prop>
            </props>
        </property>
    </bean>

配置管理第三方bean

管理Druid连接池对象:先导入druid的依赖,然后在配置文件中将druid制作成一个bean,让IOC容器进行管理

<!--管理DruidDataSource对象-->
<bean class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/spring_db"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
</bean>

Spring框架如何从配置文件中读取属性值来配置信息并使用这些信息来完成属性注入:需在applicationContext.xml中开context命名空间,使用context命名空间下的标签来加载properties配置文件,最后使用${key}来读取properties配置文件中的内容并完成属性注入

//classpath:代表的是从根路径下开始查找,但是只能查询当前项目的根路径
//system-properties-mode:设置为NEVER,表示不加载系统属性
<context:property-placeholder location="classpath:*.properties"
system-properties-mode="NEVER"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

核心容器

ApplicationContext就是核心容器,有三种创建方式
//类路径下的XML配置文件
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
//文件系统下的XML配置文件
ApplicationContext ctx = new
FileSystemXmlApplicationContext("D:\\workspace\\spring\\spring_10_container\\s
rc\\main\\resources\\applicationContext.xml");
//使用BeanFactory来创建IOC容器,是延迟加载
Resource resources = new ClassPathResource("applicationContext.xml");
BeanFactory bf = new XmlBeanFactory(resources);
BookDao bookDao = bf.getBean(BookDao.class);

Bean的三种获取方式

BookDao bookDao = (BookDao) ctx.getBean("bookDao");
BookDao bookDao = ctx.getBean("bookDao",BookDao.class);
BookDao bookDao = ctx.getBean(BookDao.class);//确保IOC容器中该类型对应的bean对象只能有一个

bean相关总结

依赖注入总结

注解开发

直接在类上添加@Component注解,即表示该类为spring管理的bean,在配置文件上进行包扫描,它会扫描指定包及其子包中的所有类上的注解

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

 3.0版支持纯注解开发,创建一个配置类SpringConfig替换applicationContext.xml配置文件

@ComponentScan:扫描

@Configuration:表明是配置类

//@Configuration注解,将其标识为一个配置类
//@ComponentScan替换<context:component-scan base-package=""/>
@Configuration
@ComponentScan({"com.itheima.service","com.itheima.dao"})
@PropertySource("jdbc.properties")
public class SpringConfig {
}
//加载配置类初始化容器
ApplicationContext ctx = new
AnnotationConfigApplicationContext(SpringConfig.class);

注解开发bean作用范围与生命周期管理 

@scope注解: 设置bean的作用范围
@Repository
@Scope("prototype")
public class BookDaoImpl implements BookDao{....}
@PostConstruct:标识初始化方法
@PreDestroy:标识销毁方法

@Repository
//@Scope设置bean的作用范围
@Scope("singleton")
public class BookDaoImpl implements BookDao {

    public void save() {
        System.out.println("book dao save ...");
    }
    //@PostConstruct设置bean的初始化方法
    @PostConstruct
    public void init() {
        System.out.println("init ...");
    }
    //@PreDestroy设置bean的销毁方法
    @PreDestroy
    public void destroy() {
        System.out.println("destroy ...");
    }

}

注解开发依赖注入

@Autowired注解:按类型自动注入,自动装配基于反射设计创建对象并通过暴力反射为私有属性进行设值,因此无需提供 setter 方法
@Qualifier注解:指定注入哪个名称的 bean 对象
@Value注解:简单类型注入或读取配置文件中的内容( @Value ( "${name}" ))
@PropertySource注解:使用注解加载 properties 配置文件

注解管理第三方bean

@Bean:将方法的返回值制作为Spring管理的一个bean对象

@Import:手动引入需要加载的配置类

直接将三方类写进jdbcConfig配置文件,在Spring的配置类上引入一下

@Bean
    public DataSource dataSource(BookDao bookDao){
        System.out.println(bookDao);
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(userName);
        ds.setPassword(password);
        return ds;
    }
@Configuration
//@ComponentScan("com.itheima")
//@Import:导入配置信息
@Import({JdbcConfig.class})
public class SpringConfig {
}

SpringMybatisJunit的整合开发

导入整合需要的jar包mybatis-spring,创建Mybatis配置类并配置SqlSessionFactory

public class MybatisConfig {
    //定义bean,SqlSessionFactoryBean,用于产生SqlSessionFactory对象
    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
        //设置模型类的别名扫描        
        ssfb.setTypeAliasesPackage("com.itheima.domain");
        //设置数据源        
        ssfb.setDataSource(dataSource);
        return ssfb;
    }
    //定义bean,返回MapperScannerConfigurer对象
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage("com.itheima.dao");
        return msc;
    }
}

整合junit,需导入spring-test依赖,Junit运行后是基于Spring环境运行的,所以Spring提供了一个专用的类运行器,这个务必要设置,这个类运行器就在Spring的测试专用包中提供的,导入的坐标就是这个东西 SpringJUnit4ClassRunner

//设置类运行器
@RunWith(SpringJUnit4ClassRunner.class)
//设置Spring环境对应的配置类
@ContextConfiguration(classes = SpringConfig.class)
//@ContextConfiguration(locations={"classpath:applicationContext.xml"})//加载
配置文件
public class AccountServiceTest {
    //支持自动装配注入bean
    @Autowired
    private AccountService accountService;

    @Test
    public void testFindById(){
        System.out.println(accountService.findById(1));
    }
}

AOP

AOP(Aspect Oriented Programming)

AOP面向切面编程,是一种编程范式,一种编程思想,作用是 在不改原有代码的前提下对其进行功能增强, 这个也就是 Spring 的理念: 无入侵式/无侵入式
  • 连接点(JoinPoint):程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等
  • 切入点 (Pointcut):需要被增强的具体方法
  • 通知 (Advice): 在切入点处执行的操作,也就是共性功能
  • 通知类:定义通知的类
  • 切面(Aspect):描述通知与切入点的对应关系,进行关系的绑定
    具体步骤:1、添加依赖 aspectjweaver 2、定义接口和实现类 3、定义通知类和通知 4、定义切入点 5、制作切面 6、将通知类配给容器并标识其为切面类  7、在配置类开启注解格式AOP功能 @EnableAspectJAutoProxy
    //通知类必须配置成Spring管理的bean
    @Component
    //设置当前类为切面类类
    @Aspect
    public class MyAdvice {
        //设置切入点,要求配置在方法上方
        @Pointcut("execution(void com.itheima.dao.BookDao.update())")
        private void pt(){}
    
        //设置在切入点pt()的前面运行当前操作(前置通知)
         @Before("pt()")
        public void method(){
            System.out.println(System.currentTimeMillis());
        }
    }

    AOP工作流程

    Spring容器启动加载bean->读取所有切面配置中的切入点-> 初始化bean(判定 bean对应的类中的方法是否匹配到任意切入点,匹配成功会创建原始对象 代理 对象并运行代理对象的方法,在该方法中会对原始方法进行功能增强 ,匹配失败直接调用原始对象的方法)->获取 bean 执行方法
    本质:
    目标对象就是要增强的类 [ :BookServiceImpl ]对应的对象,也叫原始对象,不能说它不能运行,只能说它在运行的过程中对于要增强的内容是缺失的。
    SpringAOP 是在不改变原有设计 ( 代码)的前提下对其进行增强的,它的底层采用的是代理模式实现的,所以要对原始对象进行增强,就需要对原始对象创建代理对象,在代理对象中的方法把通知 [ :MyAdvice 中的 method 方法 ] 内容加进去,就实现了增强 , 这就是我们所说的代理 (Proxy)

    AOP切入点表达式

切入点:要进行增强的方法

切入点表达式标准格式:动作关键字 ( 访问修饰符 返回值 包名 . / 接口名 . 方法名 ( 参数 ) 异常
名)
execution(public User com.itheima.service.UserService.findById(int))
* :单个独立的任意符号
..:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写

execution(* com.itheima.*.*Service.find*(..))
将项目中所有业务层方法的以find开头的方法匹配

AOP通知类型

AOP 通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要将其加入到合
理的位置,共5种形式
(1) 前置通知,追加功能到方法执行前 , 类似于在代码 1 或者代码 2 添加内容
(2) 后置通知 , 追加功能到方法执行后 , 不管方法执行的过程中有没有抛出异常都会执行,类似于在代
5 添加内容
(3) 返回后通知 , 追加功能到方法执行后,只有方法正常执行结束后才进行 , 类似于在代码 3添加内容,如果方法执行抛出异常,返回后通知将不会被添加
(4) 抛出异常后通知 , 追加功能到方法抛出异常后,只有方法执行出异常才进行 , 类似于在代码 4添加内容,只有方法抛出异常后才会被添加
(5) 环绕通知 ,环绕通知功能比较强大,它可以追加功能到方法执行的前后,这也是比较常用的方式,它可以实现其他四种通知类型的功能, 环绕通知必须依赖形参 ProceedingJoinPoint 才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知,如果未使用 ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行, 对原始方法的调用可以不接收返回值,通知方法设置成 void 即可,如果接收返回值,最好设定为 Object类型,由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须要处理 Throwable 异常

 AOP通知获取切入点方法的参数、返回值、异常信息

JoinPoint:适用于前置、后置、返回后、抛出异常后通知获取参数

ProceedingJoinPoint:适用于环绕通知获取参数

获取切入点方法返回值:返回后通知 、环绕通知

获取切入点方法运行异常信息:抛出异常后通知、环绕通知
    //ProceedingJoinPoint:专用于环绕通知,是JoinPoint子类,可以实现对原始方法的调用
    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) {
        Object[] args = pjp.getArgs();
        System.out.println(Arrays.toString(args));
        args[0] = 666;//修改原始方法的参数
        Object ret = null;
        try {
            ret = pjp.proceed(args);
        } catch (Throwable t) {
            t.printStackTrace();
        }
        return ret;
    }

    //设置返回后通知获取原始方法的返回值,要求returning属性值必须与方法形参名相同
    @AfterReturning(value = "pt()",returning = "ret")
    public void afterReturning(JoinPoint jp,String ret) {
        System.out.println("afterReturning advice ..."+ret);
    }

    //设置抛出异常后通知获取原始方法运行时抛出的异常对象,要求throwing属性值必须与方法形参名相同
    @AfterThrowing(value = "pt()",throwing = "t")
    public void afterThrowing(Throwable t) {
        System.out.println("afterThrowing advice ..."+t);
    }

AOP事务管理

Spring 事务作用:在数据层或 业务层 保障一系列的数据库操作同成功同失败
Spring为了管理事务,提供了一个平台事务管理器PlatformTransactionManager,其实现类
DataSourceTransactionManager 内部采用的是 JDBC 的事务。如果持久层采用的是 JDBC相关的技术,就可以采用这个事务管理器来管理事务

 程序出问题后,我们需要让事务进行回滚,而且这个事务应该是加在业务层上

  1. 在需要被事务管理的方法上添加注解,@Transactional可以写在接口类上、接口方法上、实现类上和实现类方法上,建议写在实现类或实现类的方法上
    //配置当前方法具有事务
        @Transactional
        public void transfer(String out,String in ,Double money) {
            accountDao.outMoney(out,money);
            int i = 1/0;
            accountDao.inMoney(in,money);
        }

  2. JdbcConfig类中配置事务管理器
    
        @Bean
        public DataSource dataSource(){....}
    
        //配置事务管理器,mybatis使用的是jdbc事务
        @Bean
        public PlatformTransactionManager transactionManager(DataSource dataSource){
            DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
            transactionManager.setDataSource(dataSource);
            return transactionManager;
        }
  3. 在SpringConfig 的配置类中开启事务注解
    //设置当前Spring环境中开启注解式事务支持
    @EnableTransactionManagement
    public class SpringConfig {
    }

  4. 给业务层方法开启事务后,方法内的事务T1、T2就会加入该方法的事务中, 保证他们在同一个事务中,当业务层中出现异常,整个事务就会回滚,保证数据的准确性

    事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法

    务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法

 

事务配置

@Transactional 注解的参数上可以进行设置事务的属性
  • readOnlytrue只读事务,false读写事务,增删改要设为false,查询设为true
  • timeout: 设置超时时间单位秒,在多长时间之内事务没有提交成功就自动回滚, -1表示不设置超时时间
  • rollbackFor: 当出现指定异常进行事务回滚
    noRollbackFor: 当出现指定异常不进行事务回滚
  • rollbackForClassName 等同于 rollbackFor, 只不过属性为异常的类全名字符串
    noRollbackForClassName 等同于 noRollbackFor ,只不过属性为异常的类全名字符串
  • isolation 设置事务的隔离级别
    DEFAULT :默认隔离级别 , 会采用数据库的隔离级别
    READ_UNCOMMITTED : 读未提交
    READ_COMMITTED : 读已提交
    REPEATABLE_READ : 重复读取
    SERIALIZABLE: 串行化

    事务传播行为

    事务传播行为:事务协调员对事务管理员所携带事务的处理态度
    Spring 事务会把 T1,T2,T3都加入到事务T中,所以当转账失败后,所有的事务都回滚,导致日志没有记录下来,如何让log方法单独是一个事务呢?给logService的log方法加个注解propagation改变事务的传播行为即可不管转账是否成功,都会记录日志
    @Service
    public class LogServiceImpl implements LogService {
        @Autowired
        private LogDao logDao;
        //propagation设置事务属性:传播行为设置为当前操作需要新事务
        @Transactional(propagation = Propagation.REQUIRES_NEW)
        public void log(String out,String in,Double money ) {
            logDao.log("转账操作由"+out+"到"+in+",金额:"+money);
    }
    }

     

     

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值