Spring基础讲解

Spring的起源

要谈Spring的历史,就要先谈J2EE。J2EE应用程序的广泛实现是在1999年和2000年开始的,它的出现带来了诸如事务管理之类的核心中间层概念的标准化,但是在实践中并没有获得绝对的成功,因为开发效率,开发难度和实际的性能都令人失望。

曾经使用过EJB开发JAVA EE应用的人,一定知道,在EJB开始的学习和应用非常的艰苦,很多东西都不能一下子就很容易的理解。EJB要严格地实现各种不同类型的接口,类似的或者重复的代码大量存在。而配置也是复杂和单调,同样使用JNDI进行对象查找的代码也是单调而枯燥。虽然有一些开发工作随着xdoclet的出现,而有所缓解,但是学习EJB的高昂代价,和极低的开发效率,极高的资源消耗,都造成了EJB的使用困难。而Spring出现的初衷就是为了解决类似的这些问题。

Spring的一个最大的目的就是使JAVA EE开发更加容易。同时,Spring之所以与Struts、Hibernate等单层框架不同,是因为Spring致力于提供一个以统一的、高效的方式构造整个应用,并且可以将单层框架以最佳的组合揉和在一起建立一个连贯的体系。可以说Spring是一个提供了更完善开发环境的一个框架,可以为POJO(Plain Old Java Object)对象提供企业级的服务。

Spring的形成,最初来自Rod Jahnson所著的一本很有影响力的书籍《Expert One-on-One J2EE Design and Development》,就是在这本书中第一次出现了Spring的一些核心思想,该书出版于2002年。另外一本书《Expert One-on-One J2EE Development without EJB》,更进一步阐述了在不使用EJB开发JAVA EE企业级应用的一些设计思想和具体的做法。有时间了可以详细的研读一下。

 

Spring的初衷

1、JAVA EE开始应该更加简单。

2、使用接口而不是使用类,是更好的编程习惯。Spring将使用接口的复杂度几乎降低到了零。

3、为JavaBean提供了一个更好的应用配置框架。

4、更多地强调面向对象的设计,而不是现行的技术如JAVA EE。

5、尽量减少不必要的异常捕捉。

6、使应用程序更加容易测试。

 

Spring的目标

1、可以令人方便愉快的使用Spring。

2、应用程序代码并不依赖于Spring APIs。

3、Spring不和现有的解决方案竞争,而是致力于将它们融合在一起。

 

Spring的基本组成

1、最完善的轻量级核心框架。

2、通用的事务管理抽象层。

3、JDBC抽象层。

4、集成了Toplink, Hibernate, JDO, and iBATIS SQL Maps。

5、AOP功能。

6、灵活的MVC Web应用框架。

 

Spring的优点

  (1)方便解耦,简化开发

      Spring就是一个大工厂,可以将所有对象创建和依赖关系维护,交给Spring来管理;

  (2)AOP编程的支持

      Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能;

  (3)声明式事务的支持

       只需要通过配置就可以完成对事务的管理,无需手动编程;

  (4)方便程序的测试

      Spring对Junit4支持,可以通过注解方便的测试Spring程序;

  (5)方便集成各种优秀的框架

      Spring不排除各种优秀的开源框架,其内部提供了对各种优秀框架(Struts、Hibernate、Mybatis、Quartz等)的直接支持

  (6)降低JavaEE API的使用难度

      Spring对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等)都提供了封装,使这些API应用难度大大降低

 

