Spring

1.Spring概述

1.1.Spring介绍

Spring是分层的Java SE/EE应用 full-stack轻量级开源框架(官网: Spring | Home ),以IoC(Inverse Of Control:反转控制)和AOP(Aspect Oriented Programming:面向切面编程)为内核

提供了展现层Spring MVC持久层Spring JDBC以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的Java EE企业应用开源框架。

1.2.Spring的体系结构

Spring 框架采用分层的理念,根据功能的不同划分成了多个模块,这些模块大体可分为 Data Access/Integration(数据访问与集成)、Web、AOP、Aspects、Instrumentation(检测)、Messaging(消息处理)、Core Container(核心容器)和 Test。

  • Core Container: 框架的最基础部分,提供控制反转和依赖注入特性

  • AOP :提供了面向切面的编程的实现

  • Data Access/Integration:简化了持久层的操作

  • Web:提供了Spring MVC Web 框架实现以及与Servlet、WebSocket的集成

  • Test:方便程序的测试

1.3.Spring的发展历程

  • 1997年IBM提出了EJB的思想

  • 1998年,SUN制定开发标准规范EJB1.0

  • 1999年,EJB1.1发布

  • 2001年,EJB2.0发布

  • 2003年,EJB2.1发布

  • 2006年,EJB3.0发布

    Rod Johnson(spring之父)

    Expert One-to-One J2EE Design and Development(2002)

    阐述了J2EE使用EJB开发设计的优点及解决方案

    Expert One-to-One J2EE Development without EJB(2004)

    阐述了J2EE开发不使用EJB的解决方式(Spring雏形)

  • 2017年9月发布了Spring的最新版本Spring5.0通用版x

2.Spring IOC

2.1.程序的耦合

  • 耦合:耦合指的就是对象之间的依赖性。对象之间的耦合越高,维护成本越高。

  • 案例:没有引入IOC容器时系统的Web层、业务层、持久层存在耦合

/**
 * 持久层实现类
 */
public class UserDaoImpl implements UserDao {

    @Override
    public void addUser(){
        System.out.println("insert into tb_user......");
    }
}
/**
 * 业务层实现类
 */
public class UserServiceImpl implements UserService {
    //硬编码:此处有依赖关系
    private UserDao userDao = new UserDaoImpl();

    public void addUser(){
        userDao.addUser();
    }
}
/**
 * 模拟表现层
 */
public class Client {
    public static void main(String[] args) {
        //硬编码:此处有依赖关系
        UserService userService = new UserServiceImpl();
        userService.addUser();
    }
}
  • 问题分析:

  • 上边的代码service层在依赖dao层的实现类,此时如果更改dao了层的实现类或此时没有dao层实现类,编译将不能通过。

  • 解决程序耦合的思路 工厂模式解耦:在实际开发中我们可以把所有的dao和service对象使用配置文件配置起来,当启动服务器应用加载的时候,通过读取配置文件把这些对象通过反射创建出来并保存在容器中。在使用的时候,直接拿过来用就好了。

2.2.IOC解决程序耦合

  • IOC (Inverse of Control)即控制反转:是指将原来程序中自己创建实现类对象的控制权反转到IOC容器中,程序只需要从IoC容器获取创建好的对象。

  • 原来:

    我们在获取对象时,都是采用new的方式。是主动的。

  • 现在:

我们获取对象时,同时跟工厂要,有工厂为我们查找或者创建对象。是被动的。

