Spring框架学习记录

初学记录笔记

1 IOC和DI
1.1 IOC:控制反转
  • IOC全称Inverse of control,是一种设计理念
  • 由代理人创建管理对象,消费者通过代理人获取对象
  • IOC的目的是降低对象之间的耦合
  • 加入IOC容器将对象统一管理,让对象关联变为弱耦合
1.2 DI:依赖注入
  • IOC是设计理念,是现代程序设计遵循的标准,是宏观目标
  • DI全称Dependency injection是具体的技术实现,是微观实现
  • DI在Java中利用反射技术实现对象注入(Injection)
2 配置IOC容器
2.1 xml配置方式
2.1.1 xml实例化beans
  • 构造方法实例化对象
<bean id="apple1" class="com.mo.spring.ioc.entity.Apple">
        <!--constructor-arg代表调用默认构造方法实例化-->
            <constructor-arg name="title" value="红富士"/>
            <constructor-arg name="origin" value="欧洲"/>
            <constructor-arg name="color" value="红色"/>
</bean>

<bean id="apple2" class="com.mo.spring.ioc.entity.Apple">
        <!--利用构造方法参数位置实现对象实例化-->
            <constructor-arg index="0" value="蛇果"/>
            <constructor-arg index="1" value="美国"/>
            <constructor-arg index="2" value="红色"/>
</bean>
  • 静态工厂实例化
<!--静态工厂获取对象 factory-method需要调用的静态方法-->
    <bean id="apple3" class="com.mo.spring.ioc.factory.AppleStaticFactory"
          factory-method="createSweetFactory">
  • 工厂实例方法实例化
<!--工厂实例方法获取对象 AppleFactoryInstance类createSweetApple()方法-->
    <bean id="factoryInstance" class="com.mo.spring.ioc.factory.AppleFactoryInstance"/>
    <bean id="apple4" factory-bean="factoryInstance" factory-method="createSweetApple"/>
2.1.2 从ioc容器中获取bean
//初始化IOC容器并实例化对象
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        // Apple apple1 = (Apple) context.getBean("apple1");
        Apple apple1 = context.getBean("apple1", Apple.class);
2.1.3 bean scope属性
  • 决定对象何时创建与作用范围
  • 配置影响容器内对象数量
  • 默认情况bean会在IOC容器创建后自动实例化,全局唯一
scope属性说明
singleton单例(默认值),每个容器有且只有一个唯一的实例,全局共享
prototype多例,每次使用都创建一个实例
requestweb环境下,每一次独立请求存在唯一实例
sessionweb环境下,每一个session存在唯一实例
applicationweb环境下,ServletContext存在唯一实例
websocket每一次websocket存在唯一实例
2.2 注解配置IOC容器
  • 摆脱繁琐的XML形式的bean与依赖注入
  • 基于声明式原则,更适合轻量级应用
  • 可读性
    按注解类型分为三类注解:组件类型注解,自动装配注解元数据注解.
2.2.1 组件类型注解
  • 声明当前类型的功能与职责
注解说明
@Component组件注解,通用注解,被该注解描述的类将被IOC容器管理并实例化
@Controller语义注解,说明当前类是MVC应用中的控制器类型
@Service语义注解,说明当前类是Service业务逻辑类
@Repository语义注解,说明当前类用于业务持久层,通常描述对应的Dao类
  • applicationContext.xml配置
<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <!--在IOC,容器初始化时自动扫描四种组件类型并完成实例化
    @Repository
    @Service
    @Controller
    @Component-->
    <context:component-scan base-package="com.mo"/>
</beans>

自动装配Autowired

分类 注解 说明
按类型装配@Autowired按容器对象类型动态注入属性,由Spring提供     
@Inject       基于JSR-330(Dependency Injection for Java),  
标准,其他同@Autowired,但不支持required.       
按名称装配@Named    与Inject配合使用,JSR-330规范,按属性名自动装
配属性.                                                               
@Resource基于JSR-250规范,优先按名称,再按类型智能匹配

@Autowired

package com.mo.spring.service;

import com.mo.spring.dao.IUserDao;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    public UserService() {
        System.out.println("UserService");
    }

    //@Autowired
    //Spring IoC容器会自动通过反射技术将属性private修饰符自动改为public,直接进行赋值,不执行set方法
    private IUserDao udao;

    public IUserDao getUdao() {
        return udao;
    }

    //@Autowired
    //装配方法放在set方法上,会自动按类型or名称对set方法进行注入
    public void setUdao(IUserDao udao) {
        System.out.println("setUdao:" + udao);
        this.udao = udao;
    }
}