(1)Spring的核心:IOC反转控制

  a.什么是IOC?

   Inversion of Control 控制反转,指的是将对象的创建权交给Spring,作用是降低程序的耦合性

  b.实现步骤

   步骤一:下载Spring开发包

       官网:http://spring.io

       下载地址:http://repo.springsource.org/libs-release-local/org/springframework/spring

   步骤二:创建web项目,引入Spring开发包

          

          

   步骤三:引入相关配置文件

       applicationContext.xml

       引入约束:(约束所在文件)                

       spring-framework-4.2.4.RELEASE\docs\spring-framework-reference\html\xsd-configuration.html

       <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 http://www.springframework.org/schema/beans/spring-beans.xsd">

       </beans>

   步骤四:编写相关类

          

   步骤五:完成配置

       <!-- Spring的入门案例================ -->

          <bean id="userDao" class="cn.itcast.spring.demo1.UserDaoImpl"></bean>

   步骤六:测试程序    

          @Test

          // Spring的方式:

          public void demo2(){

              // 创建Spring的工厂类:   
                             ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");

                // 通过工厂解析XML获取Bean的实例.

              UserDao userDao = (UserDao) applicationContext.getBean("userDao");  
              userDao.sayHello();

          }

           

        Spring中的工厂了解

   1.applicationContext接口

    ClassPathXmlApplicationContext  --加载类路径下的Spring配置文件

    FileSystemXmlApplicationContext  --加载本地磁盘下的Spring配置文件

   2.BeanFactory工厂(Spring早期创建bean对象的工厂接口)

    public void run(){
      eanFactory factory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
      UserService us = (UserService) factory.getBean("us");
      us.sayHello();
    }

  3.BeanFactory和ApplicationContext的区别

    * BeanFactory -- BeanFactory采取延迟加载,第一次getBean时才会初始化Bean
    * ApplicationContext -- 在加载applicationContext.xml时候就会创建具体的Bean对象的实例,
                 还提供了一些其他的功能
     * 事件传递
     * Bean自动装配
     * 各种不同应用层的Context实现

 

    c.Spring框架的Bean管理的配置文件方式


  技术分析之Spring框架中标签的配置

  1. id属性和name属性的区别
   * id -- Bean起个名字,在约束中采用ID的约束,唯一
   * 取值要求:必须以字母开始,可以使用字母、数字、连字符、下划线、句话、冒号 id:不能出现特殊字符

   *name -- Bean起个名字,没有采用ID的约束(了解)
   * 取值要求:name:出现特殊字符.如果<bean>没有id的话 , name可以当做id使用
   * Spring框架在整合Struts1的框架的时候,Struts1的框架的访问路径是以/开头的,例如:/bookAction

  2. class属性 -- Bean对象的全路径
  3. scope属性 -- scope属性代表Bean的作用范围

   * singleton -- 单例(默认值)
   * prototype -- 多例,在Spring框架整合Struts2框架的时候,Action类也需要交给Spring做管理,配置把Action类配置成多例!!
   * request -- 应用在Web项目中,每次HTTP请求都会创建一个新的Bean
   * session -- 应用在Web项目中,同一个HTTP Session 共享一个Bean
   * globalsession -- 应用在Web项目中,多服务器间的session

  4. Bean对象的创建和销毁的两个属性配置(了解)
   * 说明:Spring初始化bean或销毁bean时,有时需要作一些处理工作,因此spring可以在创建和拆卸bean的时候调用bean的两个生命周期方法
   * init-method -- 当bean被载入到容器的时候调用init-method属性指定的方法
   * destroy-method -- 当bean从容器中删除的时候调用destroy-method属性指定的方法
   * 想查看destroy-method的效果,有如下条件
   * scope= singleton有效
   * web容器中会自动调用,但是main函数或测试用例需要手动调用(需要使用ClassPathXmlApplicationContext的close()方法)

 

 d.依赖注入(DI)


 1. IOC和DI的概念
  * IOC -- Inverse of Control,控制反转,将对象的创建权反转给Spring!!
  * DI -- Dependency Injection,依赖注入,在Spring框架负责创建Bean对象时,动态的将依赖对象注入到Bean组件中!!

 2. DI(依赖注入)
  * 例如:如果UserServiceImpl的实现类中有一个属性,那么使用Spring框架的IOC功能时,可以通过依赖注入把该属性的值传入进来!!
  * 具体的配置如下
    <bean id="us" class="com.itheima.demo1.UserServiceImpl">
      <property name="uname" value="小风"/>
    </bean>

 

e.Spring框架的属性注入


 1. 对于类成员变量,常用的注入方式有两种
   * 构造函数注入
   * 属性setter方法注入

 2. 在Spring框架中提供了前两种的属性注入的方式
   1. 构造方法的注入方式,两步
    * 编写Java的类,提供构造方法
    public class Car {
      private String name;
      private double money;
      public Car(String name, double money) {
          this.name = name;
          this.money = money;
      }
      @Override
      public String toString() {
         return "Car [name=" + name + ", money=" + money + "]";
      }
     }

    * 编写配置文件
     <bean id="car" class="com.itheima.demo4.Car">
        <constructor-arg name="name" value="大奔"/>
        <constructor-arg name="money" value="100"/>
     </bean>

  2. 属性的setter方法的注入方式
   * 编写Java的类,提供属性和对应的set方法即可
   * 编写配置文件

  3. 如果Java类的属性是另一个Java的类,那么需要怎么来注入值呢?
   * <property name="name" rel="具体的Bean的ID或者name的值"/>
   * 例如:
      <bean id="person" class="com.itheima.demo4.Person">
         <property name="pname" value="小美"/>
         <property name="car2" ref="car2"/>
      </bean>

 

 f.数组,集合(List,Set,Map),Properties等的注入


 1. 如果是数组或者List集合,注入配置文件的方式是一样的
 <bean id="collectionBean" class="com.itheima.demo5.CollectionBean">
    <property name="arrs">
      <list>
        <value>小美</value>
        <value>小风</value>
      </list>
    </property>
 </bean>

 

