一、Spring 概述

1.1 Spring 介绍

Spring 是轻量级 Java EE 应用开源框架(全栈式开发框架,官网: http://spring.io ),由 Rod Johnson 创建,旨在解决企业级编程开发的复杂性。

1.2 Spring 的优点

  1. IOC:解决传统 Web 开发中硬编码所造成的程序耦合(控制反转)
  2. AOP:在运行期间不修改源代码对程序进行增强(切面编程)
  3. 粘合剂:除自身功能外,还可以整合其他技术和框架

1.3 Spring 的体系结构

Spring 框架按功能分为五大模块:

模块分类 核心功能
Core Container(核心容器) 提供 IOC 和依赖注入特性,是框架最基础部分
AOP、Aspects、Instrumentation(检测)、Messaging(消息处理) 提供面向切面编程实现、检测、消息处理等
Data Access/Integration(数据访问与集成) 简化持久层操作
Web 提供 Spring MVC 框架及与 Servlet、WebSocket 的集成
Test 方便程序测试

1.4 Spring 的发展历程

  • 1997 年 IBM 提出了 EJB 的思想
  • 1998 年,SUN 制定开发标准规范 EJB1.0
  • 1999 年,EJB1.1 发布
  • 2001 年,EJB2.0 发布
  • 2003 年,EJB2.1 发布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 雏形)
  • 2006 年,EJB3.0 发布
  • 2017 年 9 月发布了 Spring 的最新版本 Spring5.0 通用版
  • ......