@Resource

  • a.设置name属性,按name在IOC容器中注入bean
  • b.未设置name属性,以属性名作为bean name在IOC容器中匹配bean,如果匹配就注入,否则按类型进行匹配,同Autowired,需要加入@Pirmary解决类型冲突.

建议:在使用@Resource对象时推荐设置name或保证属性名和bean名称一致

2.2.2 元数据注解
注解 说明
@Primary按类型装配时出现多个相同类型对象,拥有此注解的对象优先被注入
@Postconstruct描述方法,相当于XML中的init-method配置的注解版本
@Predestroy描述方法,相当于XML中的destroy-method配置的注解版本
@Scope设置对象的Scope属性
@Value为属性静态注入数据
@Value 在applicationContext.xml加入 < context:property-placeholder location="config.properties"/>通知Spring IOC容器初始时加载属性文件.
2.3 Java Config配置IOC容器
  • 摆脱XML的束缚,使用独立Java类管理对象与依赖
  • 注解配置相对分散,Java Config可对配置集中管理
  • 编译时进行依赖检测
注解 说明
@Configuration描述类,说明当前类是Java Config配置类替代XML文件
@Bean描述方法返回对象将被IOC容器管理,BeanId默认方法名
@ImportResource描述类,加载静态文件,可用@Value注解获取
@ComponentScan描述类,同XML< context:component-scan base-package="com.mo"/>标签
3 AOP(Aspect Oriented Programming)面向切面编程
  • Spring AOP是使用AspectJWeaver实现类与方法匹配
  • Eclipse AspectJ是一种基于Java平台的面向切面编程的语言
  • Spring AOP利用代理模式实现对象运动时功能拓展

一些概念

名称 说明
Aspect切面,具体的可插拔组件功能类,通常一个切面只实现一个统用功能
Target Class/Method目标类,目标方法,指真正要执行与业务相关的方法
PointCut切入点,使用execution表达式说明切面要作用在系统的那些类上
JoinPoint连接点,切面运行过程中是包含了目标类/方法元数据的对象
Advice通知,说明具体的切面的执行时机,有5种类型
  • PointCut 切点表达式
*    -通配符
..   -包通配符
(..) -参数通配符

public void com.mo.spring.aop.service.UserService.createUser(形参1,形参2,..)

execution ( public * com.mo..*.*(..))

3.1 常用通知
注解 说明
Before Advice前置通知,目标方法运行前执行
After Returning Advice返回后通知,目标放返回数据后执行
After Throwing Advice异常通知,目标方法抛出异常后执行
After Advice后置通知,目标方法运行后执行
Around Advice最强大通知,自定义通知执行时机,可决定目标方法是否运行
  • 引介增强(IntroductionInterceptor)
    a.对类的增强,非方法
    b.允许在运行时为目标类增加新属性或方法
    c.允许在运行时改变类的行为,让类随运行环境动态变更
3.2 xml配置AOP
<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="userDao" class="com.mo.spring.aop.dao.UserDao"/>
    <bean id="employeeDao" class="com.mo.spring.aop.dao.EmployeeDao"/>
    <bean id="userService" class="com.mo.spring.aop.service.UserService">
        <property name="userDao" ref="userDao"/>
    </bean>
    <bean id="employeeService" class="com.mo.spring.aop.service.EmployeeService">
        <property name="employeeDao" ref="employeeDao"/>
    </bean>

    <!--AOP配置-->
    <bean id="methodAspect" class="com.mo.spring.aop.aspect.MethodAspect"/>

    <aop:config>
        <!--pointcut 切点,使用execution表达式描述切面的作用范围-->
        <!--execution(public * com.mo..*.*(..)) 说明作用在com.mo包下的所有类的所有方法-->
   <!--<aop:pointcut id="pointcut" expression="execution(public * com.mo..*.*(..))"/>-->
        <aop:pointcut id="pointcut" expression="execution(public *  com.mo..*Service.*(..))"/>
        <!--定义切面类-->
        <aop:aspect ref="methodAspect">
            <!--before前置通知,代表在目标方法运行前先执行methodAspect.printExecutionTime()-->
            <aop:before method="printExecutionTime" pointcut-ref="pointcut"/>
            <!--后置通知-->
            <aop:after method="doAfter" pointcut-ref="pointcut"/>
            <!--返回后通知-->
            <aop:after-returning method="doAfterReturning" returning="object" pointcut-ref="pointcut"/>
           	<!--异常通知-->
            <aop:after-throwing method="doAfterThrowing" throwing="throwable" pointcut-ref="pointcut"/>
             <!--环绕通知-->
            <aop:around method="check" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>