2. 如果是Set集合,注入的配置文件方式如下:
 <property name="sets">
   <set>
      <value>哈哈</value>
      <value>呵呵</value>
   </set>
 </property>

3. 如果是Map集合,注入的配置方式如下:
 <property name="map">
    <map>
       <entry key="老王" value="38"/>
       <entry key="凤姐" value="38"/>
       <entry key="如花" value="29"/>
    </map>
 </property>

4. 如果是properties属性文件的方式,注入的配置如下:
 <property name="pro">
   <props>
      <prop key="uname">root</prop>
      <prop key="pass">123</prop>
   </props>
 </property>

 

g.Spring框架整合WEB


 Spring框架整合WEB(不是最终的方案)

 1. 创建JavaWEB项目,引入Spring的开发包。编写具体的类和方法。
  * 环境搭建好后,启动服务器来测试项目,发送每访问一次都会加载一次配置文件,这样效率会非常非常慢!!

 2. 解决上面的问题
  * 将工厂创建好了以后放入到ServletContext域中.使用工厂的时候,从ServletContext中获得.
  * ServletContextListener:用来监听ServletContext对象的创建和销毁的监听器.
  * 当ServletContext对象创建的时候:创建工厂 , 将工厂存入到ServletContext

 3. Spring整合Web项目
  * 引入spring-web-4.2.4.RELEASE.jar包
  * 配置监听器
     <!-- 配置Spring的核心监听器: -->
     <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
     </listener>
     <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
     </context-param>

 4. 修改servlet的代码
   * 从ServletContext中获得工厂
   * 具体代码如下
    ServletContext servletContext = ServletActionContext.getServletContext();
    // 需要使用WEB的工厂的方式
    WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext);
    CustomerService cs = (CustomerService) context.getBean("customerService");
    cs.save();

 

h.注解方式实现上述功能


1.下载导入相应jar包

 导入 spring-aop.jar

2.配置文件

 引入约束(引入context的约束):
   <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">
  </beans>

3.配置注解扫描

 <!-- Spring的注解开发:组件扫描(类上注解: 可以直接使用属性注入的注解) -->

 context:component-scan base-package="com.spring.demo1"/>

4.添加类的注解

 @Component(value="userDao")

   public class UserDaoImpl implements UserDao {

        @Override

        public void sayHello() {

                System.out.println("Hello Spring Annotation...");

        }

  }

5.测试

 @Test

        public void demo2() {

                ApplicationContext applicationContext = new ClassPathXmlApplicationContext(

                "applicationContext.xml");

                UserDao userDao = (UserDao) applicationContext.getBean("userDao");

                userDao.sayHello();

        }

6.注解说明

(1)Spring中提供@Component的三个衍生注解:(功能目前来讲是一致的)

         * @Controller   :WEB层

         * @Service       :业务层

         * @Repository   :持久层

(2)属性注入的注解:(使用注解注入的方式,可以不用提供set方法.)

         @Value:用于注入普通类型.

         @Autowired:自动装配:

         * 默认按类型进行装配.

         * 按名称注入:

         * @Qualifier:强制使用名称注入.

         @Resource相当于:

         * @Autowired和@Qualifier一起使用.

              

 

(2)Spring的核心:AOP面向切面

 a.概述

  Aspect Oriented Programming的缩写,面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。

 AOP是OOP的延续,是软件开发的一个热点,也是Spring的一个重要内容,是函数式编程的一种衍生泛型。利用AOP可以对业务逻辑的各个部分

 进行隔离,从而减低耦合度,提高程序的可重用性,提高开发效率。

 

b.特点


 * 不修改源码的情况下对程序进行增强;

 * 可以进行权限校验,日志记录,性能监控,事务控制

 * 由AOP联盟提出,并制定规范,Spring将AOP引入框架中必须遵守AOP联盟规范

 

