Spring框架---AOP技术

AOP概念的引入

第一步创建普通Maven项目

导入依赖

<dependencies>
        <!--spring的核心-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <!--日志-->
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>
        <!--Junit测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!--数据库连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.10</version>
        </dependency>
        <!--Spring整合junit测试-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
            <scope>test</scope>
        </dependency>
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
    </dependencies>

编写数据库数据实体类 

package com.qcby.model;

public class Account {
    private Integer id;
    private String name;
    private Double money;

    public Account() {
    }

    public Account(Integer id, String name, Double money) {
        this.id = id;
        this.name = name;
        this.money = money;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}

业务层接口

package com.qcby.service;

import com.qcby.model.Account;

public interface AccountService {
    public void updateSaveAll(Account account1, Account account2);
}

业务层实现类

package com.qcby.service.Impl;

import com.qcby.dao.AccountDao;
import com.qcby.model.Account;
import com.qcby.service.AccountService;
import com.qcby.utils.TxUtils;

public class AccountServiceImpl implements AccountService{

    private AccountDao accountDao;


    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }
    /**
     * 对saveAll方法进行增强
     * @param account1
     * @param account2
     * @throws SQLException
     */

    @Override
    public void updateSaveAll(Account account1, Account account2) {
        //try {
            //TxUtils.startTransaction();
            //保存账号1
            accountDao.updateSaveAll(account1);
            //显示除零错误
            int a=1/0;
            //保存账户2
            accountDao.updateSaveAll(account2);
            //TxUtils.commit();
        //}catch (Exception e){
            //e.printStackTrace();
            //TxUtils.rollback();
        //}

    }
}

持久层接口

package com.qcby.dao;

import com.qcby.model.Account;

public interface AccountDao {
    public void updateSaveAll(Account account);
}

持久层实现类 

package com.qcby.dao;

import com.qcby.model.Account;
import com.qcby.utils.TxUtils;