</beans>
3.3 注解配置AOP
3.3.1 配置切面类和通知
@Component //标记当前类为组件
@Aspect    //说明当前类是切面类
public class MethodChecker {
    //环绕通知 参数PointCut切点表达式
    @Around("execution(* com.mo..*Service.*(..))")
    public Object check(ProceedingJoinPoint joinPoint) throws Throwable {

        try {
            long startTime = new Date().getTime();
            Object ret = joinPoint.proceed();//执行目标方法
            long endTime = new Date().getTime();
            long duration = endTime - startTime;
            if (duration >= 1000) {
                String className = joinPoint.getTarget().getClass().getName();
                String methodName = joinPoint.getSignature().getName();
                SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
                String time = dateFormat.format(new Date());
                System.out.println("====" + time + "className:" + className + "." + methodName + "duration:" + duration);
            }
            return ret;
        } catch (Throwable throwable) {
            // throwable.printStackTrace();
            System.out.println("Exception message:" + throwable.getMessage());
            throw throwable;
        }
    }
}
3.3.2 applicationContext
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"
>
    <!--初始化IOC容器 自动扫描这个包下的@Component,为其创建bean -->
    <context:component-scan base-package="com.mo"/>
    <!--启用Spring AOP注解模式-->
    <aop:aspectj-autoproxy/>
</beans>
3.3.3 使用
public class SpringApplication {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        UserService userService = context.getBean("userService", UserService.class);
        userService.createUser();
        /*
        ====环绕前
        执行创建用户业务逻辑
        新增用户数据
        ====2022-02-17 10:19:10 387className:com.mo.spring.aop.service.UserService.createUserduration:3011
        ====环绕环后
        */
    }
}
4 Spring JDBC与事务管理
4.1 spring jdbc 配置与jdbcTemplate使用
  • applicationContext.xml
<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>
    <!--数据源配置-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai&amp;PublicKeyRetrieval=true"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>
    <!--jdbcTemplate 提供增删改查api-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <bean id="employeeDao" class="com.mo.spring.jdbc.dao.EmployeeDao">
        <!--Dao注入jdbcTemplate对象-->
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>
</beans>

jdbcTemplate 增删改查

public class EmployeeDao {
    private JdbcTemplate jdbcTemplate;


    public Employee findById(Integer eno) {
        String sql = "select * from employee where eno = ?";
        //查询单条属性
        Employee employee = jdbcTemplate.queryForObject(sql, new Object[]{eno}, new BeanPropertyRowMapper<>(Employee.class));

        return employee;
    }

    public List<Employee> findByDname(String dname) {
        String sql = "select * from employee where dname=?";
        //查询复合数据
        List<Employee> employeeList = jdbcTemplate.query(sql, new Object[]{dname}, new BeanPropertyRowMapper<>(Employee.class));
        return employeeList;

    }

    public List<Map<String, Object>> findMapByDname(String dname) {
        String sql = "select eno as empno,salary as sa from employee where dname=?";
        //查询结果作为Map进行封装
        List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql, new Object[]{dname});
        return maps;
    }


    public void insert(Employee employee) {
        String sql = "insert into employee(eno,ename,salary,dname,hiredate) values(?,?,?,?,?)";
        jdbcTemplate.update(sql, new Object[]{employee.getEno(), employee.getEname(), employee.getSalary(), employee.getDname(), employee.getHiredate()});
    }

    public int update(Employee employee) {
        String sql = "UPDATE employee SET ename = ?,salary =?,dname=?,hiredate=? WHERE eno=?";
        int count = jdbcTemplate.update(sql, new Object[]{employee.getEname(), employee.getSalary(), employee.getDname(), employee.getHiredate(), employee.getEno()});
        return count;
    }

    public int delete(Integer eno){
        String sql = "DELETE FROM employee WHERE eno=?";
        int count = jdbcTemplate.update(sql, new Object[]{eno});
        return count;
    }
    public JdbcTemplate getJdbcTemplate() {
        return jdbcTemplate;
    }

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

}
4.2 编程式事务
  • 事务是以一种可靠的,一致的方式,访问和操作数据库的程序单元
  • 事务依赖于数据库实现,MYSQL通过事务区作为数据缓冲地带
  • 编程事务是指通过代码手动提交回滚事务的事务控制方法
  • SpringJDBC通过TransactionManager事务管理器实现事务控制
  • 事务管理器提供commit/rollback方法进行事务提交与回滚