c.底层实现(Spring的AOP底层用到了两种代理机制)


 * JDK的动态代理:针对实现了接口的类产生代理

 * CGLIB的动态代理:针对没有实现接口的类产生代理,应用的是底层的字节码增强技术,生成的是当前类的子类对象

 

1.JDK动态代理增强一个类中的方法

 public class MyJDKProxy implements InvacationHandler{

   private UserDao userDao;

   public MyJDKProxy (UserDao userDao){

      this.userDao = userDao;

   }

   //编写工具方法:生成代理

   public UserDao createProxy(){

      Userdao userDaoProxy = (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(), 

      userDao.getClass().getInterfaces(), this);            

      return userDaoProxy;

   }

 

   @override

   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{

      if("save".equals(method.getName())){

         System.out.println("权限校验==========");

      }

      return method.invoke(uerDao, args);

   }

     }

 

2.CGLIB动态代理增强一个类中的方法

        

 

d.相关术语


Joinpoint(连接点):指的是被拦截的点;在Spring中,这些点指的是方法,因为Spring只支持方法类型的连接点
Pointcut(切入点):指的是被拦截的Joinpoint
Advice(通知/增强):指的是拦截Joinpoint后做的事,由前置通知,后置通知,异常通知,最终通知,环绕通知
Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field.
Target(目标对象):代理的目标对象
Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程. spring采用动态代理织入,而AspectJ采用编译期织入和类装在期织入
Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类
Aspect(切面): 是切入点和通知(引介)的结合

 

e.Spring 使用AspectJ进行AOP的开发:XML的方式


1.导入jar包

* spring的传统AOP的开发的包
  spring-aop-4.2.4.RELEASE.jar
  com.springsource.org.aopalliance-1.0.0.jar
* aspectJ的开发包:
  com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
  spring-aspects-4.2.4.RELEASE.jar

2.配置文件

引入AOP约束:
<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" xsi:schemaLocation="
             http://www.springframework.org/schema/beans
             http://www.springframework.org/schema/beans/spring-beans.xsd
             http://www.springframework.org/schema/aop
             http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>

3.编写目标类

创建接口和类:
public interface OrderDao {
  public void save();
  public void update();
  public void delete();
  public void find();
}

public class OrderDaoImpl implements OrderDao {

@Override
public void save() {
  System.out.println("保存订单...");
}

@Override
public void update() {
  System.out.println("修改订单...");
}

@Override
public void delete() {
  System.out.println("删除订单...");
}

@Override
public void find() {
  System.out.println("查询订单...");
}

}

 

4.编写切面类

public class MyAspectXml {
  // 前置增强
  public void before(){
    System.out.println("前置增强===========");
  }
}

 

5.配置目标类和完成增强

<!-- 目标类================ -->
<bean id="orderDao" class="cn.itcast.spring.demo3.OrderDaoImpl">
</bean>

<!-- 配置切面类 -->
<bean id="myAspectXml" class="cn.itcast.spring.demo3.MyAspectXml"></bean>

<!-- 进行aop的配置 -->
<aop:config>
  <!-- 配置切入点表达式:哪些类的哪些方法需要进行增强 -->
  <aop:pointcut expression="execution(* cn.itcast.spring.demo3.OrderDao.save(..))" id="pointcut1"/>
  <!-- 配置切面 -->
  <aop:aspect ref="myAspectXml">
    <aop:before method="before" pointcut-ref="pointcut1"/>
  </aop:aspect>
</aop:config>

 

6.使用Junit进行测试

引入spring-test.jar

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringDemo3 {
  @Resource(name="orderDao")
  private OrderDao orderDao;

  @Test
  public void demo1(){
    orderDao.save();
    orderDao.update();
    orderDao.delete();
    orderDao.find();
  }
}

 

7.通知类型和切入点

 通知类型:

   前置通知:在目标方法执行之前执行;

   后置通知:在目标方法执行之后执行,若目标方法异常则不执行;

   环绕通知:在目标方法执行之前和之后执行,需要手动执行目标类的方法;

   最终通知:无论目标方法是否出现异常,最终都会执行;

   异常抛出通知:在目标方法出现异常时执行;

 切入点表达式:

   execution表达式:[方法访问修饰符] 返回值 包名.类名.方法名(参数)     

        

<!-- 配置切面类 -->
<bean id="myAspectXml" class="cn.itcast.spring.demo3.MyAspectXml"></bean>

