Spring框架学习笔记

hexo博客地址

Spring框架学习

学习视频链接

spring version:5.2.6

Spring的简要概述

  1. Spring 是轻量级的开源的 JavaEE 框架

  2. Spring 有两个核心部分:IOC 和 AOP

    (1)IOC:控制反转,把创建对象过程交给 Spring 进行管理

    (2)AOP:面向切面,不修改源代码进行功能增强

  3. Spring的下载地址:https://repo.spring.io/release/org/springframework/spring/

  4. Spring的运行需要第三方的日志包依赖(common-logging.jar,下载地址:http://commons.apache.org/proper/commons-logging/download_logging.cgi),maven依赖

    <dependency>
    	<groupId>commons-logging</groupId>
    	<artifactId>commons-logging</artifactId>
    	<version>1.2</version>
    </dependency>
    
  5. Spring模块
    在这里插入图片描述

  6. SpringIOC所需的基本包为日志包以及Spring模块图中的Core Container中的jar包

Spring的重要组成部分

** IOC

概念与原理

  1. 什么是 IOC

    (1)控制反转,把对象创建和对象之间的调用过程,交给 Spring 进行管理

    (2)使用 IOC 目的:为了耦合度降低

    (3)做入门案例就是 IOC 实现

  2. IOC 底层原理:xml 解析、工厂模式、反射

BeanFactory接口

  1. IOC 思想基于 IOC 容器完成,IOC 容器底层就是对象工厂

  2. Spring 提供 IOC 容器实现两种方式:(两个接口)

    (1)BeanFactory:IOC 容器基本实现,是 Spring 内部的使用接口,不提供开发人员进行使用

    加载配置文件时候不会创建对象,在获取对象(使用)才去创建对象

    (2)ApplicationContext:BeanFactory 接口的子接口,提供更多更强大的功能,一般由开发人员进行使用

    加载配置文件时候就会把在配置文件对象进行创建

  3. ApplicationContext 接口的两个常用实现类

    /**
    FileSystemXmlApplicationContext(String path), 默认系统路径
    ClassPathXmlApplicationContext,默认工程路径下
    **/
    ApplicationContext context1 = new ClassPathXmlApplicationContext("beans/beans01.xml");
    ApplicationContext context2 = new FileSystemXmlApplicationContext("E:\\spring\\spring5\\src\\beans\\beans01.xml")
    

Bean管理

  1. 概念:Bean管理指的是两个操作,Spring创建对象与Spring注入属性。
  2. 操作方式:(1)基于 xml 配置文件方式实现。(2)基于注解方式实现。

基于xml方式的Bean管理

User类

package com.hyn.spring5;

public class User {
    private String username;
    private String password;
    public void add() {
        System.out.println("add.......");
    }

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public User() {
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

xml

<bean id="user" class="com.hyn.spring5.User" />
  1. bean管理默认使用无参构造方法创建对象

  2. id 属性:唯一标识

    class属性:类全路径(包类路径)

  3. DI:依赖注入(即注入属性)
    3.1 property标签使用的是set方法注入属性

      <bean id="user" class="com.hyn.spring5.User" >
              <property name="username" value="admin"></property>
              <property name="password" value="admin"></property>
      </bean>
    

    3.2 改变bean管理默认的无参构造,使用有参构造方法,index属性指的是有参构造方法中的第几个属性(从0开始)

      <bean id="user" class="com.hyn.spring5.User" >
              <constructor-arg name="username" value="admin"></constructor-arg>
              <constructor-arg name="password" value="admin"></constructor-arg>
      <!--        <constructor-arg index="0" value="admin"></constructor-arg>-->
      <!--        <constructor-arg index="1" value="admin"></constructor-arg>-->
      </bean>
    
  4. p名称空间注入(了解)

    <!--       xmlns:p="http://www.springframework.org/schema/p"添加约束 -->
    <bean id="user" class="com.hyn.spring5.User" p:username="admin" p:password="admin">
    </bean>
    
  5. 字面量

    5.1 null值

    <property name="password"> <null></null> </property>
    

    5.2 xml方式的特殊字符处理

     <!--
         <: &lt
         >: &gt
         CDATA域:<![CDATA[内容]]>
     -->
     <property name="username" > <value><![CDATA[<<>>]]]> </value> </property>
     ```
    
    
  6. <bean id="userService" class="com.hyn.spring5.service.UserService">
     <!--注入 userDao 对象
     name 属性:类里面属性名称
     ref 属性:创建 userDao 对象 bean 标签 id 值
     -->
     <property name="userDao" ref="userDaoImpl"></property>
    </bean> <bean id="userDaoImpl" class="com.hyn.spring5.dao.UserDaoImpl"></bean>
    
  7. xml自动装配

    <!--实现自动装配
    类Emp中有属性Dept
    
     bean 标签属性 autowire,配置自动装配
     autowire 属性常用两个值:
     	byName 根据属性名称注入 ,注入值 bean 的 id 值和类属性名称一样
    	byType 根据属性类型注入
    -->
    <!--
    -----byName
    <bean id="emp" class="com.atguigu.spring5.autowire.Emp" autowire="byName">
     <!--<property name="dept" ref="dept"></property>-->
    </bean> 
    -->>
    <bean id="dept" class="com.atguigu.spring5.autowire.Dept"></bean>
    <bean id="emp" class="com.atguigu.spring5.autowire.Emp" autowire="byType">
     <!--<property name="dept" ref="dept"></property>-->
    </bean> 
    <bean id="dept" class="com.atguigu.spring5.autowire.Dept"></bean>
    

xml注入集合属性

1、注入数组类型属性
2、注入 List 集合类型属性
3、注入 Map 集合类型属性

创建类,定义数组、list、map、set 类型属性,生成对应 set 方法

Stu类

public class Stu {
    private String[] courses;
    private List<String> list;
    private Map<String, String> maps;
    private Set<String> sets;
    private List<Course> courseList;

    @Override
    public String toString() {
        return "Stu{" +
                "courses=" + Arrays.toString(courses) +
                ", list=" + list +
                ", maps=" + maps +
                ", sets=" + sets +
                ", courseList=" + courseList +
                '}';
    }

    public void setCourses(String[] courses) {
        this.courses = courses;
    }

    public void setList(List<String> list) {
        this.list = list;
    }

    public void setMaps(Map<String, String> maps) {
        this.maps = maps;
    }

    public void setSets(Set<String> sets) {
        this.sets = sets;
    }

    public void setCourseList(List<Course> courseList) {
        this.courseList = courseList;
    }
}
<bean id="course1" class="com.hyn.spring5.collectiontype.Course"></bean>
    <bean id="course2" class="com.hyn.spring5.collectiontype.Course"></bean>
    <bean id="stu" class="com.hyn.spring5.collectiontype.Stu">
        <!--数组类型属性注入-->
        <property name="courses">
            <array>
                <value>java课程</value>
                <value>数据库课程</value>
            </array>
        </property>
        <!--list 类型属性注入-->
        <property name="list">
            <list>
                <value>张三</value>
                <value>李四</value>
            </list>
        </property>
        <!--map类型属性注入-->
        <property name="maps">
            <map>
                <entry key="JAVA" value="java"></entry>
                <entry key="CPP " value="cpp"></entry>
            </map>
        </property>
        <!--set类型属性注入-->
        <property name="sets">
            <set>
                <value>idea</value>
                <value>eclipse</value>
            </set>
        </property>
        <!--list类型对象属性注入-->
        <property name="courseList">
            <list>
                <ref bean="course1"></ref>
                <ref bean="course2"></ref>
            </list>
        </property>
</bean>

测试代码

public void testCollectiontype() {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans/beans02.xml");
        Stu stu = context.getBean("stu", Stu.class);
        System.out.println(stu);
}

使用util标签将集合注入部分提取出来

<!--(1)在 spring 配置文件中引入名称空间 util-->
<?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:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/util 
        http://www.springframework.org/schema/util/spring-util.xsd">
<!--    (2)使用 util 标签完成 list 集合注入提取-->
    <!--1 提取 list 集合类型属性注入--> 
    <util:list id="bookList">
        <value>易筋经</value>
        <value>九阴真经</value>
        <value>九阳神功</value>
    </util:list>
    <!--2 提取 list 集合类型属性注入使用--> <bean id="stu" class="com.hyn.spring5.collectiontype.Stu">
        <property name="list" ref="bookList"></property>
    </bean>
</beans>

工厂bean(FactoryBean)

  1. Spring有两种类型的bean,一种普通bean,另一种为工厂bean(FactoryBean)
  2. 普通bean:在配置文件中定义的bean类型就是返回类型
  3. 工厂bean:在配置文件中定义的bean类型可以喝返回类型不一样
    3.1 创建类,让这个类作为工厂bean,实现接口FactoryBean
    3.2 实现接口里面的方法,在实现的方法中定义返回的bean类型
public class MyBean implements FactoryBean<Course> {
    //定义返回 bean
    @Override
    public Course getObject() throws Exception {
        Course course = new Course();
        course.setCname("abc");
        return course;
    }
    @Override
    public Class<?> getObjectType() {
        return null;
    }
    @Override
    public boolean isSingleton() {
        return false;
    }
}
<bean id="myBean" class="com.atguigu.spring5.factorybean.MyBean"></bean>
@Test
public void test3() {
    ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
    Course course = context.getBean("myBean", Course.class);
    System.out.println(course);
}

bean的作用域

在Spring中,默认情况下bean是单实例对象

通过scope属性设置bean为单实例还是多实例

(1)在 spring 配置文件 bean 标签里面有属性(scope)用于设置单实例还是多实例

(2)scope 属性值

第一个值 默认值,singleton,表示是单实例对象
第二个值 prototype,表示是多实例对象

(3)singleton 和 prototype 区别

第一 singleton 单实例,prototype 多实例
第二 设置 scope 值是 singleton 时,加载 spring 配置文件时候就会创建单实例对象.

设置 scope 值是 prototype 时,不是在加载 spring 配置文件时候创建 对象,在调用getBean 方法时候创建多实例对象

bean的生命周期

  1. 生命周期 :从对象创建到对象销毁的过程

  2. bean 生命周期

    (1)通过构造器创建 bean 实例(无参数构造)

    (2)为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)

    (3)把 bean 实例传递 bean 后置处理器的方法 postProcessBeforeInitialization

    (4)调用 bean 的初始化的方法(需要进行配置初始化的方法)

    (5)把 bean 实例传递 bean 后置处理器的方法 postProcessAfterInitialization

    (6)bean 可以使用了(对象获取到了)

    (7)当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)

3、演示 bean 生命周期 :

// 创建类,实现接口 BeanPostProcessor,创建后置处理器
public class MyBeanPost implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {
        System.out.println("在初始化之前执行的方法");
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName)
            throws BeansException {
        System.out.println("在初始化之后执行的方法");
        return bean;
    } 
}
// bean
public class Orders {
    //无参数构造
    public Orders() {
        System.out.println("第一步 执行无参数构造创建 bean 实例");
    }
    private String oname;
    public void setOname(String oname) {
        this.oname = oname;
        System.out.println("第二步 调用 set 方法设置属性值");
    }
    //创建执行的初始化的方法
    public void initMethod() {
        System.out.println("第三步 执行初始化的方法");
    }
    //创建执行的销毁的方法
    public void destroyMethod() {
        System.out.println("第五步 执行销毁的方法");
    } 
}

xml

<bean id="orders" class="com.atguigu.spring5.bean.Orders" initmethod="initMethod" destroy-method="destroyMethod">
 <property name="oname" value="手机"></property>
</bean>
<bean id="myBeanPost" class="com.atguigu.spring5.bean.MyBeanPost"></bean>

外部配置文件(properties文件)

配置数据库信息

  1. 引入druid包

  2. 创建外部属性文件(properties格式文件)

    # properties文件编写格式:键值对形式 key = value, key可以随意写
    prop.dirverClass=com.mysql.jdbc.Driver
    prop.url=jdbc:mysql://localhost:3306/hyn
    prop.username=root
    prop.password=root
    
  3. jdbc.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <!--
    引入context名称空间
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation值:
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd
    -->
    <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">
        <!--引入外部属性文件-->
        <context:property-placeholder location="classpath:jdbc.properties"/>
        <!--配置连接池-->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <!--${}spring中的表达式-->
            <property name="driverClassName" value="${prop.dirverClass}"></property>
            <property name="url" value="${prop.url}"></property>
            <property name="username" value="${prop.username}"></property>
            <property name="password" value="${prop.password}"></property>
        </bean>
    </beans>
    

注解引入

  1. 什么是注解
    (1)注解是代码特殊标记,格式:@注解名称(属性名称=属性值, 属性名称=属性值…)
    (2)使用注解,注解作用在类上面,方法上面,属性上面
    (3)使用注解目的:简化 xml 配置
  2. Spring 针对 Bean 管理中创建对象提供注解
    (1)@Component
    (2)@Service (表service层)
    (3)@Controller (表web层)
    (4)@Repository (表dao层)
  • 上面四个注解功能是一样的,都可以用来创建 bean 实例。
  1. 引入依赖,并开启组件扫描

    • 依赖包:spring-aop

    • 开启组件扫描

      <!--
      引入context名称空间
      xmlns:context="http://www.springframework.org/schema/context"
      xsi:schemaLocation值:
      http://www.springframework.org/schema/context
      http://www.springframework.org/schema/context/spring-context.xsd
      -->
      <!--扫描com.atguigu包下所有的类与接口-->
      <context:component-scan base-package="com.atguigu"></context:component-scan>
      
      <!--细节配置-->
      <!--示例 1
       use-default-filters="false" 表示现在不使用默认 filter,自己配置 filter
       context:include-filter ,设置扫描哪些内容
      --><context:component-scan base-package="com.atguigu" use-defaultfilters="false">
       <context:include-filter type="annotation" 
       
      expression="org.springframework.stereotype.Controller"/>
      </context:component-scan>
      <!--示例 2
       下面配置扫描包所有内容
       context:exclude-filter: 设置哪些内容不进行扫描
      --><context:component-scan base-package="com.atguigu">
       <context:exclude-filter type="annotation" 
       
      expression="org.springframework.stereotype.Controller"/>
      </context:component-scan>
      
  2. 使用注解

    //在注解里面 value 属性值可以省略不写,
    //默认值是类名称,首字母小写
    //UserService -- userService
    @Component(value = "userService") //<bean id="userService" class=".."/>
    public class UserService {
     public void add() {
     System.out.println("service add.......");
     } 
    }
    
  3. 基于注解的属性注入(均不需要set方法)

    (1)@Autowired:根据属性类型进行自动装配

    (2)@Qualifier:根据名称进行注入

    (3)@Resource:可以根据类型注入,可以根据名称注入(不属于spring,是javax下的注解

    (4)@Value:注入普通类型属性

    (5)使用:

    	@Autowired // 根据类型注入
    	private User user;
        /*
        @Autowired
        @Qualifier(value = "user") // 根据名称注入
        private User user;
        @Resource // 根据类型注入
        private User user;
        @Resource(name = "user") // 根据名称注入
        private User user;
        @Value(value = "username") // 注入普通属性值
        private String name;
        */*
    
  4. 完全注解开发

    //1. 创建配置类,替代 xml 配置文件
    @Configuration
    @ComponentScan(basePackages = "com.hyn.spring5")
    public class SpringConfig {
    }
    
    // 加载配置类,开启扫描
    ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
    

** AOP

概念

面向切面编程(方面),利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

  • 通俗描述:不通过修改源代码的方式,在主干功能里面添加新功能

底层原理(jdk动态代理与cglib动态代理)

AOP的底层采用动态代理,有两种情况。

  1. 有接口,使用jdk动态代理
  2. 无接口使用cglib动态代理
jdk动态代理(有接口)

使用Proxy(java.lang.reflect.Proxy)类里面的方法创建对象。

方法:

/**
ClassLoader loader, 类加载器
Class<?>[] interfaces, 代理类实现的接口列表 
InvocationHandler h, 调度方法调用的调用处理函数,需要手动编写
**/
public static Object newProxyInstance(ClassLoader loader, // 类加载器
                                          Class<?>[] interfaces, // 代理类实现的接口列表 
                                          InvocationHandler h) // 调度方法调用的调用处理函数

返回的类是一个实现所有interfaces的代理类($Proxy,这个类会由该方法动态编写)。假设实现类是Demo,那么我们得到的代理类强转为Demo类型(Demo与代理类同层次),只能向上转型。且&Proxy是在内存中的,我们看不到它。

若想查看&Proxy的源码,则需要在测试类的mian方法中测试(不能使用@Test单元测试),在newProxyInstance方法前,加上一行代码。

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");//jdk1.8

然后idea的工程目录下就会多出一个com文件夹,找到其中的.class文件,打开即可(文件需要反编译,idea会自动帮我们完成,但用记事本打开会是乱码)。

匿名内部类实现jdk代理

JDKProxy类

package com.hyn.spring.aop;

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

public class JDKProxy {
    public static UserDao createProxyUseDao(UserDaoImpl userDao) {
        UserDao proxyUserDao = (UserDao) Proxy.newProxyInstance(
                JDKProxy.class.getClassLoader(),
                userDao.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("方法执行前:");
                        Object result = method.invoke(userDao, args);
                        System.out.println("方法执行后:");
                        return result;
                    }
        });
        return proxyUserDao;
    }
}

Test

public static void main(String[] args) {  
	System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
    UserDao userDao = JDKProxy.createProxyUseDao(new UserDaoImpl())
    userDao.add();
    userDao.update();
}

$Proxy0类

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.sun.proxy;

import com.hyn.spring.aop.UserDao;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements UserDao {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;
    private static Method m4;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void add() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void update() throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.hyn.spring.aop.UserDao").getMethod("add");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m4 = Class.forName("com.hyn.spring.aop.UserDao").getMethod("update");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}
cglib动态代理

假设需要增强的类是UserService,cglib需要将其作为父类,然后创建它的子类作为代理类。

package com.hyn.spring.aop;

import org.springframework.cglib.core.DebuggingClassWriter;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class UserService {
    public void add() {
        System.out.println("UserService: add....");
    }
    public void update() {
        System.out.println("UserService: update....");
    }
}
class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("执行方法前。");
        methodProxy.invokeSuper(o, objects);
        System.out.println("执行方法后。");
        return null;
    }
}
class CglibProxy{

    public static UserService createUserService(UserService userService) {
        // 代理类class文件存入本地磁盘方便我们反编译查看源码
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E:\\cglib\\code");
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserService.class); // 设置代理类的父类
        enhancer.setCallback(new MyMethodInterceptor()); // 设置回调对象
        return (UserService) enhancer.create(); // 创建代理类
    }
}

testCglib

@Test
public void testCglib() {
    UserService userService = CglibProxy.createUserService(new UserService());
    userService.add();
    userService.update();
}

AOP术语

  1. 连接点:类里面可以被增强的方法
  2. 切入点:真正增强的方法
  3. 通知(增强)
    • 实际增强的逻辑部分称为通知(增强)
    • 通知有多种类型
      • 前置通知
      • 后置通知
      • 环绕通知
      • 异常通知
      • 最终通知
  4. 切面:是动作,将通知应用到切入点的过程

AOP操作

  1. Spring框架一般都是基于AspectJ实现AOP操作,但AspectJ不是Spring的组成部分,独立AOP框架,一般把AspectJ和Spring框架一起使用。

    所需jar包:

在这里插入图片描述

  1. 基于AspectJ实现AOP操作

    (1) 基于xml配置文件实现

    (2) 基于注解方式实现

  2. 切入点表达式

    (1)切入点表达式作用:知道对哪个类里面的哪个方法进行增强
    (2)语法结构: execution([权限修饰符] [返回类型] [类全路径] [方法名称]([参数列表]) )
    	举例 1:对 com.atguigu.dao.BookDao 类里面的 add 进行增强
    	execution(* com.atguigu.dao.BookDao.add(..))
    	举例 2:对 com.atguigu.dao.BookDao 类里面的所有的方法进行增强
    	execution(* com.atguigu.dao.BookDao.* (..))
    	举例 3:对 com.atguigu.dao 包里面所有类,类里面所有方法进行增强
    	execution(* com.atguigu.dao.*.* (..))
    
实例
package com.hyn.spring.aopanno;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Aspect // 生成代理对象
public class UserProxy {
    // 切入点
    @Pointcut(value = "execution(* com.hyn.spring.aopanno.User.add(..))")
    public void myPoint() {}
    // 前置通知
    @Before(value = "execution(* com.hyn.spring.aopanno.User.add(..))")
    public void before() {
        System.out.println("前置通知");
    }
    // 后置通知(返回通知)
    @AfterReturning(value = "myPoint()")
    public void afterReturning() {
        System.out.println("后置通知");
    }
    // 最终通知
    @After(value = "myPoint()")
    public void after() {
        System.out.println("最终通知");
    }
    // 环绕通知
    @Around(value = "myPoint()")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕前");
        proceedingJoinPoint.proceed();
        System.out.println("环绕后");
    }
    // 抛出异常通知
    @AfterThrowing(value = "myPoint()")
    public void afterThrowing() {
        System.out.println("异常通知");
    }
}
package com.hyn.spring.aopanno;

import org.springframework.stereotype.Component;

@Component(value = "user")
public class User {
    public void add() {
        System.out.println("User: add...");
    }
    public void update() {
        System.out.println("User: update...");
    }
}

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: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">
    <!--开启注解扫描-->
    <context:component-scan base-package="com.hyn.spring.aopanno"/>
    <!--开启Aspect生成代理对象-->
    <aop:aspectj-autoproxy proxy-target-class="false"></aop:aspectj-autoproxy>
    <!--proxy-target-class:
    false, 有接口则jdk代理,否则便用cglib。(为默认值)
    true, 全使用cglib
    -->
</beans>

替换该配置文件的配置类

package com.hyn.spring.aopanno;


import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;

@Configuration // 配置类
@ComponentScan(basePackages = "com.hyn.spring.aopanno") // 注解扫描位置
@EnableAspectJAutoProxy(proxyTargetClass = false) // Aspect生成代理对象
public class ConfigAOP {
}

Test

@Test
public void aopTest() {
    AbstractApplicationContext context = new ClassPathXmlApplicationContext("aop.xml");
    User user = context.getBean("user", User.class);
    user.add();
    user.update();
}

AOP的纯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: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 https://www.springframework.org/schema/aop/spring-aop.xsd">
    <bean id="user" class="com.hyn.spring.aopanno.User"></bean>
    <bean id="userProxy" class="com.hyn.spring.aopanno.UserProxy"></bean>
    <aop:config>
        <!--切入点-->
        <aop:pointcut id="myPoint" expression="execution(* com.hyn.spring.aopanno.User.add(..))"/>
        <!--配置切面-->
        <aop:aspect ref="userProxy">
            <aop:before method="before" pointcut-ref="myPoint"/>
            <aop:after-returning method="afterReturning" pointcut-ref="myPoint"/>
            <aop:after method="after" pointcut-ref="myPoint"/>
            <aop:around method="around" pointcut-ref="myPoint"/>
            <aop:after-throwing method="afterThrowing" pointcut-ref="myPoint"/>
        </aop:aspect>
    </aop:config>
</beans>
有多个增强类多同个方法进行增强,设置增强类优先级

在增强类上面添加注解 @Order(数字类型值),数字类型值越小优先级越高

@Component
@Aspect
@Order(1)
public class PersonProxy{
}

JdbcTemplate(不常用)

jar包

在这里插入图片描述

新增包:jdbc,oxm,tx,druid,mysql-connector-java

jdbcTemplate的常用方法

方法所属类:org.springframework.jdbc.core.JdbcTemplate

1、public int update(String sql, @Nullable Object... args)

参数:

  • sql:执行的sql语句,未知值用?代替
  • args:?的具体值

返回值:执行完sql语句,表受影响的行数

2、public <T> T queryForObject(String sql, RowMapper<T> rowMapper, @Nullable Object... args)

参数:

  • sql:执行的sql语句,未知值用?代替
  • rowMapper:查询结果需返回的类型(不可为基本数据类型)。(若返回Integer。写法:new BeanPropertyRowMapper(Integer.class))
  • args:?的具体值

返回值:查询结果

3、public <T> List<T> query(String sql, RowMapper<T> rowMapper)

  • sql:执行的sql语句,未知值用?代替
  • rowMapper:查询结果需返回的类型(不可为基本数据类型)。(若返回Integer。写法:new BeanPropertyRowMapper(Integer.class))
  • args:?的具体值

返回值:查询结果,并以list的形式返回

4、public int[] batchUpdate(String sql, List<Object[]> batchArgs)

批量操作,一个sql语句执行多次。(内部实现也就是for循环)

参数:

  • sql:执行的sql语句,未知值用?代替
  • batchArgs:第一方法中args的list

返回值:每执行完一条sql语句,表受影响的行数。以数组形式返回

示例

jdbc.properties(注意驱动包与数据库版本的冲突问题)

prop.dirverClass=com.mysql.jdbc.Driver
prop.url=jdbc:mysql://localhost:3306/user_db?useUnicode=true&characterEncoding=utf-8&userSSL=false&serverTimezone=GMT%2B8
prop.username=root
prop.password=root

jdbcBeans.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:aop="http://www.springframework.org/schema/aop"
       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/aop http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
">
    <context:component-scan base-package="com.hyn.spring.jdbctemplate"></context:component-scan>

    <context:property-placeholder location="classpath:jdbc.properties"/>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <!--${}spring中的表达式-->
        <property name="driverClassName" value="${prop.dirverClass}"></property>
        <property name="url" value="${prop.url}"></property>
        <property name="username" value="${prop.username}"></property>
        <property name="password" value="${prop.password}"></property>
    </bean>
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/> 
    </bean>
</beans>

新建数据库与表

在这里插入图片描述

新建User,并完成get,set,toString, 有参构造,无参构造等方法的编写。(略)

新建UserDao,UserService两个接口,编写相应方法。并编写其实现类。

  • UserDao
package com.hyn.spring.jdbctemplate.dao;

import com.hyn.spring.jdbctemplate.bean.User;

import java.util.List;

public interface UserDao {
    public int add(User user);
    public int delete(User user);
    public int update(User user);
    public List<User> findAllUsers();
    public User findUserById(int id);
    public int selectUserCount();
    public int[] batchAddUser(List<Object[]> batchArgs);
    public int[] batchDeleteUser(List<Object[]> batchArgs);
    public int[] batchUpdateUser(List<Object[]> batchArgs);
}
  • UserDaoImpl
package com.hyn.spring.jdbctemplate.dao;

import com.hyn.spring.jdbctemplate.bean.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public class UserDaoImpl implements UserDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public int add(User user) {
        String sql = "INSERT INTO t_user (`id`, `username`, `status`) VALUES (?, ?, ?)";
        return jdbcTemplate.update(sql, user.getId(), user.getUsername(), user.getStatus());
    }

    @Override
    public int delete(User user) {
        String sql = "DELETE FROM t_user WHERE `id` = ?";
        return jdbcTemplate.update(sql, user.getId());
    }

    @Override
    public int update(User user) {
        String sql = "UPDATE t_user SET `username` = ?, `status` = ? WHERE `id` = ?";
        return jdbcTemplate.update(sql, user.getUsername(), user.getStatus(), user.getId());
    }

    @Override
    public List<User> findAllUsers() {
        String sql = "SELECT * FROM t_user";
        return jdbcTemplate.query(sql, new BeanPropertyRowMapper<User>(User.class));
    }

    @Override
    public User findUserById(int id) {
        String sql = "SELECT * FROM t_user WHERE id = ?";
        return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), id);
    }

    @Override
    public int selectUserCount() {
        String sql = "SELECT COUNT(*) FROM t_user";
        return jdbcTemplate.queryForObject(sql, Integer.class).intValue();
    }

    @Override
    public int[] batchAddUser(List<Object[]> batchArgs) {
        String sql = "INSERT INTO t_user (`id`, `username`, `status`) VALUES (?, ?, ?)";
        return jdbcTemplate.batchUpdate(sql, batchArgs);

    }

    @Override
    public int[] batchDeleteUser(List<Object[]> batchArgs) {
        String sql = "DELETE FROM t_user WHERE `id` = ?";
        return jdbcTemplate.batchUpdate(sql, batchArgs);
    }

    @Override
    public int[] batchUpdateUser(List<Object[]> batchArgs) {
        String sql = "UPDATE t_user SET `username` = ?, `status` = ? WHERE `id` = ?";
        return jdbcTemplate.batchUpdate(sql, batchArgs);
    }
}
  • UserService
package com.hyn.spring.jdbctemplate.service;

import com.hyn.spring.jdbctemplate.bean.User;

import java.util.List;

public interface UserService {
    public void add(User user);
    public void delete(User user);
    public void update(User user);
    public void findAllUsers();
    public void findUserById(int id);
    public void selectUserCount();
    public void batchAddUser(List<Object[]> batchArgs);
    public void batchDeleteUser(List<Object[]> batchArgs);
    public void batchUpdateUser(List<Object[]> batchArgs);
}
  • UserServiceImpl
package com.hyn.spring.jdbctemplate.service;

import com.hyn.spring.jdbctemplate.bean.User;
import com.hyn.spring.jdbctemplate.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.List;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

    @Override
    public void add(User user) {
        System.out.println(userDao.add(user));
    }

    @Override
    public void delete(User user) {
        System.out.println(userDao.delete(user));
    }

    @Override
    public void update(User user) {
        System.out.println(userDao.update(user));
    }

    @Override
    public void findAllUsers() {
        System.out.println(userDao.findAllUsers());
    }

    @Override
    public void findUserById(int id) {
        System.out.println(userDao.findUserById(id));
    }

    @Override
    public void selectUserCount() {
        System.out.println(userDao.selectUserCount());
    }

    @Override
    public void batchAddUser(List<Object[]> batchArgs) {
        System.out.println(Arrays.toString(userDao.batchAddUser(batchArgs)));
    }

    @Override
    public void batchDeleteUser(List<Object[]> batchArgs) {
        System.out.println(Arrays.toString(userDao.batchDeleteUser(batchArgs)));
    }

    @Override
    public void batchUpdateUser(List<Object[]> batchArgs) {
        System.out.println(Arrays.toString(userDao.batchUpdateUser(batchArgs)));
    }
}

Test类

package com.hyn.spring.jdbctemplate.Test;

import com.hyn.spring.jdbctemplate.bean.User;
import com.hyn.spring.jdbctemplate.service.UserService;
import com.hyn.spring.jdbctemplate.service.UserServiceImpl;
import org.junit.Test;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.ArrayList;
import java.util.List;

public class jdbcTest {
    @Test
    public void test() {
        AbstractApplicationContext context = new ClassPathXmlApplicationContext("jdbcBeans.xml");
//        User user = new User(2, "java", "1");
        UserService userService = context.getBean("userServiceImpl", UserServiceImpl.class);
//        userService.add(user);
//        userService.update(user);
//        userService.delete(user);
//        userService.findAllUsers();
//        System.out.println("--------");
//        userService.findUserById(1);
//        System.out.println("--------");
//        userService.selectUserCount();
        List<Object[]> batchArgs = new ArrayList<>();
        Object[] o1 = { "java", "1", "1"};
        Object[] o2 = { "cpp", "2", "2"};
        batchArgs.add(o1);
        batchArgs.add(o2);
        userService.batchUpdateUser(batchArgs);
    }
}

事务

1、事务添加到 JavaEE 三层结构里面 Service 层(业务逻辑层)
2、在 Spring 进行事务管理操作
(1)有两种方式:编程式事务管理和声明式事务管理(使用)

3、声明式事务管理
(1)基于注解方式(使用)
(2)基于 xml 配置文件方式
4、在 Spring 进行声明式事务管理,底层使用 AOP 原理
5、Spring 事务管理 API

  • 提供一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H6sYCIkb-1598374366372)(https://huiyinian.github.io/images/img0821.png)]

Spring与MyBatis框架使用DataSourceTransactionManager

示例说明

例子:用户之间的转账

数据库
USE user_db;
DROP TABLE IF EXISTS t_user;
CREATE TABLE t_user(
	`id` INT PRIMARY KEY auto_increment,
	`username` VARCHAR(50) NOT NULL,
	`money` INT
);
INSERT INTO t_user(`username`, `money`) VALUES('A', '2500');
INSERT INTO t_user(`username`, `money`) VALUES('B', '1500');
INSERT INTO t_user(`username`, `money`) VALUES('C', '2000');
INSERT INTO t_user(`username`, `money`) VALUES('D', '3000');

根据表编写相应的User类(略)

xml配置

另需tx约束

<?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"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
">
    <context:component-scan base-package="com.hyn.spring"></context:component-scan>

    <context:property-placeholder location="jdbc.properties"/>


    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <!--${}spring中的表达式-->
        <property name="driverClassName" value="${prop.dirverClass}"></property>
        <property name="url" value="${prop.url}"></property>
        <property name="username" value="${prop.username}"></property>
        <property name="password" value="${prop.password}"></property>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--创建事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--开启事务注解-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

properties文件略

dao层
package com.hyn.spring.dao;

import com.hyn.spring.bean.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.PlatformTransactionManager;

@Repository(value = "userDao")
public class UserDao {
    @Autowired
    @Qualifier(value = "jdbcTemplate")
    private JdbcTemplate jdbcTemplate;

    public User findUserById(int id) {
        String sql = "SELECT * FROM t_user WHERE `id` = ?";
        return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), id);
    }

    public int reduceMoney(User user) { // -100
        String sql = "UPDATE t_user SET `money` = ? - 100 WHERE `id` = ?";
        return jdbcTemplate.update(sql, user.getMoney(), user.getId());
    }
    public int addMoney(User user) { // +100
        String sql = "UPDATE t_user SET `money` = ? + 100 WHERE `id` = ?";
        return jdbcTemplate.update(sql, user.getMoney(), user.getId());
    }
}
Service层
package com.hyn.spring.service;

import com.hyn.spring.bean.User;
import com.hyn.spring.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service(value = "userService")
@Transactional // 为当前类添加声明式事务,也可写在具体的方法上
public class UserService {
    @Autowired
    @Qualifier(value = "userDao")
    private UserDao userDao;

    public User findUserById(int id) {
        return userDao.findUserById(id);
    }

    public void accountMoney(User A, User B) { // A -> B 转账,金额100
        userDao.reduceMoney(A);
        int i = 10 / 0; // 模拟错误
        userDao.addMoney(B);
    }
}
@Transactional 的参数

鼠标放其后面,Ctrl+P参看
在这里插入图片描述

红框内的为常用参数。

  1. propagation:事务传播行为。多事务方法直接进行调用,这个过程中事务 是如何进行管理的

    Spring所定义的7种传播行为:
    在这里插入图片描述

  2. isolation:事务隔离级别,用来解决脏读、不可重复读、虚(幻)读的问题

    默认值:Isolation.DEFAULT,使用所连接的数据库的默认隔离规则。

在这里插入图片描述

  1. timeout:超时时间,单位s。-1为无穷大

    readOnly:是否只读

  2. rollbackFor:出现哪些异常回滚

    noRollbackFor:出现哪些异常不回滚

    推荐博客:https://www.cnblogs.com/clwydjgs/p/9317849.html(rollbackFor的详解)

(借上面链接表格一用)

属性类型描述
valueString可选的限定描述符,指定使用的事务管理器
propagationenum: Propagation可选的事务传播行为设置
isolationenum: Isolation可选的事务隔离级别设置
readOnlyboolean读写或只读事务,默认读写
timeoutint (in seconds granularity)事务超时时间设置
rollbackForClass对象数组,必须继承自Throwable导致事务回滚的异常类数组
rollbackForClassName类名数组,必须继承自Throwable导致事务回滚的异常类名字数组
noRollbackForClass对象数组,必须继承自Throwable不会导致事务回滚的异常类数组
noRollbackForClassName类名数组,必须继承自Throwable不会导致事务回滚的异常类名字数组

使用

@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, timeout = -1, readOnly = false, rollbackFor = Exception.class)
Test
package com.hyn.spring.test;

import com.hyn.spring.bean.User;
import com.hyn.spring.service.UserService;
import org.junit.Test;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class UserTest {
    @Test
    public void test01() {
        AbstractApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        UserService userService = context.getBean("userService", UserService.class);
        User A = userService.findUserById(1);
        User B = userService.findUserById(2);
        System.out.println(A);
        System.out.println(B);
        userService.accountMoney(A, B);
        System.out.println("转账成功");
    }
}
  | 可选的事务传播行为设置                 |
| isolation              | enum: Isolation                    | 可选的事务隔离级别设置                 |
| readOnly               | boolean                            | 读写或只读事务,默认读写               |
| timeout                | int (in seconds granularity)       | 事务超时时间设置                       |
| rollbackFor            | Class对象数组,必须继承自Throwable | 导致事务回滚的异常类数组               |
| rollbackForClassName   | 类名数组,必须继承自Throwable      | 导致事务回滚的异常类名字数组           |
| noRollbackFor          | Class对象数组,必须继承自Throwable | 不会导致事务回滚的异常类数组           |
| noRollbackForClassName | 类名数组,必须继承自Throwable      | 不会导致事务回滚的异常类名字数组       |

**使用**

```java
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, timeout = -1, readOnly = false, rollbackFor = Exception.class)
Test
package com.hyn.spring.test;

import com.hyn.spring.bean.User;
import com.hyn.spring.service.UserService;
import org.junit.Test;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class UserTest {
    @Test
    public void test01() {
        AbstractApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        UserService userService = context.getBean("userService", UserService.class);
        User A = userService.findUserById(1);
        User B = userService.findUserById(2);
        System.out.println(A);
        System.out.println(B);
        userService.accountMoney(A, B);
        System.out.println("转账成功");
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值