xml 配置

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

    <context:annotation-config/>

    <!--数据源配置-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai&amp;PublicKeyRetrieval=true"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>

    <!--jdbcTemplate 提供增删改查api-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <bean id="employeeDao" class="com.mo.spring.jdbc.dao.EmployeeDao">
        <!--为Dao注入jdbcTemplate对象-->
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>

    <bean id="employeeService" class="com.mo.spring.jdbc.service.EmployeeService">
        <property name="employeeDao" ref="employeeDao"/>
        <property name="transactionManager" ref="transactionManager"/>
    </bean>

    <!--事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

</beans>

使用

public void batchImport() {
        //定义事务的默认标准模式
        DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
        //开始一个事务,返回事务状态,事务状态说明当前事务的执行阶段
        TransactionStatus transaction = transactionManager.getTransaction(definition);
        try {
            for (int i = 1; i <=5; i++) {
                if (i==3){
                    throw new RuntimeException("测试异常");
                }
                Employee employee = new Employee();
                employee.setEno(10+i);
                employee.setEname("员工"+i);
                employee.setSalary(3000f);
                employee.setDname("测试部");
                employee.setHiredate(new Date());
                employeeDao.insert(employee);
            }
            //提交事务
            transactionManager.commit(transaction);
        } catch (Exception e) {
            //e.printStackTrace();
            //回滚
            transactionManager.rollback(transaction);
            //抛出或者实际情况处理
            throw e;
        }
4.3 声明式事务
  • 不修改源码的情况下通过配置形式自动实现事务的控制,声明式事务本质就是AOP环绕通知
  • 当目标方法执行成功时,自动提交事务
  • 当目标方法抛出异常时,自动事务回滚

配置过程
1.配置TransactionManager事务管理器
2.配置事通知与事务属性
3.为事务通知绑定PointCut切点

xml配置

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

    <context:annotation-config/>

    <!--数据源配置-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url"
                  value="jdbc:mysql://localhost:3306/test?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai&amp;PublicKeyRetrieval=true"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>

    <!--jdbcTemplate 提供增删改查api-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <bean id="employeeDao" class="com.mo.spring.jdbc.dao.EmployeeDao">
        <!--为Dao注入jdbcTemplate对象-->
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>

    <bean id="employeeService" class="com.mo.spring.jdbc.service.EmployeeService">
        <property name="employeeDao" ref="employeeDao"/>
        <property name="transactionManager" ref="transactionManager"/>
    </bean>

    <!--1.事务管理器,用于事务创建/提交/回滚-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--2.事务通知配置,配置那些方法使用事务-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager" >
        <tx:attributes>
            <!--目标方法名为batchImport时,启用声明式事务,运行成功提交,异常回滚-->
            <tx:method name="batchImport" propagation="REQUIRED"/>
            <!--以batch开头的方法都启用事务-->
            <tx:method name="batch*" propagation="REQUIRED"/>
            <!--设置findXX方法不需要使用事务-->
            <tx:method name="find*" propagation="NOT_SUPPORTED" read-only="true"/>
            <tx:method name="get*" propagation="NOT_SUPPORTED" read-only="true"/>

            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>
    <!--定义声明式事务的作用范围-->
    <aop:config >
        <aop:pointcut id="pointcut" expression="execution(* com.mo..*Service.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/>
    </aop:config>
</beans>
4.4 事务传播方式
事务传播类型 说明
PROPAGATION_REQUIRED(默认)如果当前没有事务,就新建一个事务,如果已经存在一个事务中,就加入到这个事务中
PROPAGATION_MANDATORY使用当前事务,如果没有事务,就抛出异常
PROPAGATION_SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行
PROPAGATION_REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起
PROPAGATION_NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
PROPAGATION_NEVER以非事务方式执行,如果当前存在事务,就抛出异常
PROPAGATION_NESTED如果当前存在事务,则在嵌套事务内执行.如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作
学习中...(2/2)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值