<!-- 进行aop的配置 -->
<aop:config>
  <!-- 配置切入点表达式:哪些类的哪些方法需要进行增强 -->
  <aop:pointcut expression="execution(* cn.itcast.spring.demo3.*Dao.save(..))" id="pointcut1"/>
  <aop:pointcut expression="execution(* cn.itcast.spring.demo3.*Dao.delete(..))" id="pointcut2"/>
  <aop:pointcut expression="execution(* cn.itcast.spring.demo3.*Dao.update(..))" id="pointcut3"/>
  <aop:pointcut expression="execution(* cn.itcast.spring.demo3.*Dao.find(..))" id="pointcut4"/>
  <!-- 配置切面 -->
  <aop:aspect ref="myAspectXml">
    <aop:before method="before" pointcut-ref="pointcut1"/>
    <aop:after-returning method="afterReturing" pointcut-ref="pointcut2"/>
    <aop:around method="around" pointcut-ref="pointcut3"/>
    <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut4"/>
    <aop:after method="after" pointcut-ref="pointcut4"/>
  </aop:aspect>
</aop:config>

 

f.Spring使用AspectJ进行AOP开发,注解方式


1.导入jar

* spring的传统AOP的开发的包
  spring-aop-4.2.4.RELEASE.jar
  com.springsource.org.aopalliance-1.0.0.jar
* aspectJ的开发包:
  com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
  spring-aspects-4.2.4.RELEASE.jar

2.配置文件

引入AOP约束:
<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" xsi:schemaLocation="
             http://www.springframework.org/schema/beans
             http://www.springframework.org/schema/beans/spring-beans.xsd
             http://www.springframework.org/schema/aop
             http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>

3.目标类和切面类

public class ProductDao {
  public void save(){
    System.out.println("保存商品...");
  }
  public void update(){
    System.out.println("修改商品...");
  }
  public void delete(){
    System.out.println("删除商品...");
  }
  public void find(){
    System.out.println("查询商品...");
  }
}

@Aspect

public class MyAspectAnno {

             @Before("MyAspectAnno.pointcut1()")

             public void before(){

                          System.out.println("前置通知===========");

             }

             @Pointcut("execution(* cn.itcast.spring.demo4.ProductDao.save(..))")

             private void pointcut1(){}

}

 

4.配置注解自动代理

 <!-- 开启aop注解的自动代理 -->

 <aop:aspectj-autoproxy/>

 <!-- 目标类============ -->

 <bean id="productDao" class="cn.itcast.spring.demo4.ProductDao"></bean>

 <!-- 配置切面类 -->

 <bean id="myAspectAnno" class="cn.itcast.spring.demo4.MyAspectAnno"></bean> 

 

5.其他通知的注解

 @Aspect

    public class MyAspectAnno {

        @Before("MyAspectAnno.pointcut1()")

        public void before(){

        System.out.println("前置通知===========");

    }

    @AfterReturning("MyAspectAnno.pointcut2()")

    public void afterReturning(){

        System.out.println("后置通知===========");

    }

    @Around("MyAspectAnno.pointcut3()")

    public Object around(ProceedingJoinPoint joinPoint) throws Throwable{

        System.out.println("环绕前通知==========");

        Object obj = joinPoint.proceed();

        System.out.println("环绕后通知==========");

        return obj;

    }

    @AfterThrowing("MyAspectAnno.pointcut4()")

    public void afterThrowing(){

        System.out.println("异常抛出通知========");

    }

    @After("MyAspectAnno.pointcut4()")

    public void after(){

        System.out.println("最终通知==========");

    }

    //自定义切入点

    @Pointcut("execution(* cn.itcast.spring.demo4.ProductDao.save(..))")

    private void pointcut1(){}

    @Pointcut("execution(* cn.itcast.spring.demo4.ProductDao.update(..))")

    private void pointcut2(){}

    @Pointcut("execution(* cn.itcast.spring.demo4.ProductDao.delete(..))")

    private void pointcut3(){}

    @Pointcut("execution(* cn.itcast.spring.demo4.ProductDao.find(..))")

    private void pointcut4(){}

    }

    @Aspect

    publicclass MyAspectAnno {

        @Before("MyAspectAnno.pointcut1()")

        publicvoid before(){

            System.out.println("前置通知===========");

        }   

    @Pointcut("execution(* cn.itcast.spring.demo4.ProductDao.save(..))")

    privatevoid pointcut1(){}

}

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

林海峰

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

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

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

打赏作者

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

抵扣说明:

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

余额充值