二、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 实现类,编译失败

    2.2 工厂模式的 IOC 解决程序耦合

    2.2.1 什么是 IOC

    • IOC (Inverse of Control) 即控制反转:控制是控制对象的创建;正转是自己创建依赖对象;反转是由IOC 工厂来创建依赖对象;
    • 传统方式:主动 new 创建依赖对象。

    • IOC 方式:被动从 IOC 工厂获取依赖对象。

      这种被动接收的方式获取对象的思想就是控制反转,它是 spring 框架的核心之一。
    • IOC (工厂模式) 解耦 步骤
      1. 把所有dao 和 service 对象使用配置文件配置起来
      2. 当服务器启动时读取配置文件
      3. 通过反射创建对象保存在容器(Map)中
      4. 使用时,直接从工厂拿

    2.2.2 工厂模式的 IOC 解耦

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

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

    • 案例二

    配置文件(bean.properties):

    properties

    #1、配置要使用的dao和service
    UserDao=com.hg.dao.UserDaoImpl
    UserService=com.hg.service.UserServiceImpl
    

    工厂类:

    /**
     * bean工厂
     */
    public class BeanFactory_v2 {
    
        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_v2.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_v2.getBean("UserService");
                System.out.println(userService);
            }
        }
    }
    

    问题

    1. 每次都会创建新的对象
    2. 程序运行时才创建对象 (读取配置文件)
    • 案例三
    package com.hg.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_v3 {
    
        //定义一个容器,用于存放对象
        private static Map<String, Object> beans = new HashMap<>();
    
        /**
         * 加载配置文件
         */
        static {
            try {
                //2、读取配置文件
                //不能使用:web工程发布后没有src目录
                //InputStream is = new FileInputStream("src/bean.properties");
                InputStream is = 
                BeanFactory_v3.class.getClassLoader()
                    .getResourceAsStream("bean.properties");
    
                //3、通过反射创建对象,把对象存到容器中
                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();
                    beans.put(key, value);
                }
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 4、在使用的时候,直接从工厂拿
         * @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.1.1 pom.xml

    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.hg</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>8</maven.compiler.source>
            <maven.compiler.target>8</maven.compiler.target>
        </properties>
        
        <dependencies>
            <!-- Spring常用依赖 -->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.2.8.RELEASE</version>
            </dependency>
        </dependencies>
    </project>
    

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

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

    核心容器模块说明

    • spring-beans + spring-core:提供 IOC/DI,BeanFactory(延迟加载)
    • spring-context:扩展 BeanFactory,ApplicationContext(立即加载)
    • spring-expression:Spring 表达式语言
    2.3.1.2 dao
    /**
     * 持久层实现类
     */
    public class UserDaoImpl implements UserDao {
    
        @Override
        public void addUser(){
            System.out.println("insert into tb_user......");
        }
    }
    
    2.3.1.3 service
    /**
     * 业务层实现类
     */
    public class UserServiceImpl implements UserService {
        //此处有依赖关系
        private UserDao userDao = new UserDaoImpl();
        
    	@Override
        public void addUser(){
            userDao.addUser();
        }
    }
    

    2.3.2 IOC

    2.3.2.1 applicationContext.xml

    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、<bean>标签作用:通过反射创建对象并交给spring的ioc容器去管理
           		id:给对象在容器中提供一个唯一标识。用于获取对象	
    		   	class:指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数
    	-->
        <bean id="userDao" class="com.hg.dao.UserDaoImpl"></bean>
        <bean id="userService" class="com.hg.service.UserServiceImpl"></bean>
    </beans>

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

    2.3.2.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.3 DI(构造,set,自动注入)

    概述:DI(Dependency Injection)依赖注入:将依赖对象(userDao)从容器中拿出来赋值给调用者(userService)

    2.3.3.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

    ​
    <?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.hg.dao.UserDaoImpl"></bean>
        <bean id="userService" class="com.hg.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 index="2" value="18"></constructor-arg>
        </bean>
    </beans>
    
    ​
    2.3.3.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

    <?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.hg.dao.UserDaoImpl"></bean>
        <bean id="userService" class="com.hg.service.UserServiceImpl">
            <!--
                   要求:必须提供set方法
                   标签:property
                           ==给谁赋值:==
    				           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.3.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

    <?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.hg.dao.UserDaoImpl"></bean>
            <!--autowire="byType":按照类型自动注入值-->
        <bean id="userService" class="com.hg.service.UserServiceImpl" autowire="byType">
        </bean>
    </beans>
    
    2.3.3.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

    <?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.hg.dao.UserDaoImpl"></bean>
        <bean id="userService" class="com.hg.service.UserServiceImpl">
            <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 中工厂的类结构图

    • 区别

      • BeanFactory:顶级接口, 在 getBean 的时候才会创建对象。
      • ApplicationContext:子接口, 读取配置文件就会创建对象。
    /**
     * 业务层实现类
     */
    public class UserServiceImpl implements UserService {
    
        private UserDao userDao;
    
        public UserServiceImpl() {
            System.out.println("UserServiceImpl对象创建了...");
        }
    
        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");
            System.out.println("Spring IOC容器创建好了");
        }
    }
    
      /**
       * 业务层实现类
       */
      public class UserServiceImpl implements UserService {
      
          private UserDao userDao;
      
          public UserServiceImpl() {
              System.out.println("UserServiceImpl对象创建了...");
          }
      
          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"));
              System.out.println("Spring IOC容器创建好了");
          }
      }
      

      2.5 bean 的作用域范围

      2.5.1 概述

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

      2.5.2 五种作用域

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

        xml

        <bean id="..." class="..." scope="singleton"/>
        
      • 单例、多例的使用场景:

        • 单例:Service、DAO、SqlSessionFactory(所有工厂)
        • 多例:Connection、SqlSession

      2.6 bean 的生命周期

      2.6.1 单例 bean

      • 案例

      xml

      <bean id="userService" class="com.hg.service.UserServiceImpl"
              			scope="singleton" init-method="init" destroy-method="destroy">
      

      java

      /**
       * 业务层实现类
       */
      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.close();
          }
      }
      
      • 生命周期

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

      2.6.2 多例 bean

      • 案例

      xml

      <bean id="userService" class="com.hg.service.UserServiceImpl"
              			scope="prototype" init-method="init" destroy-method="destroy">
      

      java

      /**
       * 业务层实现类
       */
      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();
          }
      }
      

      java

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

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

      三、基于注解的 IOC 配置

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

      3.1 创建工程

      3.1.1 pom.xml

      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.hg</groupId>
          <artifactId>Spring_IOC_Annotation</artifactId>
          <version>1.0-SNAPSHOT</version>
      
          <dependencies>
              <!-- Spring常用依赖 -->
              <dependency>
                  <groupId>org.springframework</groupId>
                  <artifactId>spring-context</artifactId>
                  <version>5.2.8.RELEASE</version>
              </dependency>
          </dependencies>
      </project>
      

      3.1.2 dao

      java

      /**
       * 持久层实现类
       */
      public class UserDaoImpl implements UserDao {
      
          @Override
          public void addUser(){
              System.out.println("insert into tb_user......");
          }
      }
      

      3.1.3 service

      java

      /**
       * 业务层实现类
       */
      public class UserServiceImpl implements UserService {
      
          private UserDao userDao;
      
          @Override
          public void addUser(){
              userDao.addUser();
          }
      }
      

      3.2 IOC

      3.2.1 applicationContext.xml

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

      3.2.2 dao

      java

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

      3.2.3 service

      java

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

      3.3 DI

      3.3.1 service

      java

      @Service
      public class UserServiceImpl implements UserService {
      
          @Autowired
          private UserDao userDao;
      
          public void addUser() {
              userDao.addUser();
          }
      }
      

      3.3.2 测试

      java

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

      3.4 常用注解

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

      3.4.1 用于创建对象的(IOC)

      3.4.1.1 @Controller

                用于web/controller/servlet类

      3.4.1.2 @Service
      • 案例(service类)
        //@Service("userService")声明bean,且id="userService"
        @Service//声明bean,且id="userServiceImpl"
        public class UserServiceImpl implements UserService {
             ...   
        }
        
      3.4.1.3 @Repository
      • 案例(dao类)
        //@Repository("userDao")声明bean,且id="userDao"
        @Repository//声明bean,且id="userDaoImpl"
        public class UserDaoImpl implements UserDao {
        
            @Override
            public void addUser(){
                System.out.println("insert into tb_user......");
            }
        }
        
      3.4.1.4 @Component
      • 作用:把资源交给 spring 来管理,相当于:<bean id="" class="">;通用。
      • 属性:value:指定 bean 的 id;如果不指定 value 属性,默认 bean 的 id 是当前类的类名,首字母小写;
      3.4.1.5 @Scope
      • 作用:指定 bean 的作用域范围。
      • 属性:value:指定范围的值,singleton prototype request session。

      3.4.2 用于属性注入的(DI)

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

      3.4.2.1 @Autowired
      • 作用:自动按类型注入。set 方法可以省略
      • 案例
        @Service
        public class UserServiceImpl implements UserService {
        
            @Autowired //注入类型为UserDAO的bean
            private UserDao userDao;
        
            public void addUser(){
                userDao.addUser();
            }
        }
        
      3.4.2.2 @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.4.2.3 @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

          properties

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

          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 ">
              <!--加载config.properties-->
              <context:property-placeholder location="config.properties"/>
              <context:component-scan base-package="com.hg"></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();
              }
          }
          

      四、Spring AOP(重点)

      4.1 为什么要学习 AOP?

      • 案例:有一个接口 Service 有一个 addUser 方法,在调用 addUser (被调用时打印调用前的时间与调用后的时间),其实现为:

        java

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

      4.2 AOP 概述

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

      • AOP 本质:把程序重复代码抽取出来,需要执行时,使用动态代理,不改源码,对程序增强:权限校验,日志记录,性能监控,事务控制.
      • 解决的核心问题
        • 案例中直接在 addUser() 方法中写日志逻辑,导致日志代码与业务代码耦合,且无法在其他方法 / 类中复用;
        • AOP 可将 "打印方法执行时间" 的逻辑抽取为独立模块,按需切入到任意方法的执行前后,实现一次编写、多处复用。

      4.3 代理(Proxy)模式

      代理模式详解:静态代理、JDK 动态代理与 CGLib 动态代理-CSDN博客https://blog.csdn.net/2403_89058622/article/details/158661084?spm=1001.2014.3001.5501

      4.4 AOP核心术语

      术语 定义
      连接点(joinpoint) 被拦截到的点,Spring 中仅支持方法类型的连接点,即被拦截的方法
      切入点(pointcut) 对哪些连接点进行拦截的定义(要增强的方法)
      通知(advice) 拦截到连接点后执行的代码,分为前置、后置、异常、最终、环绕通知五类
      切面(aspect) 切入点和通知的结合,把增强应用到切点上
      引介(introduction) 特殊通知,运行期为类动态添加方法 / 字段(无需修改代码)
      目标对象(Target) 要代理 / 增强的目标类
      织入(weave) 将通知(advice)应用到目标对象(target)的过程
      代理(Proxy) 类被 AOP 增强后生成的代理类

      4.5 基于 XML 的 AOP 配置

      1. 工程搭建

      (1)pom.xml 依赖配置

      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.hg</groupId>
          <artifactId>Spring_AOP_Xml</artifactId>
          <version>1.0-SNAPSHOT</version>
      
          <dependencies>
              <dependency>
                  <groupId>org.springframework</groupId>
                  <artifactId>spring-context</artifactId>
                  <version>5.2.8.RELEASE</version>
              </dependency>
          
              <dependency>
                  <groupId>org.springframework</groupId>
                  <artifactId>spring-aspects</artifactId>
                  <version>5.2.8.RELEASE</version>
              </dependency>
          </dependencies>
      </project>
      
      (2)核心业务代码
      • DAO 层
      /**
       * 持久层实现类
       */
      public class UserDaoImpl implements UserDao {
      
          @Override
          public void addUser(){
              System.out.println("insert into tb_user......");
          }
      }
      
      • Service 层
      /**
       * 业务层实现类
       */
      public class UserServiceImpl implements UserService {
      
          private UserDao userDao;
          
          public void setUserDao(UserDao userDao){
              this.userDao=userDao;
          }
      	
          @Override
          public void addUser(){
              userDao.addUser();
          }
      }
      
      (3)Spring 核心配置文件(applicationContext.xml)

      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
             			   http://www.springframework.org/schema/aop/spring-aop.xsd">
          
          <bean id="userDao" class="com.hg.dao.UserDaoImpl"></bean>
          <bean id="userService" class="com.hg.service.UserServiceImpl">
              <property name="userDao" ref="userDao"></property>
          </bean>
      </beans>
      
      (4)测试类(web层 )

      运行

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

      2. 增强类编写

      package com.hg.advice;
      
      import org.aspectj.lang.ProceedingJoinPoint;
      
      import java.util.Date;
      
      public class MyLogAdvice {
      
          //前置通知
          public void before(){
              System.out.println("前置通知");
          }
      
          //后置通知【try】
          public void afterReturning(){
              System.out.println("后置通知");
          }
      
          //异常通知【catch】
          public void afterThrowing(){
              System.out.println("异常通知");
          }
      
          //最终通知【finally】
          public void after(){
              System.out.println("最终通知");
          }
      
          //环绕通知
          public void around(ProceedingJoinPoint joinPoint){
              try {
                  System.out.println("方法执行前的环绕通知");
                  joinPoint.proceed();
                  System.out.println("方法执行后的环绕通知");
              } catch (Throwable throwable) {
                  throwable.printStackTrace();
              }
          }
      }
      

      3. 配置增强、切点、切面

      xml

      <!--配置增强类-->
      <bean id="myLogger" class="com.hg.advice.MyLogger"></bean>
      
      <!--AOP核心配置:切点 + 切面-->
      <aop:config>
          <!--切点定义:表达式语法 execution([修饰符] 返回值类型 包名.类名.方法名(参数))-->
          <aop:pointcut id="pointcut" expression="execution(* com.hg.service.*.*(..))"/>
          
          <!--切面:切点 + 通知的绑定-->
          <aop:aspect ref="myLogger">
              <!--前置通知:切入点方法执行前执行-->
              <aop:before method="before" pointcut-ref="pointcut"/>
              <!--后置通知(try块):与异常通知互斥-->
              <aop:after-returning method="afterReturning" pointcut-ref="pointcut"/>
              <!--异常通知(catch块):与后置通知互斥-->
              <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut"/>
              <!--最终通知(finally块):无论是否异常都会执行-->
              <aop:after method="after" pointcut-ref="pointcut"/>
              <!--环绕通知:覆盖方法执行全流程-->
              <aop:around method="around" pointcut-ref="pointcut"/>
          </aop:aspect>
      </aop:config>
      

      4. 测试说明

      • Service 实现接口时:生成 JDK 动态代理

      • Service 不实现接口时:生成 CGLIB 动态代理

      4.6 基于注解的 AOP 配置

      1. 工程搭建

      (1)pom.xml 依赖(同 XML 配置)
      (2)核心业务代码(添加注解)
      • DAO 层
      @Repository
      public class UserDaoImpl implements UserDao {
      
          @Override
          public void addUser(){
              System.out.println("insert into tb_user......");
          }
      }
      
      • Service 层
      @Service
      public class UserServiceImpl implements UserService {
      
          @Autowired
          private UserDao userDao;
      
          public void addUser() {
              userDao.addUser();
          }
      }
      
      (3)Spring 配置文件
      • 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.hg"></context:component-scan>
          <!--开启注解AOP支持-->
          <aop:aspectj-autoproxy/>
      </beans>
      
      (4)测试类

      运行

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

      2. 注解式 AOP 实现

      @Component
      @Aspect //声明为切面类
      public class MyLogger {
      
          //前置通知 + 切入点表达式
          @Before("execution(* com.hg.service.*.*(..))")
          public void before(){
              System.out.println("方法开始时间:"+new Date());
          }
      
          //最终通知 + 切入点表达式
          @After("execution(* com.hg.service.*.*(..))")
          public void after(){
              System.out.println("方法结束时间:"+new Date());
          }
      }
      

      3. AOP 常用注解说明

      注解 作用
      @Aspect 声明当前类为切面类
      @Before 配置前置通知,指定切入点表达式
      @AfterReturning 配置后置(try)通知,指定切入点表达式
      @AfterThrowing 配置异常(catch)通知,指定切入点表达式
      @After 配置最终(finally)通知,指定切入点表达式
      @Around 配置环绕通知,指定切入点表达式

      五、Spring 整合 MyBatis

      5.1、工程搭建

      5.1.1 pom.xml 依赖配置

      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.hg</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>8</maven.compiler.source>
              <maven.compiler.target>8</maven.compiler.target>
          </properties>
      
          <dependencies>
              <!-- Spring常用依赖 -->
              <dependency>
                  <groupId>org.springframework</groupId>
                  <artifactId>spring-context</artifactId>
                  <version>5.2.8.RELEASE</version>
              </dependency>
              <dependency>
                  <groupId>org.springframework</groupId>
                  <artifactId>spring-aspects</artifactId>
                  <version>5.2.8.RELEASE</version>
              </dependency>
              <dependency>
                  <groupId>org.springframework</groupId>
                  <artifactId>spring-tx</artifactId>
                  <version>5.2.8.RELEASE</version>
              </dependency>
              <dependency>
                  <groupId>org.springframework</groupId>
                  <artifactId>spring-jdbc</artifactId>
                  <version>5.2.8.RELEASE</version>
              </dependency>
              <!-- MySql驱动 -->
              <dependency>
                  <groupId>mysql</groupId>
                  <artifactId>mysql-connector-java</artifactId>
                  <version>5.1.47</version>
              </dependency>
              <!-- Druid连接池 -->
              <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>
              <!-- MyBatis整合Spring依赖 -->
              <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>
              <!-- 测试依赖 -->
              <dependency>
                  <groupId>org.springframework</groupId>
                  <artifactId>spring-test</artifactId>
                  <version>5.2.8.RELEASE</version>
              </dependency>
              <dependency>
                  <groupId>junit</groupId>
                  <artifactId>junit</artifactId>
                  <version>4.12</version>
              </dependency>
          </dependencies>
      
          <build>
              <resources>
                  <!-- 加载java目录下的xml文件 -->
                  <resource>
                      <directory>src/main/java</directory>
                      <includes>
                          <include>**/*.xml</include>
                      </includes>
                  </resource>
                  <!-- 加载resources目录下的所有文件 -->
                  <resource>
                      <directory>src/main/resources</directory>
                  </resource>
              </resources>
          </build>
      </project>
      

      5.1.2 日志配置(log4j.properties)

      properties

      # Global logging configuration
      log4j.rootLogger=DEBUG, stdout
      # Console output...
      log4j.appender.stdout=org.apache.log4j.ConsoleAppender
      log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
      log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
      

      5.1.3 Spring 核心配置文件(初始版 applicationContext.xml)

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

      5.2、配置数据源

      5.2.1 数据库配置文件(db.properties)

      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
      

      5.2.2 配置 Druid 数据源(更新 applicationContext.xml)

      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" />
      
          <!-- 配置Druid数据源 -->
          <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>
      
          <!-- 组件扫描 -->
          <context:component-scan base-package="com.hg"></context:component-scan>
      </beans>
      

      5.3、整合 MyBatis(完善 applicationContext.xml)

      xml

      <!-- 配置MyBatis的SqlSessionFactory -->
      <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
          <!-- 注入数据源 -->
          <property name="dataSource" ref="dataSource"></property>
          <!-- 配置实体类别名包 -->
          <property name="typeAliasesPackage" value="com.hg.pojo"></property>
      </bean>
      
      <!-- Mapper接口扫描器:自动生成Mapper代理类并交给Spring容器管理 -->
      <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
          <!-- 指定Mapper接口所在包 -->
          <property name="basePackage" value="com.hg.mapper"></property>
          <!-- 指定SqlSessionFactory名称 -->
          <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
      </bean>
      

      5.4、功能实现与测试

      5.4.1 数据库表创建

      sql

      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;
      

      5.4.2 实体类(pojo/User.java)

      java

      package com.hg.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() {
          }
      
          // Getter & Setter
          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;
          }
      }
      

      5.4.3 Mapper 层

      (1)接口(mapper/UserMapper.java)

      java

      package com.hg.mapper;
      
      import com.hg.pojo.User;
      import org.apache.ibatis.annotations.Insert;
      
      public interface UserMapper {
          /**
           * 添加用户
           * @param user 用户对象
           */
          public void addUser(User user);
      }
      
      (2)映射文件(mapper/UserMapper.xml)

      xml

      <?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.hg.mapper.UserMapper">
          <insert id="addUser" parameterType="User">
      		insert into t_user(name,money) values(#{name},#{money})
      	</insert>
      </mapper>
      

      5.4.4 Service 层

      java

      package com.hg.service;
      
      import com.hg.mapper.UserMapper;
      import com.hg.pojo.User;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Service;
      
      @Service
      public class UserServiceImpl implements UserService {
      
          @Autowired
          private UserMapper userMapper;
      
          @Override
          public void addUser(User user) {
              userMapper.addUser(user);
          }
      }
      

      5.4.5 测试类(test/ServiceTest.java)

      java

      运行

      package com.hg.test;
      
      import com.hg.pojo.User;
      import com.hg.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")//加载Spring配置文件
      public class ServiceTest {
          
          @Autowired
          private UserService userService;
      
          @Test
          public void testAdd(){
              userService.addUser(new User("张三丰",4000F));
              userService.addUser(new User("宋远桥",2000F));
          }
      }
      

      六、 事务控制(重点)

      6.1. 事务介绍

      6.1.1. 什么是事务?

      当需要一次执行多条 SQL 语句时,可使用事务。通俗来讲:若这几条 SQL 语句全部执行成功,则对数据库进行一次更新;若有一条 SQL 语句执行失败,则这几条 SQL 语句全部不执行,此时需用到事务。

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

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

      特性 简称 通俗解释
      原子性 A 事务不可分割,要么全执行,要么全不执行
      一致性 C 事务执行前后,数据总状态不变
      隔离性 I 事务之间相互隔离,互不干扰
      持久性 D 事务一旦提交不可再回滚

      6.1.2. 数据库本身控制事务

      mysql

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

      6.1.3.jdbc 中使用事务

      1. 获取对数据库的连接

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

      java

      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(); 
      }
      

      6.2. 转账案例

      6.2.1. 拷贝上一章代码

      6.2.2. 添加转账业务

      6.2.2.1.mapper

      xml

      <?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.hg.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>
      

      java

      public interface UserMapper {
      
          ... ...
      
          /**扣钱*/
          void updateUserOfSub(@Param("source") String source, @Param("money") Float money);
          /*加钱*/
          void updateUserOfAdd(@Param("target") String target, @Param("money") Float money);
      }
      
      6.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);
          }
      }
      
      6.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.先将数据恢复到转账前:

      • 故意模拟转账业务出现问题:

      再次测试:

      业务执行出错,但数据出现异常:

      原因:不满足事务的一致性(减钱的操作已提交,加钱的操作未执行 / 未提交)。

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

      • 说明:
        • JavaEE 体系分层开发,事务处理位于业务层,Spring 提供了面向业务层的事务处理解决方案。
        • Spring 框架提供了一组事务控制接口,核心接口在spring-tx.RELEASE.jar中。
        • Spring 事务控制基于 AOP 实现,支持编程式和配置式两种方式,重点学习配置式实现。

      6.3.1.PlatformTransactionManager

      • 此接口是 Spring 的事务管理器,提供了事务操作的核心方法,源代码如下:
      public interface PlatformTransactionManager { 
          //开启事务  
          TransactionStatus getTransaction(TransactionDefinition definition) 
                      throws TransactionException; 
          //提交事务
          void commit(TransactionStatus status) throws TransactionException; 
          //回滚事务
          void rollback(TransactionStatus status) throws TransactionException;   
      } 
      
      • 不同 ORM 框架对应的实现类(真正管理事务的对象):

        • DataSourceTransactionManager:适用于 Spring JDBC 或 MyBatis/iBatis 持久化场景
        • HibernateTransactionManager:适用于 Hibernate 持久化场景

      6.3.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(适用于大多数场景),也可自定义实现接口。

      6.3.2.1. 事务隔离级别
      • 事务并发时的安全问题:

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

        隔离级别 说明
        ISOLATION_DEFAULT 默认级别,使用数据库自身的事务隔离级别
        ISOLATION_READ_UNCOMMITTED 最低级别,会产生脏读、不可重复读和幻像读
        ISOLATION_READ_COMMITTED 避免脏读,可能出现不可重复读和幻像读(Oracle 默认级别)
        ISOLATION_REPEATABLE_READ 防止脏读、不可重复读,可能出现幻像读(MySQL 默认级别)
        ISOLATION_SERIALIZABLE 最高级别,事务顺序执行,避免所有并发问题(性能开销最大)
      6.3.2.2. 事务的传播行为
      • 定义:当一个事务方法被另一个事务方法调用时,该事务方法的执行策略(是否新建事务、是否加入已有事务等)。

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

        事务传播行为类型 说明
        PROPAGATION_REQUIRED 最常用:当前无事务则新建一个,有事务则加入
        PROPAGATION_SUPPORTS 支持当前事务,无事务则以非事务方式执行
        PROPAGATION_MANDATORY 必须在事务中执行,无事务则抛异常
        PROPAGATION_REQUIRES_NEW 新建事务,若当前有事务则挂起原事务
        PROPAGATION_NOT_SUPPORTED 以非事务方式执行,若当前有事务则挂起原事务
        PROPAGATION_NEVER 以非事务方式执行,若当前有事务则抛异常
        PROPAGATION_NESTED 有事务则在嵌套事务内执行,无事务则同 REQUIRED
      6.3.2.3. 事务超时
      • timeout:当前事务所需数据被其他事务占用时的等待时间
        • 正数(如 100):自定义等待时间(秒)
        • -1:使用数据库默认等待时间(推荐)
      6.3.2.4. 读写性
      • readonly:事务的读写属性
        • true:只读,提升查询效率(适合查询操作)
        • false:可读可写(适合增删改操作)
      6.3.2.5. 回滚规则
      • TransactionAttribute(继承 TransactionDefinition):默认实现类DefaultTransactionAttribute,指定:RuntimeException(非检查异常)触发事务回滚,CheckedException(检查异常)不触发自动回滚。

      • rollbackOn:自定义回滚规则(可省略,默认rollbackOn="Exception"

        • 抛出RuntimeException:自动回滚
        • 抛出CheckException:不自动回滚

      6.3.3.TransactionStatus

      • PlatformTransactionManager.getTransaction (…) 返回该接口对象,代表新的 / 已存在的事务,源代码如下:
      public  interface TransactionStatus{
         boolean isNewTransaction(); // 是否是新事务
         void setRollbackOnly();     // 设置事务仅回滚
         boolean isRollbackOnly();   // 事务是否标记为仅回滚
      }
      

      6.4. 改造转账案例

      6.4.1.applicationContext.xml

      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>
      

      6.4.2.service

      java

      @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();
              }
          }
      }
      

      6.4.3. 测试

      java

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

      • 正常执行效果:

      注:当前实现虽完成了事务控制,但代码耦合度高、冗余,可通过动态代理(AOP 配置式事务)简化代码。

      6.5 动态代理控制事务

      6.5.1 工厂类创建代理对象

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

      java

      package com.hg.factory;
      import com.hg.service.UserService;
      import com.hg.service.UserServiceImpl;
      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;
                      }
                  });
          }
      }
      

      6.5.2 applicationContext.xml 配置代理

      xml

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

      6.5.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);
          }
      }
      

      6.5.4 测试类

      @RunWith(SpringJUnit4ClassRunner.class)
      @ContextConfiguration("classpath:applicationContext.xml")
      public class ServiceTest {
          @Autowired
          @Qualifier("proxyService")//注入代理对象
          private UserService userService;
          
          @Test
          public void testUpdate(){
              userService.updateUser("张三丰","宋远桥",1F);
          }
      }
      
      • 事务回滚效果:

      6.6 Spring AOP 控制事务

      6.6.1 导入 schema 约束

      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"
             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">
      </beans>    
      

      6.6.2 配置事务增强

      xml

      <!-- 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>
      

      6.6.3 配置切点

      xml

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

      6.6.4 配置切面

      xml

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

      6.6.5 删除工厂类

      删除之前编写的 BeanFactory 工厂类

      6.6.6 测试

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

      6.7 事务失效的场景?【面试题】

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

        • 修改 applicationContext.xml:
        • 修改 service:
      • 问题二:如果我们在service把异常捕捉了,那么事务还会回滚吗?


      七、 基于注解的事务控制

      7.1 拷贝上一章代码

      7.2 applicationContext.xml 开启注解事务

      xml

      <!-- 开启spring对注解事务的支持,并指定对应的事务管理器 -->
      <tx:annotation-driven transaction-manager="transactionManager"/> 
      

      7.3 Service 层使用事务注解

      @Service
      @Transactional
      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);
          }
      }
      

      7.4 测试

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

      Logo

      AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

      更多推荐