import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class AccountDaoImpl implements AccountDao {

    //实现两个账户的转账
    @Override
    public void updateSaveAll(Account account) {
        Connection connection = null;
        PreparedStatement stmt = null;
        try{
            connection = TxUtils.getConnection();
            String sql = "update account set money = money + ? where name = ?";
            // 预编译SQL语句
            stmt = connection.prepareStatement(sql);
            stmt.setDouble(1,account.getMoney());
            stmt.setString(2,account.getName());
            //查询
            int result = stmt.executeUpdate();
            System.out.println("修改影响了"+result+"行数据");
        }catch (Exception e){
            try{
                stmt.close();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
        }

    }
}

 使用事务工具类TxUtils获取连接,使两次对数据库的操作在同一个连接,使用线程存储这个连接,避免因错误等原因导致”钱转丢问题“

package com.qcby.utils;

import java.sql.Connection;
import java.sql.SQLException;

import javax.sql.DataSource;

import com.alibaba.druid.pool.DruidDataSource;

/**
 * 事务的工具类
 */
public class TxUtils {
	
	private static DruidDataSource ds = null;

	// 使用ThreadLocal存储当前线程中的Connection对象
	private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();

	// 在静态代码块中创建数据库连接池
	static {
		try {
			// 通过代码创建Druid数据库连接池
			ds = new DruidDataSource();
			ds.setDriverClassName("com.mysql.jdbc.Driver");
			ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
			ds.setUsername("root");
			ds.setPassword("2020");
		} catch (Exception e) {
			throw new ExceptionInInitializerError(e);
		}
	}

	/**
	 * @Method: getConnection
	 * @Description: 从数据源中获取数据库连接
	 * @Anthor:
	 * @return Connection
	 * @throws SQLException
	 */
	public static Connection getConnection() throws SQLException {

		// 从当前线程中获取Connection
		Connection conn = threadLocal.get();

		if (conn == null) {
			// 从数据源中获取数据库连接
			conn = getDataSource().getConnection();
			// 将conn绑定到当前线程
			threadLocal.set(conn);
		}
		return conn;
	}

	/**
	 * @Method: startTransaction
	 * @Description: 开启事务
	 * @Anthor:
	 *
	 */
	public static void startTransaction() {
		try {
			Connection conn = threadLocal.get();
			if (conn == null) {
				conn = getConnection();
				// 把 conn绑定到当前线程上
				threadLocal.set(conn);
			}

			// 开启事务
			conn.setAutoCommit(false);

		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * @Method: rollback
	 * @Description:回滚事务
	 * @Anthor:
	 */
	public static void rollback() {
		try {
			// 从当前线程中获取Connection
			Connection conn = threadLocal.get();
			if (conn != null) {
				// 回滚事务
				conn.rollback();
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * @Method: commit
	 * @Description:提交事务
	 * @Anthor:
	 */
	public static void commit() {
		try {
			// 从当前线程中获取Connection
			Connection conn = threadLocal.get();
			if (conn != null) {

				// 提交事务
				conn.commit();

			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * @Method: close
	 * @Description:关闭数据库连接(注意,并不是真的关闭,而是把连接还给数据库连接池)
	 * @Anthor:
	 *
	 */
	public static void close() {
		try {
			// 从当前线程中获取Connection
			Connection conn = threadLocal.get();
			if (conn != null) {
				conn.close();
				// 解除当前线程上绑定conn
				threadLocal.remove();
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * @Method: getDataSource
	 * @Description: 获取数据源
	 * @Anthor:
	 * @return DataSource
	 */
	public static DataSource getDataSource() {
		// 从数据源中获取数据库连接
		return ds;
	}

}

使用JDK动态代理

创建一个JdkProxy代理类,来对目标对象的方法进行增强,代理核心方法updateSaveAll()

package com.qcby.JdkProxy;

import com.qcby.service.AccountService;
import com.qcby.utils.TxUtils;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 生成代理对象
 * 传入目标对象,生成该对象的代理对象,返回,在对目标对象的方法进行增强
 */
public class JdkProxy {

    /**
     * 获取代理对象的方法 返回代理对象  对目标对象的方法进行增强
     * @param accountService
     * @return
     */
    public static Object getProxy(AccountService accountService){
        /**
         * 使用JDK动态代理生成代理对象
         * 第一个参数:类的加载器
         * 第二个参数:当前传入的对象实现了哪些接口要字节码对象
         * 第三个参数:回调函数
         */
        Object proxy = Proxy.newProxyInstance(JdkProxy.class.getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() {
            /**
             * 调用代理对象的方法invoke方法就会执行
             * @param proxy
             * @param method
             * @param args
             * @return
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                //System.out.println("你调用了代理对象的invoke方法了。。。");
                //对目标对象的方法进行增强
                Object result = null;
                try {
                    //开启事务
                    TxUtils.startTransaction();
                    result = method.invoke(accountService, args);
                    //提交事务
                    TxUtils.commit();
                } catch (Exception e) {
                    e.printStackTrace();
                    //回滚事务
                    TxUtils.rollback();
                } finally {
                    TxUtils.close();
                }
                return result;
            }
        });
        return proxy;
    }
}

配置文件 

<?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: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">

    <bean id="accountService" class="com.qcby.service.Impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"/>
    </bean>
    
    <bean id="accountDao" class="com.qcby.dao.AccountDaoImpl"/>
</beans>

编写测试类

package com.qcby.springAopTest;

import com.qcby.JdkProxy.JdkProxy;
import com.qcby.model.Account;
import com.qcby.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Test01 {

    @Autowired
    private AccountService accountService;


    @Test
    public void run(){
        Account account1 = new Account(null,"aaa",-1000.00);
        Account account2 = new Account(null,"bbb",1000.00);
        //accountService.updateSaveAll(account1,account2);
        Object proxyobj = JdkProxy.getProxy(accountService);
        //强转成accountService因为生成代理对象时候是写死的
        AccountService proxy = (AccountService) proxyobj;
        //在调用增强后的代理对象的savaAll方法
        //accountService.updateSaveAll(account1,account2);
        proxy.updateSaveAll(account1,account2);
    }

}

AOP相关的概念

AOP的概述

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程

  • AOP是一种编程范式,隶属于软工范畴,指导开发者如何组织程序结构
  • 通过预编译方式或者运行期动态代理实现程序功能的统一维护的一种技术
  • AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型
  • 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率

AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码(性能监视、事务管理、安全检查、缓存)

可以在不修改源代码的前提下,对程序进行增强!!

减少重复的代码,提供开发的xiaolv,维护方便

AOP的底层原理

AOP是基于动态代理实现的,动态分为两种

JDK的动态代理技术        基于接口

​ 1、为接口创建代理类的字节码文件

​ 2、使用ClassLoader将字节码文件加载到JVM

​ 3、创建代理类实例对象,执行对象的目标方法

cglib代理技术        基于类

AOP相关的术语

名词

解释

Joinpoint(连接点)

连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。

Pointcut(切入点)

切入点是指我们要对哪些Joinpoint进行拦截的定义

Advice(通知/增强)

通知是指拦截到Joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)

Target(目标对象)

代理的目标对象

Weaving(织入)

把增强应用到目标对象来创建新的代理对象的过程

Proxy(代理)

一个类被AOP织入增强后,就产生一个结果代理类

Aspect(切面)

切入点和通知的结合,以后咱们自己来编写和配置的

AOP配置文件方式

创建maven项目,引入依赖

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.2</version>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.12</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
  
        <!-- AOP联盟 -->
        <dependency>
            <groupId>aopalliance</groupId>
            <artifactId>aopalliance</artifactId>
            <version>1.0</version>
        </dependency>
        <!-- Spring Aspects -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <!-- aspectj -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.3</version>
        </dependency>
  </dependencies>

创建applicationContext_demo2.xml配置文件,引入具体的AOP的schema约束

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

创建包结构,编写具体的接口和实现类

接口 

package com.qcby.demo2;

public interface UserService {

    public void save();
}

实现类 

package com.qcby.demo2;

public class UserServiceImpl implements UserService{

    @Override
    public void save() {
        System.out.println("业务层:保存用户...");
    }
}

将目标类配置到Spring中

<!--管理bean对象-->
    <bean id="userService" class="com.qcby.demo2.UserServiceImpl"/>

定义切面类

package com.qcby.demo2;

/**
 * 自定义切面类 = 切入点(表达式) + 通知(增强的代码)
 */
public class MyXmlAspect {

    /**
     * 通知
     */
    public void log(){
        //发送手机端行
        //发送邮件/记录日志/事务管理

        System.out.println("增强的方法执行了。。。");
    }
}

在配置文件中定义切面类

<!--配置切面类,把该类交给IOC容器管理-->
    <bean id="myXmlAspect" class="com.qcby.demo2.MyXmlAspect"/>

在配置文件中完成aop的配置

<!--配置AOP的增强-->
    <aop:config>
        <!--配置切面 = 切入点 + 通知组成-->
        <aop:aspect ref="myXmlAspect">
            <!--前置通知:UserServiceImpl的save方法执行前,会增强-->
            <aop:before method="log" pointcut="execution(public void com.qcby.demo2.UserServiceImpl.save())"/>
        </aop:aspect>
    </aop:config>

完成测试

package com.qcby.demo2Test;

import com.qcby.demo2.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext_demo2.xml")
public class demo2Test {

    @Autowired
    private UserService userService;

    /**
     * 测试
     */
    @Test
    public void run1(){
        userService.save();
    }
}

 测试结果

切入点的表达式

再配置切入点的时候,需要定义表达式,具体展开如下:

切入点表达式的格式如下:

  • execution([修饰符] 返回值类型 包名.类名.方法名(参数))
  • 修饰符可以省略不写,不是必须要出现的。
  • 返回值类型是不能省略不写的,根据你的方法来编写返回值。可以使用 * 代替。
  • 包名例如:com.tx.demo3.BookDaoImpl
    • 首先com是不能省略不写的,但是可以使用 * 代替
    • 中间的包名可以使用 * 号代替
    • 如果想省略中间的包名可以使用 ..
  • 类名也可以使用 * 号代替,也有类似的写法:*DaoImpl
  • 方法也可以使用 * 号代替
  • 参数如果是一个参数可以使用 * 号代替,如果想代表任意参数使用 ..

<?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:context="http://www.springframework.org/schema/context"
       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/context
                http://www.springframework.org/schema/context/spring-context.xsd
                http://www.springframework.org/schema/aop
                http://www.springframework.org/schema/aop/spring-aop.xsd">
​
    <!--管理bean对象-->
    <bean id="userService" class="com.qcby.demo2.UserServiceImpl"/>
​
    <!--配置切面类,把该类交给IOC容器管理-->
    <bean id="myXmlAspect" class="com.qcby.demo2.MyXmlAspect"/>
​
    <!--配置AOP的增强-->
    <aop:config>
        <!--配置切面 = 切入点 + 通知组成-->
        <aop:aspect ref="myXmlAspect">
            <!--前置通知:UserServiceImpl的save方法执行前,会增强
                <aop:before method="log" pointcut="execution(public void com.qcby.demo2.UserServiceImpl.save())" />
            -->
​
            <!--
                切入点的表达式
                    execution() 固定的写法
                    public          是可以省略不写的
                   方法的返回值      int String 通用的写法,可以编写 * 不能省略不写的
                   包名+类名        不能省略不写的,编写 *  UserServiceImpl AccountServiceImpl
                   方法名称         save() 可以写 *
                   参数列表         (..) 表示任意类型和个数的参数
                   比较通用的表达式:execution(public * com.qcby.*.*ServiceImpl.*(..))
            -->
            <aop:before method="log" pointcut="execution(* com.qcby.*.*ServiceImpl.save*(..))" />
​
        </aop:aspect>
    </aop:config>
​
</beans>

AOP的通知类型

通知类型类型说明

before

前置通知 目标方法执行前,进行增强。

afte

最终通知 目标方法执行成功或者失败,进行增强。

after-returning

后置通知 目标方法执行成功后,进行增强。

after-throwing

异常通知 目标方法执行失败后,进行增强。

around

环绕通知 目标方法执行前后,都可以进行增强。目标对象的方法需要手动执行。

<?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:context="http://www.springframework.org/schema/context"
       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/context
                http://www.springframework.org/schema/context/spring-context.xsd
                http://www.springframework.org/schema/aop
                http://www.springframework.org/schema/aop/spring-aop.xsd">
​
    <!--管理bean对象-->
    <bean id="userService" class="com.qcby.demo2.UserServiceImpl"/>
​
    <!--配置切面类,把该类交给IOC容器管理-->
    <bean id="myXmlAspect" class="com.qcby.demo2.MyXmlAspect"/>
​
    <!--配置AOP的增强-->
    <aop:config>
        <!--配置切面 = 切入点 + 通知组成-->
        <aop:aspect ref="myXmlAspect">
           
            <!--
                AOP的通知类型
                    前置通知:目标方法执行前,进行增强。
                        <aop:before method="log" pointcut="execution(* com.qcby.*.*ServiceImpl.save*(..))" />
                    最终通知:目标方法执行成功或者失败,进行增强。
                        <aop:after method="log" pointcut="execution(* com.qcby.*.*ServiceImpl.save*(..))" />
                    后置通知:目标方法执行成功后,进行增强。
                        <aop:after-returning method="log" pointcut="execution(* com.qcby.*.*ServiceImpl.save*(..))" />
                    异常通知:目标方法执行失败后,进行增强。
                        <aop:after-throwing method="log" pointcut="execution(* com.qcby.*.*ServiceImpl.save*(..))" />
                    环绕通知:目标方法执行前后,都可以进行增强。目标对象的方法需要手动执行。
            -->
            <aop:around method="aroundLog" pointcut="execution(* com.qcby.*.*ServiceImpl.save*(..))" />
​
        </aop:aspect>
    </aop:config>
​
</beans>

Spring的AOP技术-注解方式

AOP注解方式入门程序(半注解)

给切面类添加注解 @Aspect,编写增强的方法,使用通知类型注解声明

package com.qcby.demo3;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Component  // 把该类交给IOC去管理
@Aspect  // 声明是切面类  == <aop:aspect ref="myXmlAspect">
public class MyAnnoAspect {

    /**
     * 通知的方法
     */
    //@Before(value = "切入点表达式")
    @Before(value = "execution(public * com.qcby.demo3.OrderServiceImpl.save(..))")
    public void log(){
        System.out.println("增强了。。。");
    }
}

配置文件中开启自动代理

<aop:aspectj-autoproxy/>

编写测试

/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext_demo3.xml")
public class Demo3 {
​
    @Autowired
    private OrderService orderService;
​
    /**
     * 测试
     */
    @Test
    public void run1(){
        orderService.save();
    }
​
}

通知类型的注解

通知类型注解

注解

@Before

前置通知

@AfterReturing

后置通知

@Around

环绕通知(目标对象方法默认不执行的,需要手动执行)

@After

最终通知

@AfterThrowing

异常抛出通知

纯注解的方式

​
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
​

@Configuration      // 配置类
@ComponentScan(value = "com.qcby.demo3")   // 扫描包
@EnableAspectJAutoProxy     // 开启自动代理 == <aop:aspectj-autoproxy />
public class SpringConfig {
    
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值