这种被动接收的方式获取对象的思想就是控制反转,它是spring框架的核心之一。

  • 案例一

    /**
     * bean工厂
     */
    public class BeanFactory {
    
        /**
         * 获得UserServiceImpl对象
         * @return
         */
        public static UserService getUserService(){
            return new UserServiceImpl();
        }
    
        /**
         * 获得UserDaoImpl对象
         * @return
         */
        public static UserDao getUserDao(){
            return new UserDaoImpl();
        }
    }

    问题:我们在开发中会有很多个service和dao,此时工厂类就要添加无数个方法。

    • 案例二

      #配置要使用的dao和service
      UserDao=com.qf.dao.UserDaoImpl
      UserService=com.qf.service.UserServiceImpl
      /**
       * bean工厂
       */
      public class BeanFactory {
      
          private static Properties prop = new Properties();
      
          /**
           * 根据全类名获取bean对象
           * @param beanName
           * @return
           * @throws ClassNotFoundException
           */
          public static Object getBean(String beanName) {
              try {
                   //不能使用:web工程发布后没有src目录
                  //InputStream is = new FileInputStream("src/bean.properties");
                  InputStream is = 
                  BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
                  prop.load(is);
                  return Class.forName(prop.getProperty(beanName)).newInstance();
              } catch (Exception e) {
                  e.printStackTrace();
              }
              return null;
          }
      
          public static void main(String[] args) {
              System.out.println(prop.get("UserService"));
              System.out.println(getBean("UserService"));
          }
      }
      /**
       * 业务层实现类
       */
      public class UserServiceImpl implements UserService {
          
          private UserDao userDao = (UserDao) BeanFactory.getBean("UserDao");
      
          public void addUser(){
            userDao.addUser();
          }
      }

      测试:

      /**
       * 模拟表现层
       */
      public class Client {
          public static void main(String[] args) {
              //直接引用接口实现类
            for (int i = 0; i < 5; i++) {
                  UserService userService = 
                    (UserService)BeanFactory.getBean("UserService");
                  System.out.println(userService);
              }
          }
      }

      问题:

      1.每次都会创建新的对象

      2.程序运行时才创建对象(读取配置文件)
  • 案例三

    package com.qf.factory;
    
    import java.io.InputStream;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Properties;
    import java.util.Set;
    
    /**
     * bean工厂
     */
    public class BeanFactory {
    
        //定义一个容器,用于存放对象
        private static Map<String, Object> beans = new HashMap<>();
    
        /**
         * 加载配置文件
         */
        static {
            try {
                //1、读取配置文件
                //不能使用:web工程发布后没有src目录
                //InputStream is = new FileInputStream("src/bean.properties");
                InputStream is = 
                BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
    
                //2、通过反射创建对象
                Properties prop = new Properties();
                prop.load(is);
                Set<Map.Entry<Object, Object>> entrySet = prop.entrySet();
                for (Map.Entry<Object, Object> entry : entrySet) {
                    String key = entry.getKey().toString();
                    String beanName = entry.getValue().toString();
                    Object value = Class.forName(beanName).newInstance();
                    //3、把对象存到容器中
                    beans.put(key, value);
                }
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 根据bean的唯一标识获取对象
         * @param beanName
         * @return
         */
        public static Object getBean(String beanName) {
            try {
                return beans.get(beanName);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
        public static void main(String[] args) {
            System.out.println(getBean("UserService"));
        }
    }

2.3.Spring的IOC解决程序耦合

2.3.1.创建空工程

 

  2.3.2.创建工程

 2.3.2.1.pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.qf</groupId>
    <artifactId>Spring_IOC_Xml</artifactId>
    <version>1.0-SNAPSHOT</version>
    
    <properties>
        <!-- 项目源码及编译输出的编码 -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <!-- 项目编译JDK版本 -->
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    
    <dependencies>
        <!-- Spring常用依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.8.RELEASE</version>
        </dependency>
    </dependencies>
</project>

注意:Jar包彼此存在依赖,只需引入最外层Jar即可由Maven自动将相关依赖Jar引入到项目中。

Spring常用功能的Jar包依赖关系

核心容器由 beans、core、context 和 expression(Spring Expression Language,SpEL)4个模块组成。

  • spring-beans和spring-core模块是Spring框架的核心模块,包含了控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI)。BeanFactory使用控制反转对应用程序的配置和依赖性规范与实际的应用程序代码进行了分离。BeanFactory属于延时加载,也就是说在实例化容器对象后并不会自动实例化Bean,只有当Bean被使用时,BeanFactory才会对该 Bean 进行实例化与依赖关系的装配。

  • spring-context模块构架于核心模块之上,扩展了BeanFactory,为它添加了Bean生命周期控制、框架事件体系及资源加载透明化等功能。此外,该模块还提供了许多企业级支持,如邮件访问、远程访问、任务调度等,ApplicationContext 是该模块的核心接口,它的超类是 BeanFactory。与BeanFactory不同,ApplicationContext实例化后会自动对所有的单实例Bean进行实例化与依赖关系的装配,使之处于待用状态。

  • spring-expression 模块是统一表达式语言(EL)的扩展模块,可以查询、管理运行中的对象,同时也可以方便地调用对象方法,以及操作数组、集合等。它的语法类似于传统EL,但提供了额外的功能,最出色的要数函数调用和简单字符串的模板函数。EL的特性是基于Spring产品的需求而设计的,可以非常方便地同Spring IoC进行交互。

2.3.2.2.dao

/**
 * 持久层实现类
 */
public class UserDaoImpl implements UserDao {

    @Override
    public void addUser(){
        System.out.println("insert into tb_user......");
    }
}

2.3.2.3.service

/**
 * 业务层实现类
 */
public class UserServiceImpl implements UserService {
    //此处有依赖关系
    private UserDao userDao = new UserDaoImpl();

    public void addUser(){
        userDao.addUser();
    }
}

2.3.3.IOC

2.3.3.1.applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--1、注意:要导入schema约束-->
<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">
    
    <!--
		2、把对象交给spring来创建
       		id:给对象在容器中提供一个唯一标识。用于获取对象	
		   	class:指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数
	-->
    <bean id="userDao" class="com.qf.dao.UserDaoImpl"></bean>
    <bean id="userService" class="com.qf.service.UserServiceImpl"></bean>
</beans>

注意:命名无限制,约定俗成命名有:spring-context.xml、applicationContext.xml、beans.xml

2.3.3.2.测试

/**
 * 模拟表现层
 */
public class Client {
    public static void main(String[] args) {
        //1.使用ApplicationContext接口,就是在获取spring容器
        ApplicationContext ac = new 
            ClassPathXmlApplicationContext("applicationContext.xml");
        //2.根据bean的id获取对象
        UserDao userDao = (UserDao) ac.getBean("userDao");
        System.out.println(userDao);

        UserService userService = (UserService) ac.getBean("userService");
        System.out.println(userService);
        userService.addUser();
    }
}
  • 问题:service层仍然耦合

2.3.4.DI

概述:DI(Dependency Injection)依赖注入,在Spring创建对象的同时,为其属性赋值,称之为依赖注入。

2.3.4.1.构造函数注入

顾名思义,就是使用类中的构造函数,给成员变量赋值。注意,赋值的操作不是我们自己做的,而是通过配置的方式,让spring框架来为我们注入。具体代码如下:

/**
 * 业务层实现类
 */
public class UserServiceImpl implements UserService {

    private UserDao userDao;
    private String name;
    private Integer age;

    public UserServiceImpl(UserDao userDao, String name, Integer age) {
        this.userDao = userDao;
        this.name = name;
        this.age = age;
    }

    public void addUser(){
        System.out.println(name+","+age);
        userDao.addUser();
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<!--1、注意:要导入schema约束-->
<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">
    <!--2、把对象交给spring来创建-->
    <bean id="userDao" class="com.qf.dao.UserDaoImpl"></bean>
    <bean id="userService" class="com.qf.service.UserServiceImpl">
        <!--
               要求:类中需要提供一个对应参数列表的构造函数。
               标签:constructor-arg
                       ==给谁赋值:==
				           index:指定参数在构造函数参数列表的索引位置
				           name:指定参数在构造函数中的名称
				       ==赋什么值:==
				           value:它能赋的值是基本数据类型和String类型
				           ref:它能赋的值是其他bean类型,也就是说,必须得是在配置文件中配置过的bean
        -->
        <constructor-arg name="userDao" ref="userDao"></constructor-arg>
        <constructor-arg name="name" value="张三"></constructor-arg>
        <constructor-arg name="age" value="18"></constructor-arg>
    </bean>
</beans>

2.3.4.2.set方法注入

顾名思义,就是在类中提供需要注入成员的set方法。具体代码如下:

/**
 * 业务层实现类
 */
public class UserServiceImpl implements UserService {

    private UserDao userDao;
    private String name;
    private Integer age;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

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

    public void setAge(Integer age) {
        this.age = age;
    }

    public void addUser(){
        System.out.println(name+","+age);
        userDao.addUser();
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<!--1、注意:要导入schema约束-->
<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">
    <!--2、把对象交给spring来创建-->
    <bean id="userDao" class="com.qf.dao.UserDaoImpl"></bean>
    <bean id="userService" class="com.qf.service.UserServiceImpl">
        <!--
               要求:property
               标签:constructor-arg
                       ==给谁赋值:==
				           name:找的是类中set方法后面的部分
				       ==赋什么值:==
				           value:它能赋的值是基本数据类型和String类型
				           ref:它能赋的值是其他bean类型,也就是说,必须得是在配置文件中配置过的bean
        -->
        <property name="userDao" ref="userDao"></property>
        <property name="name" value="张三"></property>
        <property name="age" value="18"></property>
    </bean>
</beans>

2.3.4.3.自动注入

不用在配置中 指定为哪个属性赋值,由spring自动根据某个 "原则" ,在工厂中查找一个bean并为属性注入值。具体代码如下:

/**
 * 业务层实现类
 */
public class UserServiceImpl implements UserService {

    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void addUser(){
        userDao.addUser();
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<!--1、注意:要导入schema约束-->
<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">
    <!--2、把对象交给spring来创建-->
    <bean id="userDao" class="com.qf.dao.UserDaoImpl"></bean>
        <!--autowire="byType":按照类型自动注入值-->
    <bean id="userService" class="com.qf.service.UserServiceImpl" autowire="byType">
    </bean>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<!--1、注意:要导入schema约束-->
<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">
    <!--2、把对象交给spring来创建-->
    <bean id="userDao" class="com.qf.dao.UserDaoImpl"></bean>
    <!--autowire="byType":按照类型自动注入值-->
    <bean id="userService" class="com.qf.service.UserServiceImpl" autowire="byName">
    </bean>
</beans>

2.3.4.4.注入集合类型的属性

顾名思义,就是给类中的集合成员传值,它用的也是set方法注入的方式,只不过变量的数据类型都是集合。我们这里介绍注入数组,List,Set,Map。具体代码如下:

/**
 * 业务层实现类
 */
public class UserServiceImpl implements UserService {

    private UserDao userDao;
    private String[] myStrs;
    private List<String> myList;
    private Set<String> mySet;
    private Map<String,String> myMap;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void setMyStrs(String[] myStrs) {
        this.myStrs = myStrs;
    }

    public void setMyList(List<String> myList) {
        this.myList = myList;
    }

    public void setMySet(Set<String> mySet) {
        this.mySet = mySet;
    }

    public void setMyMap(Map<String, String> myMap) {
        this.myMap = myMap;
    }

    public void addUser(){
        System.out.println(Arrays.toString(myStrs));
        System.out.println(myList);
        System.out.println(mySet);
        System.out.println(myMap);
        userDao.addUser();
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<!--1、注意:要导入schema约束-->
<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">
    <!--2、把对象交给spring来创建-->
    <bean id="userDao" class="com.qf.dao.UserDaoImpl"></bean>
    <bean id="userService" class="com.qf.service.UserServiceImpl">
        <!--
                要求:property
                标签:constructor-arg
                        ==给谁赋值:==
				            name:找的是类中set方法后面的部分
				        ==赋什么值:==
				            value:它能赋的值是基本数据类型和String类型
				            ref:它能赋的值是其他bean类型,也就是说,必须得是在配置文件中配置过的bean
        -->
        <property name="userDao" ref="userDao"></property>
        <!-- 给mySet集合注入数据 -->
        <property name="mySet">
            <set>
                <value>AAA</value>
                <value>BBB</value>
                <value>CCC</value>
            </set>
        </property>
        <!-- 注入array数组数据 -->
        <property name="myArray">
            <array>
                <value>AAA</value>
                <value>BBB</value>
                <value>CCC</value>
            </array>
        </property>
        <!-- 注入list集合数据 -->
        <property name="myList">
            <list>
                <value>AAA</value>
                <value>BBB</value>
                <value>CCC</value>
            </list>
        </property>
        <!-- 注入Map数据 -->
        <property name="myMap">
            <map>
                <entry key="testA" value="aaa"></entry>
                <entry key="testB" value="bbb"></entry>
            </map>
        </property>
    </bean>
</beans>

2.4.Spring中的工厂类

2.4.1.ApplicationContext

  • ApplicationContext的实现类,如下图:

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

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

2.4.2.BeanFactory

  • spring中工厂的类结构图

     区别:

    • ApplicationContext:只要一读取配置文件,默认情况下就会创建对象。

      /**
       * 业务层实现类
       */
      public class UserServiceImpl implements UserService {
      
          private UserDao userDao;
      
          public UserServiceImpl() {
              System.out.println("bean对象创建了...");
          }
      
          public void setUserDao(UserDao userDao) {
              this.userDao = userDao;
          }
      
          public void addUser(){
              userDao.addUser();
          }
      }
      /**
       * 模拟表现层
       */
      public class Client {
          public static void main(String[] args) {
              new ClassPathXmlApplicationContext("applicationContext.xml");
          }
      }
    • BeanFactory:是在 getBean 的时候才会生成类的实例。

      /**
       * 业务层实现类
       */
      public class UserServiceImpl implements UserService {
      
          private UserDao userDao;
      
          public UserServiceImpl() {
              System.out.println("bean对象创建了...");
          }
      
          public void setUserDao(UserDao userDao) {
              this.userDao = userDao;
          }
      
          public void addUser(){
              userDao.addUser();
          }
      }
      /**
       * 模拟表现层
       */
      public class Client {
          public static void main(String[] args) {
              new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
          }
      }

2.4.bean的作用范围

2.4.1.概述

  • 在Spring中,bean作用域用于确定哪种类型的bean实例应该从Spring容器中返回给调用者。

2.4.2.五种作用域

  • 目前Spring Bean的作用域或者说范围主要有五种:

    作用域说明
    singleton默认值,Bean以单例方式存在spring IoC容器
    prototype每次从容器中调用Bean时都返回一个新的实例,相当于执行newInstance()
    requestWEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中
    sessionWEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中
    applicationWEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 ServletContext 域中
  • 可以通过 <bean> 标签的scope 属性控制bean的作用范围,其配置方式如下所示:

    <bean id="..." class="..." scope="singleton"/>
  • 需要根据场景决定对象的单例、多例模式

    单例:Service、DAO、SqlSessionFactory(或者是所有的工厂)

    多例:Connection、SqlSession

2.5.bean的生命周期

2.5.1.单例bean

  • 案例

    <bean id="userService" class="com.qf.service.UserServiceImpl"
          			scope="singleton" init-method="init" destroy-method="destroy">
    /**
     * 业务层实现类
     */
    public class UserServiceImpl implements UserService {
    
        private UserDao userDao;
    
        public UserServiceImpl() {
            System.out.println("调用构造方法创建bean...");
        }
    
        public void setUserDao(UserDao userDao) {
            System.out.println("调用set方法注入值...");
            this.userDao = userDao;
        }
    
        public void init(){
            System.out.println("调用init方法初始化bean...");
        }
    
        public void destroy(){
            System.out.println("调用destroy方法销毁bean...");
        }
    
        public void addUser(){
            userDao.addUser();
        }
    }
    /**
     * 模拟表现层
     */
    public class Client {
        public static void main(String[] args) {
            ClassPathXmlApplicationContext ac = 
            	new ClassPathXmlApplicationContext("applicationContext.xml");
            ac.getBean("userService");
            //关闭容器
            ac.close();
        }
    }
  • 生命周期:

    [容器启动]--->构造方法(实例化)--->set方法(注入)--->init方法(初始化)--->[容器关闭]--->destroy方法(销毁)

2.5.2.多例bean

案例

  • <bean id="userService" class="com.qf.service.UserServiceImpl"
          			scope="prototype" init-method="init" destroy-method="destroy">
    /**
     * 业务层实现类
     */
    public class UserServiceImpl implements UserService {
    
        private UserDao userDao;
    
        public UserServiceImpl() {
            System.out.println("调用构造方法创建bean...");
        }
    
        public void setUserDao(UserDao userDao) {
            System.out.println("调用set方法注入值...");
            this.userDao = userDao;
        }
    
        public void init(){
            System.out.println("调用init方法初始化bean...");
        }
    
        public void destroy(){
            System.out.println("调用destroy方法销毁bean...");
        }
    
        public void addUser(){
            userDao.addUser();
        }
    }
    /**
     * 模拟表现层
     */
    public class Client {
        public static void main(String[] args) {
            ClassPathXmlApplicationContext ac = 
                	new ClassPathXmlApplicationContext("applicationContext.xml");
            //使用对象
            ac.getBean("userService");
        }
    }
  • 生命周期:

    [使用对象]---->构造方法(实例化)--->set方法(注入)--->init方法(初始化)--->[JVM垃圾回收]--->destroy方法(销毁)

    3.基于注解的IOC配置

    学习基于注解的IOC配置,大家脑海里首先得有一个认知,即注解配置和xml配置要实现的功能都是一样的,都是要降低程序间的耦合。只是配置的形式不一样。

3.1.创建工程

3.1.1.pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.qf</groupId>
    <artifactId>Spring_IOC_Annotation</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!-- Spring常用依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.8.RELEASE</version>
        </dependency>
    </dependencies>
</project>

3.1.2.dao

/**
 * 持久层实现类
 */
public class UserDaoImpl implements UserDao {

    @Override
    public void addUser(){
        System.out.println("insert into tb_user......");
    }
}

3.1.3.service

/**
 * 业务层实现类
 */
public class UserServiceImpl implements UserService {

    private UserDao userDao;

    public void addUser(){
        userDao.addUser();
    }
}

3.2.IOC

3.2.1.applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       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
				http://www.springframework.org/schema/context
      			http://www.springframework.org/schema/context/spring-context.xsd ">

    <!-- 告知spring框架在,读取配置文件,创建容器时,扫描注解,依据注解创建对象,并存入容器中 -->
    <context:component-scan base-package="com.qf"></context:component-scan>
</beans>

3.2.2.dao

@Repository
public class UserDaoImpl implements UserDao {
	... ...
}

3.2.3.service

@Service
public class UserServiceImpl implements UserService {
	... ...
}

3.3.DI

3.3.1.service

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

    public void addUser() {
        userDao.addUser();
    }
}

3.3.2.测试

/**
 * 模拟表现层
 */
public class Client {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext ac = 
            new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = ac.getBean("userServiceImpl",UserService.class);
        userService.addUser();
    }
}

3.3.常用注解

3.3.1.用于创建对象的

以下四个注解的作用及属性都是一模一样的,都是针对一个的衍生注解只不过是提供了更加明确的语义化。

3.3.1.1.@Controller

  • 作用:

    把资源交给spring来管理,相当于:<bean id="" class="">;一般用于表现层。

  • 属性:

    value:指定bean的id;如果不指定value属性,默认bean的id是当前类的类名,首字母小写;

3.3.1.2.@Service

  • 作用:

    把资源交给spring来管理,相当于:<bean id="" class="">;一般用于业务层。

  • 属性:

    value:指定bean的id;如果不指定value属性,默认bean的id是当前类的类名,首字母小写;

  • 案例

    //@Service("userService")声明bean,且id="userServiceImpl"
    @Service//声明bean,且id="userServiceImpl"
    public class UserServiceImpl implements UserService {
     	...   
    }

3.3.1.3.@Repository

  • 作用:

    把资源交给spring来管理,相当于:<bean id="" class="">;一般用于持久层。

  • 属性:

    value:指定bean的id;如果不指定value属性,默认bean的id是当前类的类名,首字母小写;

  • 案例

    //@Repository("userDaoImpl")声明bean,且id="userDaoImpl"
    @Repository//声明bean,且id="userDaoImpl"
    public class UserDaoImpl implements UserDao {
    
        @Override
        public void addUser(){
            System.out.println("insert into tb_user......");
        }
    }

3.3.1.4.@Component

  • 作用:

    把资源交给spring来管理,相当于:<bean id="" class="">;通用。

  • 属性:

    value:指定bean的id;如果不指定value属性,默认bean的id是当前类的类名,首字母小写;

3.3.1.5.@Scope

  • 作用:

    指定bean的作用域范围。

  • 属性:

    value:指定范围的值,singleton prototype request session。

3.3.2.用于属性注入的

以下四个注解的作用相当于:<property name="" ref="">

3.3.2.1.@Autowired

  • 作用:

    自动按照类型注入。set方法可以省略。

  • 案例:

    @Service
    public class UserServiceImpl implements UserService {
    
        @Autowired //注入类型为UserDAO的bean
        private UserDao userDao;
    
        public void addUser(){
            userDao.addUser();
        }
    }

3.3.2.1.@Resource

  • 作用:

    自动按照类型注入。set方法可以省略。

  • 属性:

name:指定bean的id。

  • 案例:

    @Service
    public class UserServiceImpl implements UserService {
    
        @Resource(name="userDaoImpl")//注入id=“userDaoImpl”的bean
        private UserDao userDao;
    
        public void addUser(){
            userDao.addUser();
        }
    }

3.3.2.1.@Value

  • 作用:

    注入基本数据类型和String类型数据的

  • 属性:

value:用于指定值

  • 案例一

    @Service
    public class UserServiceImpl implements UserService {
    
        @Resource(name="userDaoImpl") //注入id=“userDaoImpl”的bean
        private UserDao userDao;
        @Value("张三")//注入String
        private String name;
        @Value("18")//注入Integer
        private Integer age;
    
        public void addUser(){
            System.out.println(name+","+age);
            userDao.addUser();
        }
    }
  • 案例二

    1. 创建config.properties

      name=张三
      age=18
    2. 加载配置文件

      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:context="http://www.springframework.org/schema/context"
             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
      			http://www.springframework.org/schema/context
            		http://www.springframework.org/schema/context/spring-context.xsd ">
          <!--加载config.properties-->
          <context:property-placeholder location="config.properties"/>
          <context:component-scan base-package="com.qf"></context:component-scan>
      </beans>
    3. 注入属性值

      @Service
      public class UserServiceImpl implements UserService {
      
          @Autowired
          private UserDao userDao;
          @Value("${name}")//注入String
          private String name;
          @Value("${age}")//注入Integer
          private Integer age;
      
          public void addUser() {
              System.out.println(name+","+age);
              userDao.addUser();
          }
      }

4.Spring AOP

4.1.为什么要学习AOP?

  • 案例:有一个接口Service有一个insert方法,在insert被调用时打印调用前的毫秒数与调用后的毫秒数,其实现为:

    public class UserServiceImpl implements UserService {
    
        private UserDao userDao;
    
        public void setUserDao(UserDao userDao) {
            this.userDao = userDao;
        }
    
        public void addUser(){
            System.out.println("方法开始时间:"+new Date());
            userDao.addUser();
            System.out.println("方法结束时间:"+new Date());
        }
    }
  • 问题:输出日志的逻辑还是无法复用

4.2.AOP概述

AOP:全称是Aspect Oriented Programming即:面向切面编程。

简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对程序进行增强:权限校验,日志记录,性能监控,事务控制.

4.3.代理(Proxy)模式

  • 作用:通过代理可以控制访问某个对象的方法,在调用这个方法前做前置处理,调用这个方法后做后置处理。(即: AOP的微观实现!)

  • 核心角色

    • 抽象角色(接口):定义公共对外方法

    • 真实角色(周杰伦):实现抽象角色,定义真实角色所要实现的业务逻辑,

    • 代理角色(代理人):实现抽象角色,是真实角色的代理,通过调用真实角色的方法来完成业务逻辑,并可以附加自己的操作。

4.3.1.静态代理

4.3.1.1.抽象角色

package com.qf.proxy.StaticProxy;

public interface Star {
	/**
	 * 面谈
	 */
	void confer();
	/**
	 * 签合同
	 */
	void signContract();
	/**
	 * 订票
	 */
	void bookTicket();
	/**
	 * 唱歌
	 */
	void sing();
	/**
	 * 收钱
	 */
	void collectMoney();
}

4.3.1.2.真正角色(周杰伦)

package com.qf.proxy.StaticProxy;

public class RealStar implements Star {

	public void bookTicket() {
		System.out.println("RealStar.bookTicket()");
	}

	public void collectMoney() {
		System.out.println("RealStar.collectMoney()");
	}

	public void confer() {
		System.out.println("RealStar.confer()");
	}

	public void signContract() {
		System.out.println("RealStar.signContract()");
	}

	public void sing() {
		System.out.println("RealStar(周杰伦本人).sing()");
	}
}

4.3.1.3.代理角色(经纪人)

package com.usian.proxy.StaticProxy;

public class ProxyStar implements Star {
	
	private Star star;
	
	public ProxyStar(Star star) {
		super();
		this.star = star;
	}

	public void bookTicket() {
		System.out.println("ProxyStar.bookTicket()");
	}

	public void collectMoney() {
		System.out.println("ProxyStar.collectMoney()");
	}

	public void confer() {
		System.out.println("ProxyStar.confer()");
	}

	public void signContract() {
		System.out.println("ProxyStar.signContract()");
	}

	public void sing() {
		star.sing();
	}
}

4.3.1.4.测试

package com.usian.proxy.StaticProxy;

public class Client {
	public static void main(String[] args) {
		Star proxy = new ProxyStar(new RealStar());
		
		proxy.confer();
		proxy.signContract();
		proxy.bookTicket();
		proxy.sing();
		
		proxy.collectMoney();
		
	}
}

4.3.1.5.静态代理的缺点

  1. 代理类和实现类实现了相同的接口,这样就出现了大量的代码重复。

  2. 代理对象只服务于一种类型的对象。如果要服务多类型的对象,例如代码是只为UserService类的访问提供了代理,但是还要为其他类如DeptService类提供代理的话,就需要我们再次添加代理DeptService的代理类。

4.3.2.jdk动态代理

4.3.2.1.抽象角色

同上

4.3.2.2.真正角色

同上

4.3.2.3.代理角色(经纪人)

package com.qf.proxy.JdkProxy;

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

//代理工厂,用来创建代理对象
public class ProxyFactory {

    Object realObj;

    public ProxyFactory(Object object) {
        this.realObj = object;
    }

    public Object getProxyObject() {
        //使用Proxy获取代理对象
        /*
            newProxyInstance()方法参数说明:
                ClassLoader loader:类加载器,用于加载代理类,使用真实对象的类加载器即可
                Class<?>[] interfaces:真实对象所实现的接口,代理模式真实对象和代理对象实现相同接口
                InvocationHandlerh:代理对象的调用处理程序
         */
        return Proxy.newProxyInstance(realObj.getClass().getClassLoader(),
                realObj.getClass().getInterfaces(),
                new InvocationHandler() {
                    /*
                        InvocationHandler中invoke方法参数说明:
                            proxy:代理对象
                            method:对应于在代理对象上调用的接口方法的 Method 实例
                            args:代理对象调用接口方法时传递的实际参数
                     */
                    public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {

                        System.out.println("真正的方法执行前!");
                        System.out.println("面谈,签合同,预付款,订机票");
                        //执行真实对象
                        Object result = method.invoke(realObj, args);
                        System.out.println("真正的方法执行后!");
                        System.out.println("收尾款");
                        return result;
                    }
                });
    }
}

4.3.2.4.测试

package com.qf.proxy.JdkProxy;

//测试类
public class Client {
    public static void main(String[] args) {
        //获取代理对象
        Star proxyObject = (Star) new ProxyFactory(new RealStar()).getProxyObject();
        proxyObject.sing();
    }
}

4.3.3.Cglib动态代理

CGLIB是第三方提供的包,所以需要引入jar包的坐标:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>

4.3.3.1.真正角色

同上

4.3.3.2.代理角色(经纪人)

package com.qf.proxy.CglibProxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

//代理工厂
public class ProxyFactory implements MethodInterceptor {

    public ProxyFactory() {
    }

    public Object getProxyObject(Object realObj) {
        //创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
        Enhancer enhancer = new Enhancer();
        //设置父类的字节码对象
        enhancer.setSuperclass(realObj.getClass());
        //设置回调函数
        enhancer.setCallback(new ProxyFactory());
        //创建代理对象
        return enhancer.create();
    }

    /*
        intercept方法参数说明:
            obj : 代理对象
            method : 真实对象中的方法的Method实例
            args : 实际参数
            methodProxy :代理对象中的方法的method实例
     */
    public Object intercept(Object obj, Method method, Object[] args, 
                            MethodProxy methodProxy)
            throws Throwable {
        Object object = null;

        System.out.println("真正的方法执行前!");
        System.out.println("面谈,签合同,预付款,订机票");

        object = methodProxy.invokeSuper(obj, args);

        System.out.println("真正的方法执行后!");
        System.out.println("收尾款");
        return object;
    }
}

4.3.3.3.测试

package com.qf.proxy.CglibProxy;

//测试类
public class Client {
    public static void main(String[] args) {
        //获取代理对象
        Star proxyObject = (Star) new ProxyFactory(new RealStar()).getProxyObject();
        proxyObject.sing();
    }
}

4.4.AOP相关术语

  1. 连接点(joinpoint)

    被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法。

  2. 切入点(pointcut)

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

  3. 通知(advice)

    所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类

  4. 切面(aspect)

    是切入点和通知的结合

    5.引介(introduction)

    是一种特殊的通知,在不修改代码的前提下,引介可以在运行期为类动态地添加一些方法或字段

  5. 目标对象(Target)

    要代理的目标对象(要增强的类)

  6. 织入(weave)

    将增强应用到目标的过程将advice应用到target的过程

  7. 代理(Proxy)

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

4.5.AOP配置

4.5.1.创建工程

4.5.1.1.pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.qf</groupId>
    <artifactId>Spring_AOP_Xml</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!-- Spring常用依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.8.RELEASE</version>
        </dependency>
    </dependencies>
</project>

4.5.1.2.dao

/**
 * 持久层实现类
 */
public class UserDaoImpl implements UserDao {

    @Override
    public void addUser(){
        System.out.println("insert into tb_user......");
    }
}

4.5.1.3.service

/**
 * 业务层实现类
 */
public class UserServiceImpl implements UserService {

    private UserDao userDao;

    public void addUser(){
        userDao.addUser();
    }
}

4.5.1.4.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="userDao" class="com.qf.dao.UserDaoImpl"></bean>
    <bean id="userService" class="com.qf.service.UserServiceImpl">
        <property name="userDao" ref="userDao"></property>
    </bean>
</beans>

4.5.1.5.测试

/**
 * 模拟表现层
 */
public class Client {
    public static void main(String[] args) {
        ApplicationContext ac = 
            new ClassPathXmlApplicationContext("applicationContext.xml");
        //使用对象
        UserService userService = ac.getBean("userService",UserService.class);
        userService.addUser();
    }
}

4.5.2.增强

  1. 创建增强类

    package com.qf.advice;
    
    import java.util.Date;
    //增强类/通知类
    public class MyLogger {
    
        public void beforeMethodLog(){
            System.out.println("方法开始时间:"+new Date());
        }
    
        public void afterMethodLog(){
            System.out.println("方法开始时间:"+new Date());
        }
    }
  2. 配置增强类

    <!--增强-->
    <bean id="myLogger" class="com.qf.advice.MyLogger"></bean>

4.5.3.切点

  1. 切点表达式

    表达式语法:

    execution([修饰符] 返回值类型 包名.类名.方法名(参数))

    例如:

    execution(* com.qf.service.UserService.add(..))

    execution(* com.qf.service.UserService.*(..))

    execution(* com.usian.service.*.*(..))

  2. 配置切点

    <aop:config>
        <!--切点-->
        <aop:pointcut id="pointcut" expression="execution(* com.qf.service.*.*(..))"/>
    </aop:config>

4.5.4.切面

  1. 增强的类型

    • aop:before:用于配置前置通知

    • aop:after-returning:用于配置后置【try】通知,它和异常通知只能有一个执行

    • aop:after-throwing:用于配置异常【catch】通知,它和后置通知只能执行一个

    • aop:after:用于配置最终【finally】通知

    • aop:around:用于配置环绕通知

  2. 配置切面

    <!--切面-->
    <aop:aspect ref="myLogger">
        <!-- 用于配置前置通知:指定增强的方法在切入点方法之前执行 
    		method:用于指定通知类中的增强方法名称
    		ponitcut-ref:用于指定切入点
    	-->
    	<aop:before method="beforeMethodLog" pointcut-ref="pointcut"></aop:before>
    	<aop:after method="afterMethodLog" pointcut-ref="pointcut"></aop:after>
    </aop:aspect>

5.基于注解的AOP配置

5.1.创建工程

5.1.1.pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.qf</groupId>
    <artifactId>Spring_AOP_Annotation</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!-- Spring常用依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.1.8.RELEASE</version>
        </dependency>
    </dependencies>
</project>

5.1.2.dao

@Repository
public class UserDaoImpl implements UserDao {

    @Override
    public void addUser(){
        System.out.println("insert into tb_user......");
    }
}

5.1.3.service


@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

    public void addUser() {
        userDao.addUser();
    }
}

5.1.4.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: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.qf"></context:component-scan>
</beans>

5.1.5.测试

/**
 * 模拟表现层
 */
public class Client {
    public static void main(String[] args) {
        ApplicationContext ac = 
            new ClassPathXmlApplicationContext("applicationContext.xml");
        //使用对象
        UserService userService = ac.getBean("userServiceImpl",UserService.class);
        userService.addUser();
    }
}

5.2.增强

5.2.1.applicationContext.xml

<!-- 开启spring对注解AOP的支持 -->
<aop:aspectj-autoproxy/>

3.2.3.AOP配置

  1. 常用注解

    • @Aspect:把当前类声明为切面类

    • @Before:前置通知,可以指定切入点表达式

    • @AfterReturning:后置【try】通知,可以指定切入点表达式

    • @AfterThrowing:异常【catch】通知,可以指定切入点表达式

    • @After:最终【finally】通知,可以指定切入点表达式

    • @Around:环绕通知,可以指定切入点表达式

  2. 注解方式实现aop

    @Component
    @Aspect
    public class MyLogger {
    
        @Before("execution(* com.qf.service.*.*(..))")
        public void beforeMethodLog(){
            System.out.println("方法开始时间:"+new Date());
        }
    
        @After("execution(* com.qf.service.*.*(..))")
        public void afterMethodLog(){
            System.out.println("方法开始时间:"+new Date());
        }
    }

6.Spring整合MyBatis

6.1.创建工程

6.1.1.pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.qf</groupId>
    <artifactId>Spring_MyBatis</artifactId>
    <version>1.0-SNAPSHOT</version>


    <properties>
        <!-- 项目源码及编译输出的编码 -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <!-- 项目编译JDK版本 -->
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <!-- Spring常用依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.1.8.RELEASE</version>
        </dependency>
        <!-- MySql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <!--连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.0</version>
        </dependency>
        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.3</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.3</version>
        </dependency>
        <!--日志-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.26</version>
        </dependency>
        <!--junit-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.1.8.RELEASE</version>
        </dependency>
    </dependencies>
</project>

5.1.2.log4j.properties

log4j.rootLogger=DEBUG,A1

log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=[%t] [%c]-[%p] %m%n

6.1.3.applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       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
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">
    
    <context:component-scan base-package="com.qf"></context:component-scan>
</beans>

6.2.配置数据源

6.2.1.db.properties

jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=UTF-8
jdbc.username=root
jdbc.password=1111

6.2.2.applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       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
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       ">

    <!--加载配置文件-->
	<context:property-placeholder location="classpath:db.properties" />

    <!-- 配置数据源 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
          											destroy-method="close">
        <property name="driverClassName" value="${jdbc.driverClass}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

6.3.整合MyBatis

6.3.1.applicationContext.xml

<!--会话工厂-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"></property>
    <property name="typeAliasesPackage" value="com.qf.pojo"></property>
</bean>

<!--扫描basePackage所指定的包下的所有接口,生成代理类并交给spring管理-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <!--mapper所在的包-->
    <property name="basePackage" value="com.qf.mapper"></property>
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>

6.4.测试

6.4.1.创建表

CREATE TABLE `t_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `money` float DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8;

6.4.2.pojo

package com.qf.pojo;

public class User {
    private Integer id;
    private String name;
    private Float money;

    public User(String name, Float money) {
        this.name = name;
        this.money = money;
    }

    public User() {
    }

    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 Float getMoney() {
        return money;
    }

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

6.4.3.mapper

public interface UserMapper {

    public void addUser(User user);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qf.mapper.UserMapper">
    <insert id="addUser" parameterType="User">
		insert into t_user(name,money) values(#{name},#{money})
	</insert>
</mapper>

6.4.4.service

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public void addUser(User user) {
        userMapper.addUser(user);
    }
}

6.4.5.junit

package com.qf.test;

import com.qf.pojo.User;
import com.qf.service.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.xml")//加载配置文件
public class ServiceTest {
    
    @Autowired
    private UserService userService;

    @Test
    public void testAdd(){
        userService.addUser(new User("张三丰",4000F));

        userService.addUser(new User("宋远桥",2000F));
    }
}

7.Spring事务控制

7.1.事务介绍

7.1.1.什么是事务?

当你需要一次执行多条SQL语句时,可以使用事务。通俗一点说,如果这几条SQL语句全部执行成功,则才对数据库进行一次更新,如果有一条SQL语句执行失败,则这几条SQL语句全部不进行执行,这个时候需要用到事务。

刘德华《无间道》:去不了终点,回到原点

回顾一下数据库事务的四大特性ACID:

原子性(Atomicity)
       要么都执行,要么都不执行

一致性(Consistency)
       事务前后的数据都是正确的

隔离性(Isolation)
      事物之间相互隔离,互不干扰(并发执行的事务彼此无法看到对方的中间状态)

持久性(Durability)
       事务一旦提交不可再回滚 

7.1.2.数据库本身控制事物

begin transaction;
      //1.本地数据库操作:张三减少金额
      //2.本地数据库操作:李四增加金额
rollback;
或
commit transation;

7.2.3.jdbc中使用事物

1.获取对数据库的连接

2.设置事务不自动提交(默认情况是自动提交的)

conn.setAutoCommit(false);   //其中conn是第一步获取的随数据库的连接对象。

3.把想要一次性提交的几个sql语句用事务进行提交

try{
    Statement stmt = null; 
    stmt =conn.createStatement(); 
    stmt.executeUpdate(sql1); 
    int a=6/0;
    stmt.executeUpdate(Sql2); 
    . 
    . 
    . 
    conn.commit();   //使用commit提交事务 
}

4.捕获异常,进行数据的回滚(回滚一般写在catch块中)

catch(Exception e) { 
   ... 
   conn.rollback(); 
}

7.2.转账案例

7.2.1.拷贝上一章代码

7.2.2.添加转账业务

7.2.2.1.mapper

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qf.mapper.UserMapper">
    ... ...

	<!--转账-->
	<update id="updateUserOfSub">
		update t_user set money=money-#{money} where name=#{source}
	</update>

	<update id="updateUserOfAdd">
		update t_user set money=money+#{money} where name=#{target}
	</update>
</mapper>
public interface UserMapper {

    ... ...

    /**扣钱*/
    void updateUserOfSub(@Param("source") String source, @Param("money") Float money);
    /*加钱*/
    void updateUserOfAdd(@Param("target") String target, @Param("money") Float money);
}

7.2.2.2.service

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    /**
     * 转账
     * @param source
     * @param target
     * @param money
     */
    @Override
    public void updateUser(String source, String target, Float money) {
        userMapper.updateUserOfSub(source, money);
        int a = 6/0;
        userMapper.updateUserOfAdd(target, money);
    }
}

7.2.2.3.测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")//加载配置文件
public class ServiceTest {
    @Autowired
    private UserService userService;
    /**
     * 转账业务
     */
    @Test
    public void testUpdate(){
        userService.updateUser("张三丰","宋远桥",1F);
    }
}
  1. 此时我们观察数据表里面的变化情况:

    转账是成功的,但是涉及到业务的问题,如果业务层实现类有其中一个环节出问题,都会导致灾难。

  2. 我们先把数据恢复到转账前。

    现在我们故意模拟转账业务出现问题

    再来测试:

    业务执行出错,但是!

    这是因为:不满足事务的一致性(减钱的事务提交了,加钱的事务没有提交,甚至都没有执行到)。

7.2.Spring中事务控制的API介绍

  • 说明:

    • JavaEE体系进行分层开发,事务处理位于业务层,Spring提供了分层设计业务层的事务处理解决方案。

    • Spring框架为我们提供了一组事务控制的接口。具体在后面的小节介绍。这组接口是在spring-tx.RELEASE.jar中。

    • spring的事务控制都是基于AOP的,它既可以使用编程的方式实现,也可以使用配置的方式实现。我们学习的重点是使用配置的方式实现。

7.2.1.PlatformTransactionManager

  • 此接口是spring的事务管理器,它里面提供了我们常用的操作事务的方法,源代码如下:

    public interface PlatformTransactionManager { 
        
      	//开启事务  
        TransactionStatus getTransaction(TransactionDefinition definition) 
            						throws TransactionException; 
        //提交事务
        void commit(TransactionStatus status) throws TransactionException; 
        
      	//回滚事务
        void rollback(TransactionStatus status) throws TransactionException;   
    } 
  • 真正管理事务的对象

    Spring为不同的orm框架提供了不同的PlatformTransactionManager接口实现类:

    • DataSourceTransactionManager:使用Spring JDBC或iBatis 进行持久化数据时使用

    • HibernateTransactionManager:使用Hibernate版本进行持久化数据时使用

7.2.2.TransactionDefinition

  • TransactionDefinition接口包含与事务属性相关的方法,源代码如下:

    public interface TransactionDefinition {
        int PROPAGATION_REQUIRED = 0;
        int PROPAGATION_SUPPORTS = 1;
        int PROPAGATION_MANDATORY = 2;
        int PROPAGATION_REQUIRES_NEW = 3;
        int PROPAGATION_NOT_SUPPORTED = 4;
        int PROPAGATION_NEVER = 5;
        int PROPAGATION_NESTED = 6;
        int ISOLATION_DEFAULT = -1;
        int ISOLATION_READ_UNCOMMITTED = 1;
        int ISOLATION_READ_COMMITTED = 2;
      int ISOLATION_REPEATABLE_READ = 4;
        int ISOLATION_SERIALIZABLE = 8;
        int TIMEOUT_DEFAULT = -1;
        
        //传播行为
        int getPropagationBehavior();
    	//隔离级别
        int getIsolationLevel();
    
        int getTimeout();
    
        boolean isReadOnly();
    }
    

    TransactionDefinition 接口定义的事务规则包括:事务隔离级别、事务传播行为、事务超时、事务的只读、回滚规则属性,同时,Spring 还为我们提供了一个默认的实现类:DefaultTransactionDefinition,该类适用于大多数情况。如果该类不能满足需求,可以通过实现 TransactionDefinition 接口来实现自己的事务定义。

7.2.2.1.事务隔离级别

  • 事务并发时的安全问题

    问题描述隔离级别
    脏读一个事务读取到另一个事务还未提交的数据read-commited
    不可重复读一个事务内多次读取一行数据的相同内容,其结果不一致repeatable-read
    幻读一个事务内多次读取一张表中的相同内容,其结果不一致serialized-read
  • Spring事务隔离级别(比数据库事务隔离级别多一个default)由低到高为:

    隔离级别
    ISOLATION_DEFAULT这是一个platfromtransactionmanager默认的隔离级别,使用数据库默认的事务隔离级别。
    ISOLATION_READ_UNCOMMITTED这是事务最低的隔离级别,会产生脏读,不可重复读和幻像读。
    ISOLATION_READ_COMMITTED这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。 Oracle数据库默认的隔离级别。
    ISOLATION_REPEATABLE_READ这种事务隔离级别可以防止脏读、不可重复读,但是可能出现幻像读。MySQL数据库默认的隔离级别。
    ISOLATION_SERIALIZABLE这是花费最高代价但是最可靠的事务隔离级别,事务被处理为顺序执行。除了防止脏读、不可重复读外,还避免了幻像读。

7.2.2.2.事务的传播行为

  • 什么是事务传播行为?

    事务传播行为(propagation behavior)指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。 例如:methodA事务方法调用methodB事务方法时,methodB是继续在调用者methodA的事务中运行呢,还是为自己开启一个新事务运行,这就是由methodB的事务传播行为决定的。

  • Spring定义了七种传播行为:

    事务传播行为类型说明
    PROPAGATION_REQUIRED如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
    PROPAGATION_SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行。
    PROPAGATION_MANDATORY使用当前的事务,如果当前没有事务,就抛出异常。
    PROPAGATION_REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起。
    PROPAGATION_NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
    PROPAGATION_NEVER以非事务方式执行,如果当前存在事务,则抛出异常。
    PROPAGATION_NESTED如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与REQUIRED类似的操作。

7.2.2.3.事务超时

  • timeout事务超时时间: 当前事务所需操作的数据被其他事务占用,则等待。

    • 100:自定义等待时间100(秒)。

    • -1:由数据库指定等待时间,默认值。(建议)

7.2.2.4.读写性

  • readonly 读写性

    • true:只读,可提高查询效率,适合查询

    • false:可读可写,适合增删改

7.2.2.5.回滚规则

  • rollback-for 回滚规则,可省略或设置 rollback-for="Exception"

    • 如果事务中抛出 RuntimeException,则自动回滚

    • 如果事务中抛出 CheckException,不会自动回滚

7.3.3.TransactionStatus

  • PlatformTransactionManager.getTransaction(…) 方法返回一个 TransactionStatus 对象,该对象可能代表一个新的或已经存在的事务,源代码如下:

    public  interface TransactionStatus{
       boolean isNewTransaction();
       void setRollbackOnly();
       boolean isRollbackOnly();
    }

7.4.改造转账案例

7.4.1.applicationContext.xml

 	<!--配置事物管理器-->
    <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--配置事物属性-->
    <bean class="org.springframework.transaction.support.DefaultTransactionDefinition">
        <property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/>
        <property name="readOnly" value="false"></property>
    </bean>

7.4.2.service

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private TransactionDefinition txDefinition;
    @Autowired
    private PlatformTransactionManager txManager;
    /**
     * 转账
     * @param source
     * @param target
     * @param money
     */
    @Override
    public void updateUser(String source, String target, Float money) {
        // 获取一个事务
        TransactionStatus txStatus = txManager.getTransaction(txDefinition);
        try {

            userMapper.updateUserOfSub(source, money);
            int a = 6/0;
            userMapper.updateUserOfAdd(target, money);
            //提交事务
            txManager.commit(txStatus);
        }catch (Exception e){
            //回滚事务
            txManager.rollback(txStatus);
            e.printStackTrace();
        }
    }
}

7.4.3.测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")//加载配置文件
public class ServiceTest {
    @Autowired
    private UserService userService;
    /**
     * 转账业务
     */
    @Test
    public void testUpdate(){
        userService.updateUser("张三丰","宋远桥",1F);
    }
}
  • 事务回滚:

  • 满足执行:

  • 我们现在虽然实现了事务控制,但是代码非常的臃肿,我们可以使用动态代理简化代码

7.3.动态代理控制事务

7.3.1.factory

我们创建一个工厂,专门用来给 Service 创建代理对象,如下:

package com.qf.factory;

import com.qf.service.UserService;
import com.qf.service.UserServiceImpl;
import org.hamcrest.Factory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;

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

/**
 * bean工厂
 */
@Component
public class BeanFactory {
    @Autowired
    private UserService userService;
    @Autowired
    private TransactionDefinition txDefinition;
    @Autowired
    private PlatformTransactionManager txManager;

    /**
     * 获得UserServiceImpl对象
     *
     * @return
     */
    public UserService getUserService() {
        return (UserService) Proxy.newProxyInstance(
            userService.getClass().getClassLoader(),
            userService.getClass().getInterfaces(),
            new InvocationHandler() {
                    public Object invoke(Object proxy, Method method, Object[] args)
                        											throws Throwable {
                        //开启事务
                        TransactionStatus txStatus = 
                            txManager.getTransaction(txDefinition);
                        try {
                            method.invoke(userService, args);
                            //提交事务
                            txManager.commit(txStatus);
                        } catch (Exception e) {
                            //回滚事务
                            txManager.rollback(txStatus);
                            e.printStackTrace();
                        }
                        return null;
                    }
                });
    }
}

7.3.2.applicationContext.xml

<!--配置service代理对象-->
<bean id="proxyService" factory-bean="beanFactory" factory-method="getUserService"></bean>

7.2.3.service

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    /**
     * 转账
     * @param source
     * @param target
     * @param money
     */
    @Override
    public void updateUser(String source, String target, Float money) {
        userMapper.updateUserOfSub(source, money);
        int a = 6/0;
        userMapper.updateUserOfAdd(target, money);
    }
}

7.2.4.测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class ServiceTest {
    @Autowired
    @Qualifier("proxyService")//注入代理对象
    private UserService userService;

    @Test
    public void testUpdate(){
        userService.updateUser("张三丰","宋远桥",1F);
    }
}
  • 事务回滚:

7.4.Spring AOP控制事务

7.4.1.导入schema约束

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx" 
       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/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd
       http://www.springframework.org/schema/aop
       https://www.springframework.org/schema/aop/spring-aop.xsd">
    
    <!--配置事物属性
    <bean class="org.springframework.transaction.support.DefaultTransactionDefinition">
        <property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/>
        <property name="readOnly" value="false"></property>
    </bean>

    配置service代理对象
    <bean id="proxyService" factory-bean="beanFactory" factory-method="getUserService"> 
    </bean>-->
</beans>    

7.4.2.配置增强

    <!-- 1、增强 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--事务属性-->
        <tx:attributes>
        <!-- 指定方法名称:是业务核心方法
            read-only:是否是只读事务。默认false,不只读。
            isolation:指定事务的隔离级别。默认值是使用数据库的默认隔离级别。
            propagation:指定事务的传播行为。
            timeout:指定超时时间。默认值为:-1。永不超时。
            rollback-for:用于指定一个异常,当执行产生该异常时,事务回滚。产生其他异常,事务不回滚。
						 省略时任何异常都回滚。
            -->
        <tx:method name="*" read-only="false" propagation="REQUIRED"/>
        <tx:method name="select*" read-only="true" propagation="SUPPORTS"/>
        <tx:method name="get*" read-only="true" propagation="SUPPORTS"/>
       </tx:attributes>
    </tx:advice>

7.4.3.配置切点

    <aop:config>
        <!--2、切点-->
        <aop:pointcut expression="execution(* com.qf.service.*.*(..))" id="pointcut"/>
    </aop:config>

7.4.4.配置切面

    <aop:config>
        <!--3、切面-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"></aop:advisor>
    </aop:config>

7.4.5.factory

删除bean工程

7.4.6.测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")//加载配置文件
public class ServiceTest {
    @Autowired
    private UserService userService;
    /**
     * 转账业务
     */
    @Test
    public void testUpdate(){
        userService.updateUser("张三丰","宋远桥",1F);
    }
}
  • 事务回滚:

  • 问题一:如果我们把方法名称改了,那么事务还会回滚吗?

    • 修改applicationContext.xml:

    • 修改service:

  • 问题二:如果我们把异常捕捉了,那么事务还会回滚吗?

8.基于注解的AOP控制事务

8.1.拷贝上一章代码

8.2.applicationContext.xml

<!-- 开启spring对注解事务的支持 -->
<tx:annotation-driven transaction-manager="transactionManager"/> 

8.3.service

@Service
@Transactional(readOnly=true,propagation= Propagation.SUPPORTS)
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;
    /**
     * 转账
     * @param source
     * @param target
     * @param money
     */
    @Override
    @Transactional(readOnly=false,propagation=Propagation.REQUIRED)
    public void updateUser(String source, String target, Float money) {
        userMapper.updateUserOfSub(source, money);
        int a = 6/0;
        userMapper.updateUserOfAdd(target, money);
    }
}

8.4.测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")//加载配置文件
public class ServiceTest {
    @Autowired
    private UserService userService;
    /**
     * 转账业务
     */
    @Test
    public void testUpdate(){
        userService.updateUser("张三丰","宋远桥",1F);
    }
}
  • 事务回滚:

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值