Spring 5 基础

作者: yjtuuige | 来源:发表于2022-03-25 21:48 被阅读0次

    一、Spring 概述

    1.1 简介

    • Spring:春天 --> 给软件行业带来了春天;

    • 2002,首次推出了 Spring 框架的雏形 interface21 框架;

    • Spring 框架,即以 interface21 框架为基础,经过重新设计,并不断丰富内涵,于 2004 年 3 月 24 日,发布了 1.0 正式版;

    • Rod Johnson:Spring Framework 创始人;

    • Spring 理念:使现有的技术更加容易使用,本身是一个大杂烩,整合了现有的技术框架。

    • SSH:Struct2 + Spring + Hibernate

    • SSM:SpringMVC + Spring + Mybatis

    • 官网:链接

    • 下载地址:链接

    • Github:链接

    • Maven 依赖:

      • 导入 webmvc 会自动导入相关依赖;
      • jdbc 用于和 Mybatis 整合;
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.17</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.3.17</version>
    </dependency>
    

    1.2 优点

    • Spring 是一个开源的免费的框架(容器);
    • Spring 是一个轻量级的、非入侵式的框架;
    • 控制反转(IOC)、面向切面编程(AOP);
    • 支持事务的处理,对框架整合的支持;
    • ==总结一句话:Spring 就是,轻量级的控制反转(IOC)和面向切面(AOP)编程的框架==

    1.3 组成

    • Spring 框架是一个分层架构,由 7 个定义良好的模块组成;

    • Spring 模块,构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式;

    • 组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现;

    模块简介:

    • 核心容器
      • 提供 Spring 框架的基本功能;
      • 主要组件是 BeanFactory,它是工厂模式的实现,使用控制反转(IOC)模式,将应用程序的配置,和依赖性规范与实际的应用程序代码分开;
    • Spring 上下文
      • 是一个配置文件,向 Spring 框架提供上下文信息,包括企业服务,例如:JNDIEJB、电子邮件、国际化、校验和调度功能;
    • Spring AOP
      • 面向切面的编程功能,可管理任何支持 AOP 的对象;
      • 基于 Spring 的应用程序中的对象,提供了事务管理服务;
      • 不用依赖组件,就可以将声明性事务管理,集成到应用程序中;
    • Spring DAO
      • JDBC DAO 抽象层,提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息;
      • 异常层次结构,简化了错误处理,降低了需要编写的异常代码数量(例如打开和关闭连接);
      • Spring DAO 的面向 JDBC 的异常,遵从通用的 DAO 异常层次结构;
    • Spring ORM
      • Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDOHibernateiBatis SQL Map
      • 遵从 Spring 的通用事务和 DAO 异常层次结构;
    • Spring Web 模块
      • 建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文;
      • Spring 框架支持与 Jakarta Struts 的集成;
      • 简化了处理多部分请求,以及将请求参数绑定到域对象的工作;
    • Spring MVC 框架
      • 全功能的构建 Web 应用程序的 MVC 实现;
      • 通过策略接口,MVC 框架变成为高度可配置的;
      • MVC 容纳了大量视图技术,其中包括 JSPVelocityTilesiTextPOI

    1.4 拓展

    • Spring Boot:

      • 快速开发的脚手架;
      • 基于 Spring Boot,可以快速的开发单个微服务;
      • 约定大于配置;
    • Spring Cloud:

      • SpringCloud 是基于 SpringBoot 实现的;
    • 学习 SpringBoot 的前提,需要完全掌握 Spring 以及SpringMVC,承上启下的作用;

    • 弊端:发展了太久之后,违背了原来的理念,配置十分繁琐;

    二、IOC 基础

    • 新建空白的 maven 项目;

    2.1 IOC 理论推导

    以前代码的实现方式:

    1. 创建 UserDao 接口:
    public interface UserDao {
        public void getUser();
    }
    
    1. 创建 UserDaoImpl 实现类 :
    public class UserDaoImpl implements UserDao{
        @Override
        public void getUser() {
            System.out.println("获取用户数据");
        }
    }
    
    1. 创建 UserService 业务接口:
    public interface UserService {
        public void getUser();
    }
    
    1. 创建 UserServiceImpl 业务实现类:
    public class UserServiceImpl implements UserService {
        private UserDao userDao = new UserDaoImpl();
        
        @Override
        public void getUser() {
            userDao.getUser();
        }    
    }    
    
    1. 测试:
    public class MyTest {
        @Test
        public void testUser() {
            UserService service = new UserServiceImpl();
            service.getUser();
        }
    }
    

    以前增加需求的实现方式:

    1. 增加 Userdao 的实现类:
    public class UserDaoOracleImpl implements UserDao{
        @Override
        public void getUser() {
            System.out.println("Oracle数据");
        }
    }
    
    1. 在 UserServiceImpl 实现类里,修改对应的实现:
    public class UserServiceImpl implements UserService {
        // 修改UserDao的对应实现类
        private UserDao userDao = new UserDaoOracleImpl();
        
        @Override
        public void getUser() {
            userDao.getUser();
        }    
    } 
    
    • 发现问题:用户的需求变化,会影响内部的实现代码,需要根据用户的需求去修改源代码;

    解决方案:

    • 使用 Set 接口,实现不同的需求:
    public class UserServiceImpl implements UserService { 
        private UserDao userDao;
    
        // 利用set进行动态实现值的注入
        public void setUserDao(UserDao userDao) {
            this.userDao = userDao;
        }
    
        @Override
        public void getUser() {
            userDao.getUser();
        }
    }
    
    • 测试:
    public class MyTest {
        @Test
        public void testUser() {
            UserServiceImpl service = new UserServiceImpl();
            service.setUserDao(new UserDaoImpl());
            service.getUser();
            // 用Oracle去实现
            service.setUserDao(new UserDaoOracleImpl());
            service.getUser();
        }
    }
    

    小结:

    • 实现方式对比:

    • 之前,程序是主动创建对象,控制权在程序员;

    • 使用了 set 注入后,程序不再具有主动性,而是变成了被动的接收对象;

    • 这种思想,从本质上解决了问题,程序员不用再去管理对象的创建,只专注于业务的实现,使系统的耦合性大大降低,这就是 IOC 的原型;

    2.2 IOC 本质

    • 控制反转 IOC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现 IOC 的一种方法(也有人认为 DI 只是 IOC 的另一种说法);

    • 没有 IOC 的程序中,使用面向对象编程,对象的创建与对象间的依赖关系,完全硬编码在程序中,对象的创建由程序自己控制,控制反转后,将对象的创建转移给第三方;

    • 所谓控制反转,就是获得依赖对象的方式反转了;

    • IOC 是 Spring 框架的核心内容,实现方式:

      • 使用 XML 配置;
      • 使用注解;
      • 新版本的 Spring 可以零配置实现 IOC;
    • Spring 容器在初始化时,先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时,再从 IOC 容器中取出需要的对象;

    • 采用 XML 方式配置 Bean 时,Bean 的定义信息是和实现分离的,而采用注解的方式,可以把两者合为一体,Bean 的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的;

    • 控制反转,是一种通过描述(XML 或注解)并通过第三方,去生产或获取特定对象的方式;

    • 在 Spring 中,实现控制反转的是 IOC 容器,其实现方法是,依赖注入(Dependency Injection,DI);

    三、Hello Spring

    3.1 搭建环境

    • 导入相关依赖,spring 需要导入commons-logging 进行日志记录,maven 会自动下载对应的依赖项;
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.17</version>
    </dependency>
    

    3.2 编码代码

    • 创建实体类:Hello.java
    public class Hello {    
        private String str;
    
        public String getStr() {
            return str;
        }
    
        public void setStr(String str) {
            this.str = str;
        }
    
        public void show() {
            System.out.println("Hello " + str);
        }
    }
    
    • 创建 spring 配置文件,beans.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
            https://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <!--bean就是java对象 , 由Spring创建和管理-->
        <!--id:对象名 class:类-->
        <bean id="hello" class="com.study.spring.pojo.Hello">
            <!--name:属性 value:值-->
            <property name="str" value="Spring"/>
        </bean>
    </beans>
    
    • 测试:
    public class MyTest {
        @Test
        public void helloTest() {
            // 获取beans.xml:拿到Spring管理对象的容器
            ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
            // genBean:参数就是Spring配置文件中bean的id(对象名)
            Hello hello = (Hello) context.getBean("hello");
            hello.show();
        }
    }
    

    3.3 思考

    • Hello 对象是谁创建?
      • 由 Spring 创建;
    • Hello 对象的属性是怎么设置的?
      • 由 Spring 容器设置的;

    这个过程就叫做 控制反转

    • 控制:控制对象的创建;

      • 传统应用程序,对象是由程序本身控制创建
      • 使用 Spring 后,对象是由 Spring 来创建
    • 反转:程序本身不创建对象,而变成被动的接收对象;

    • 依赖注入:利用 set 方法来进行注入;

    • IOC 是一种编程思想,由主动的编程,变成被动的接收;

    • 可以通过 new ClassPathXmlApplicationContext 查看底层源码;

    3.4 修改之前代码

    • IDEA 快捷创建 beans.xml 文件,自动导入 spring 配置信息:

    • 配置上下文:按提示操作

    • bean 对象添加:

    <bean id="userDaoImpl" class="com.study.spring.dao.UserDaoImpl"/>
    <bean id="oracleImpl" class="com.study.spring.dao.UserDaoOracleImpl"/>
    <bean id="service" class="com.study.spring.service.UserServiceImpl">
        <!--
            注意:
            name不是属性,而是set方法后面的那部分(首字母小写)
            ref:引用Spring容器中已经创建好的对象
            value:具体的值,基本数据类型
        -->
        <property name="userDao" ref="oracleImpl"/>
    </bean>
    
    • 测试:
    @Test
    public void testSpring() {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        UserService service = (UserService) context.getBean("service");
        service.getUser();
    }
    

    小结:

    • 要实现不同的操作,不用在程序中去改动,只需要在 xml 配置文件中进行修改;
    • 所谓的 IOC,就是对象由 Spring 来创建、管理、装配;

    四、IOC 创建对象方式

    4.1 通过无参构造(默认)

    • 创建实体类:无参构造
    public class User {
        private String name;
    
        public User() {
            System.out.println("无参构造");
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public void show() {
            System.out.println("name=" + name);
        }
    }
    
    • 创建配置文件:beans.xml
    <bean id="user" class="com.study.spring.pojo.User">
        <property name="name" value="测试"/>
    </bean>
    
    • 测试:
    @Test
    public void testUser() {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        // 在执行getBean的时候,user已经通过无参构造创建好了
        User user = (User) context.getBean("user");
        // 调用对象的方法
        user.show();
    }
    
    • 运行结果:

    • 在调用 show 方法之前,User 对象,已经通过无参构造初始化了;

    4.2 通过有参构造

    • 创建实体类:有参构造
    public class User2 {
        private String name;
    
        public User2(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
        
        public void show() {
            System.out.println("name=" + name);
        }
    }
    

    有参构造 beans.xml 的三种创建方式

    1. 下标赋值:
    <!--1:下标赋值!-->
    <bean id="user2" class="com.study.spring.pojo.User2">
        <constructor-arg index="0" value="有参测试"/>
    </bean>
    
    1. 类型赋值:不建议使用,重复类型难以分辨
    <!--2:类型赋值,不建议使用,重复类型难以分辨-->
    <bean id="user2" class="com.study.spring.pojo.User2">
        <constructor-arg type="java.lang.String" value="有参测试2"/>
    </bean>
    
    1. 参数名赋值:
    <!--3:直接通过参数名来设置-->
    <bean id="user2" class="com.study.spring.pojo.User2">    
        <constructor-arg name="name" value="有参测试3"/>
    </bean>
    
    • 测试:
    @Test
    public void testUser2() {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        User2 user2 = (User2) context.getBean("user2");
        user2.show();
    }
    

    小结:

    • 在配置文件加载时,容器中管理的对象,就已经初始化了;

    五、Spring 配置

    5.1 别名

    • alias:设置别名;
    <!--设置别名:在获取Bean的时候可以使用别名获取-->
    <alias name="user" alias="userNew"/>
    

    5.2 Bean 的配置

    • bean 就是 java 对象,由 Spring 创建和管理:
    <!--
        id:bean的唯一标识符,相当于对象名;
        如果没有配置id,name就是默认标识符,配置id后,name为别名;
        name可设多个别名,可以用逗号,分号,空格隔开;
        如果不配置id和name,可以根据applicationContext.getBean(.class)获取对象;
        class:bean对象所对应的全限定名:包名 + 类名;
    -->
    <bean id="hello" name="hello2 h2,h3;h4" class="com.study.spring.pojo.Hello">
        <property name="name" value="Spring"/>
    </bean>
    

    5.2 import

    • 一般用于团队开发,可以将多个配置文件,导入合并为一个;
    • 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">
        <!--applicationContext.xml导入其它配置文件,合并成总配置文件-->
        <import resource="beans.xml"/>
        <import resource="beans2.xml"/>
        <import resource="beans3.xml"/>
    </beans>
    

    六、依赖注入(DI)

    6.1 构造器注入

    • 查看上文;

    6.2 set 注入(重点)

    • 依赖注入(Dependency Injection,DI):set 方法注入
      • 依赖:指 Bean 对象的创建,依赖于容器;
      • 注入:Bean 对象中的所有属性,由容器来注入;

    搭建环境

    • 要求被注入的属性,必须有 set 方法:

      • 方法名由 set + 属性首字母大写;
      • 属性是 boolean 类型,没有set方法,是 is;
    • 创建实体类:复杂类型(引用类)

    public class Address {
        private String address;
        // get、set、toString
    }
    
    • 真实测试对象:
    public class Student {
        private String name;
        private Address address;
        private String[] books;
        private List<String> hobbys;
        private Map<String, String> card;
        private Set<String> games;
        private String wife;
        private Properties info;
        // get、set、toString
    }
    
    • 配置文件: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="student" class="com.study.spring.pojo.Student">
            <!--1. 普通值注入,value-->
            <property name="name" value="学生1"/>
        </bean>
    </beans>
    
    • 测试:
    @Test
    public void studentTest() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Student student = (Student) context.getBean("student");
        System.out.println(student.getName());
    }
    
    • 完善注入信息:
    <!--bean类型,引用类-->
    <bean id="address" class="com.study.spring.pojo.Address"/>
    <bean id="student" class="com.study.spring.pojo.Student">
        <!--1. 普通值注入,value-->
        <property name="name" value="学生1"/>
        <!--2. Bean注入,ref-->
        <property name="address" ref="address"/>
        <!--3. 数组注入,array-->
        <property name="books">
            <array>
                <value>西游记</value>
                <value>红楼梦</value>
                <value>水浒传</value>
            </array>
        </property>
        <!--4. list-->
        <property name="hobbys">
            <list>
                <value>爬山</value>
                <value>阅读</value>
                <value>听歌</value>
            </list>
        </property>
        <!--5. Map-->
        <property name="card">
            <map>
                <entry key="建行" value="217842215439"/>
                <entry key="工行" value="54358942439"/>
            </map>
        </property>
        <!--6. Set-->
        <property name="games">
            <set>
                <value>LOL</value>
                <value>BOB</value>
                <value>COC</value>
            </set>
        </property>
        <!--7. null-->
        <property name="wife">
            <null/>
        </property>
        <!--8. Properties-->
        <property name="info">
            <props>
                <prop key="driver">com.mysql.cj.jdbc.Driver</prop>
                <prop key="url">jdbc:mysql://localhost:3306/数据库名?</prop>
                <prop key="username">root</prop>
                <prop key="password">123456</prop>
            </props>
        </property>
    </bean>
    
    • 测试:
    @Test
    public void studentTest() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Student student = (Student) context.getBean("student");
        System.out.println(student.toString());
    
        /*
            Student{
                name='学生1',
                address=Address{address='null'},
                books=[西游记, 红楼梦, 水浒传],
                hobbys=[爬山, 阅读, 听歌],
                card={建行=217842215439, 工行=54358942439},
                games=[LOL, BOB, COC],
                wife='null',
                info={
                    password=123456,
                    driver=com.mysql.cj.jdbc.Driver,
                    url=jdbc:mysql://localhost:3306/数据库名?,
                    username=root
                    }
              }
         */
        }
    }
    

    6.3 拓展方式注入

    • 使用 cp 命名空间,进行注入;

    • 官方解释:

    • 创建实例类:

    public class User {
        private String name;
        private int age;
        // // get、set、toString
    }
    
    • 创建配置文件:beans.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:p="http://www.springframework.org/schema/p"
           xmlns:c="http://www.springframework.org/schema/c"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <!--p命名空间注入,可以直接注入属性的值:property-->
        <bean id="user" class="com.study.spring.pojo.User" p:age="20" p:name="p 测试"/>
        <!--c命名空间注入,通过构造器注入:construct-args-->
        <bean id="user2" class="com.study.spring.pojo.User" c:age="18" c:name="c 测试"/>
    </beans>
    
    • 测试:
    @Test
    public void testUser() {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        User user = (User) context.getBean("user");
        User user2 = (User) context.getBean("user2");
        System.out.println(user);
        System.out.println(user2);
    }
    
    • 注意点:
      • p 和 c 命名空间,不能直接使用,需要导入 xml 头文件约束;
      • c 命名空间,通过构造器注入,必须定义无参构造(否则报错);
    <!--头文件约束-->
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:c="http://www.springframework.org/schema/c"
    

    6.4 Bean 的作用域

    • 六种模式:

    单例模式(Spring 默认机制):singleton

    • get 到的都是同一个对象:

    • 配置:

    <bean id="user" class="com.study.spring.pojo.User" scope="singleton"/>
    
    • 测试:
    @Test
    public void testUser2() {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        User user = (User) context.getBean("user");
        User user2 = (User) context.getBean("user");
        System.out.println(user==user2);    // true
    }
    

    原型模式:prototype

    • 每次从容器中 get 时,都产生一个新的对象:

    • 配置:

    <bean id="user" class="com.study.spring.pojo.User" scope="prototype"/>
    
    • 测试:
    @Test
    public void testUser2() {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        User user = (User) context.getBean("user");
        User user2 = (User) context.getBean("user");
        System.out.println(user==user2);    // false
    }
    
    • requestsessionapplication 只能在 web 开发中使用;

    七、Bean 的自动装配

    • 自动装配是 Spring 满足 bean 依赖的一种方式;
    • Spring 会在上下文中自动寻找,并自动给 bean 装配属性;
    • Spring 中的三种装配方式:
      • 在 xml 中显式配置;
      • 在 Java 中显式配置;
      • 隐式的自动装配 bean(重点);

    7.1 搭建测试环境

    创建项目:

    • 一个人有两个宠物;

    • 实体类:Cat

    public class Cat {
        public void shout() {
            System.out.println("miao~");
        }
    }
    
    • 实体类:Dog
    public class Dog {
        public void shout() {
            System.out.println("wang~");
        }
    }
    
    • 实体类:People
    public class People {
        private Cat cat;
        private Dog dog;
        private String name;
        // get、set、toString
    }
    
    • 创建 Spring 配置文件:
    <?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="cat" class="com.study.spring.pojo.Cat"/>
        <bean id="dog" class="com.study.spring.pojo.Dog"/>
        <bean id="people" class="com.study.spring.pojo.People">
            <property name="cat" ref="cat"/>
            <property name="dog" ref="dog"/>
            <property name="name" value="测试"/>
        </bean>
    </beans>
    
    • 测试:
    @Test
    public void myTest() {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        People people = (People) context.getBean("people");
        people.getCat().shout();
        people.getDog().shout();
    }
    
    • 查看运行结果:

    7.2 ByName 自动装配

    • 修改 bean 配置,增加属性 autowire="byName"
    <!--byName:会自动在容器上下文中,查找和自己对象set方法后面的值对应的bean的id-->
    <bean id="people" class="com.study.spring.pojo.People" autowire="byName">
        <property name="name" value="测试"/>
    </bean>
    
    • 当 bean 节点有 autowire="byName" 属性时:
      • 查找此类中,所有的 set 方法名(去掉 set 后,首字母小写 );
      • 去 spring 容器中,寻找对应 set 方法名的 id 对象;
      • 如果有,就取出注入,如果没有,就报空指针异常;

    7.3 ByType 自动装配

    • 修改 bean 配置,autowire="byType"
      • 被引用的 bean 不再需要 id;
      • 类型:必须保证全局唯一,否则报错;
    <bean class="com.study.spring.pojo.Cat"/>
    <bean class="com.study.spring.pojo.Dog"/>
    <!--byType:会自动在容器上下文中查找,和自己对象属性类型相同的bean!必须保证类型全局唯一!-->
    <bean id="people" class="com.study.spring.pojo.People" autowire="byType">
        <property name="name" value="测试"/>
    </bean>
    

    小结:

    • byName
      • 必须保证所有 bean 的 id 唯一;
      • bean 需要和自动注入属性的 set 方法值一致;
    • byType
      • 必须保证所有 bean 的 class 唯一;
      • bean 需要和自动注入属性的类型一致;

    7.4 使用注解实现自动装配

    • jdk1.5 支持注解,Spring2.5 开始支持注解;

    • 使用注解实现自动装配,需重新配置 xml 头部信息:

      • 导入 context 约束;
      • 开启属性注解支持:<context:annotation-config/>
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            https://www.springframework.org/schema/context/spring-context.xsd">
    
        <!--开启属性注解支持-->
        <context:annotation-config/>
    
    </beans>
    

    @Autowired

    • @Autowired 是按类型自动转配的,不支持 id 匹配;
    • 需要导入 spring-aop 的包,或依赖;
    • 使用方式:
      • 直接在属性上使用即可;
      • 也可以在 set 方式上使用;
      • 注:使用 @Autowired 可以不用写 Set 方法,前提:
        • 自动装配的属性在 IOC(Spring)容器中存在;
        • 且符合名字 byName;
    • 修改:在类中去掉 Set 方法,使用 @Autowired 注解;
    public class People {
        @Autowired
        private Cat cat;
        @Autowired
        private Dog dog;
        private String name;
        // get、toString
    }    
    
    • 修改配置文件:
    <!--开启属性注解支持-->
    <context:annotation-config/>
    
    <bean id="cat" class="com.study.spring.pojo.Cat"/>
    <bean id="dog" class="com.study.spring.pojo.Dog"/>
    <bean id="people" class="com.study.spring.pojo.People"/>
    

    拓展

    • @Nullable:字段可以为 null

      public People(@Nullable String name) {
          this.name = name;
      }
      
    • @Autowired(required=false)

      • 默认:true(必须存在对象,不能为 null)
      // Autowired 源码
      public @interface Autowired {
          boolean required() default true;
      }
      
      • false:对象可以为 null;
      // 如果允许对象为 null,设置required = false,默认为true
      @Autowired(required = false)
      private Cat cat;
      

    @Qualifier

    • @Autowired 是根据类型自动装配,加上@Qualifier 就可以根据 byName 的方式自动装配;

    • @Qualifier 不能单独使用

    • 测试:

      • 修改配置文件内容,类型不变,名字不为类的默认名字:
    <bean id="cat1" class="com.study.spring.pojo.Cat"/>
    <bean id="dog1" class="com.study.spring.pojo.Dog"/>
    <bean id="people" class="com.study.spring.pojo.People"/>
    
    • 在属性上添加 @Qualifier 注解:
    @Autowired
    @Qualifier(value = "cat1")
    private Cat cat;
    @Autowired
    @Qualifier(value = "dog1")
    private Dog dog;
    

    小结:

    • 没有加 @Qualifier 测试,会直接报错;
    • 如果 @Autowired 自动装配的环境比较复杂,自动装配无法通过 @Autowired 一个注解完成时,可以使用 @Qualifier(value=“xxx”) 去配置 @Autowired 的使用,指定一个唯一的 bean 对象注入;

    @Resource:Java 注解

    • JDK11 以上,包被移除了,需要导入包或依赖:

      <dependency>
          <groupId>javax.annotation</groupId>
          <artifactId>javax.annotation-api</artifactId>
          <version>1.3.2</version>
      </dependency>
      
    • 修改实体类注解:

      @Resource(name = "cat11")
      private Cat cat;  
      @Resource
      private Dog dog;
      private String name;
      
    • 修改配置文件:

      <bean id="cat11" class="com.study.spring.pojo.Cat"/>
      <bean id="dog11" class="com.study.spring.pojo.Dog"/>
      <bean id="people" class="com.study.spring.pojo.People"/>
      

    小结:

    • @Resource 和 @Autowired 的区别:

      • 都是用来 自动装配 的,都可以放在属性字段上;

      • @Autowired 通过 byType 的方式实现,而且必须要求这个对象存在;【常用】

      • @Resource 默认通过 byName 的方式实现,如果找不到名字,则通过 byType 实现,如果两个都找不到的情况下,就报错;【常用】

      • 执行顺序不同@Autowired 通过 byType 的方式实现,@Resource 默认通过 byName 的方式实现;

    • 注意:byType 的类型,必须全局唯一;

    八、使用注解开发

    环境配置:

    • spring 4 之后,想要使用注解,必须要引入 aop 的包;

    • 配置文件中,要引入 context 约束,增加注解的支持:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            https://www.springframework.org/schema/context/spring-context.xsd">
        <context:annotation-config/>   
    </beans>
    

    8.1 Bean 的实现

    • 之前是使用 bean 的标签,进行 bean 注入,但实际开发中,一般会使用注解;
    • 配置扫描哪些包下的注解:
    <!--指定注解扫描包-->
    <context:component-scan base-package="com.study.spring"/>
    
    • 在 dao 包下创建类,增加注解:
    /*
        @Component:组件
        相当于配置文件中 <bean id="user" class="当前注解的类"/>
        可以指定对象名:@Component("对象名"),默认:类名首字母小写
     */
    @Component
    public class User {
        public String name;
    }
    

    8.2 属性注入

    • 不需要 set 方法,属性名上直接添加:@value("值")
    @Component
    public class User {
        // 相当于配置文件中 <property name="name" value="测试"/>
        @Value("测试")
        public String name;
    }
    
    • 如果提供了 set 方法,在 set 方法上添加 @value("值")
    @Value("测试")
    public void setName(String name) {
        this.name = name;
    }
    
    • 测试:
    @Test
    public void testUser() {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        User user = (User) context.getBean("user");
        System.out.println(user.name);
    }
    

    8.3 衍生注解

    • @Component 的衍生注解:web 开发中,按 mvc 三层架构分层;

      • @Repository:dao 层;
      • @Service:service 层;
      • @Controller:web 层;
    • 四个注解功一样:将类注册到 Spring 中,装配 Bean;

    8.4 自动装配注解

    • @Autowired
      • 自动装配,通过类型,名字;
      • 如果 @Autowired 不能唯一自动装配上属性,则需要通过 @Qualifier(value="xxx")
    • @Nullable:字段标记了这个注解,说明这个字段可以为 null;
    • @Resource(Java 注解):自动装配,通过名字,类型;

    8.5 作用域

    • @Scope:
      • singleton(默认):单例模式,创建这个对象,关闭工厂,所有的对象都会销毁;
      • prototype:原型模式,关闭工厂,所有的对象不会销毁,内部的垃圾回收机制会回收;
    @Component
    @Scope("prototype")
    public class User {
        public String name;
    }
    

    小结:

    • xml 与注解:
      • xml 更加万能,适用于任何场合,维护简单方便;
      • 注解,不是自己的类无法使用,维护相对复杂;
    • xml 与注解整合开发,推荐最佳实践方式:
      • xml 用来管理 bean
      • 注解,只负责完成属性的注入
    • 在使用的过程中,需要注意:
      • 让注解生效,必须开启注解的支持;
    <!--指定注解扫描包-->
    <context:component-scan base-package="com.study.spring"/>
    <context:annotation-config/>
    

    九、使用 Java 的方式配置 Spring

    • JavaConfig 原来是 Spring 的一个子项目;
    • 它通过 Java 类的方式,提供 Bean 的定义信息;
    • 在 Spring4 的版本,JavaConfig 已正式成为 Spring4 的核心功能;

    搭建环境

    • 创建项目;
    • 创建实体类:
    // @Component:将这个类,标注为Spring的一个组件,放到容器中
    @Component
    public class User {
        private String name;
    
        public String getName() {
            return name;
        }
    
        @Value("测试")
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }
    
    • 新建 config 配置包,创建 MyConfig 配置类:
    /*
        @Configuration:
            代表这是一个配置类,等同于beans.xml
            本身就是一个组件(@Component),
            也会被Spring容器托管,注册到容器中
     */
    @Configuration
    // 可以设置扫描包
    @ComponentScan("com.study.spring.config")
    // 可以引入其它配置类
    @Import(MyConfig2.class)
    public class MyConfig {
    
        /*
            注册一个bean,相当于bean标签
            方法名:相当于bean标签中的id属性
            方法的返回值:相当于bean标签中的class属性
         */
        @Bean
        public User getUser() {
            // 返回要注入到bean的对象
            return new User();
        }
    }
    
    • 测试:
    @Test
    public void testUser() {
        // 使用了配置类方式,只能通过AnnotationConfig上下文来获取容器,通过配置类的class对象加载
        ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        // getBean的参数为,@Bean对应的方法名,非类名首字母小写!!
        User user = (User) context.getBean("getUser");
        System.out.println(user.getName());
    }
    
    • 纯 Java 的配置方式,SpringBoot 中比较常见;

    十、代理模式

    • 为什么要学习代理模式?

      • Spring AOP 的底层就是代理模式;
    • 代理模式的分类:

      • 静态代理;
      • 动态代理;

    10.1 静态代理

    • 静态代理角色分析:
      • 抽象角色:一般使用接口或者抽象类来实现;
      • 真实角色:被代理的角色;
      • 代理角色:代理真实角色,代理后 , 会增加附属操作;
      • 客户:访问代理对象的人;

    代码实现

    • 抽象角色:租房
    // 租房
    public interface Rent {
        public void rent();
    }
    
    • 真实角色:房东
    // 房东:实现租房接口
    public class Host implements Rent {
        @Override
        public void rent() {
            System.out.println("房东出租房屋...");
        }
    }
    
    • 代理角色:代理人
    // 代理:代理房东、实现租房接口、并增加附属操作
    public class Proxy implements Rent {
        private Host host;
    
        public Proxy() {
        }
    
        // 有参构造方式,注入真实对象
        public Proxy(Host host) {
            this.host = host;
        }
    
        @Override
        public void rent() {
            seeHouse();
            // 房东租房
            host.rent();
            hetong();
            fare();
        }
    
        // 看房
        public void seeHouse() {
            System.out.println("中介带看房");
        }
    
        // 签合同
        public void hetong() {
            System.out.println("签订合同");
        }
    
        // 收中介费
        public void fare() {
            System.out.println("收中介费");
        }
    }
    
    • 客户:客户端访问代理角色
    // 客户
    public class Client {
        public static void main(String[] args) {
            // 房东要租房子
            Host host = new Host();
            // 代理:中介代理房东出租房子,但是,代理会增加附属操作
            Proxy proxy = new Proxy(host);
            // 客户不用面对房东,直接找中介即可
            proxy.rent();
        }
    }
    
    • 代理模式的好处:
      • 可以使真实角色的操作更加纯粹,不用去关注一些公共的业务;
      • 公共业务交给代理角色,实现了业务的分工;
      • 公共业务发生扩展时,方便集中管理;
    • 缺点:
      • 一个真实角色,就会产生一个代理角色,代码量会翻倍,开发效率会变低;

    10.2 加深理解

    静态代理实现 CRUD

    • 抽象角色:用户业务(增、删、改、查)
    // 抽象角色:增删改查业务
    public interface UserService {
        public void add();
    
        public void delete();
    
        public void update();
    
        public void query();
    }
    
    • 真实对象:完成增、删、改、查操作
    // 真实对象:完成增删改查操作
    public class UserServiceImpl implements UserService {
        @Override
        public void add() {
            System.out.println("增加一个用户");
        }
    
        @Override
        public void delete() {
            System.out.println("删除一个用户");
        }
    
        @Override
        public void update() {
            System.out.println("修改一个用户");
        }
    
        @Override
        public void query() {
            System.out.println("查询用户");
        }
    }
    
    • 代理角色:通过代理类,增加日志功能
    // 代理角色:增加日志的实现
    public class UserServiceProxy implements UserService {
        public UserServiceImpl userService;
    
        // set方法,注入真实对象(注入方式:有参构造、set方法)
        public void setUserService(UserServiceImpl userService) {
            this.userService = userService;
        }
    
        @Override
        public void add() {
            log("add");
            userService.add();
        }
    
        @Override
        public void delete() {
            log("delete");
            userService.delete();
        }
    
        @Override
        public void update() {
            log("update");
            userService.update();
        }
    
        @Override
        public void query() {
            log("query");
            userService.query();
        }
    
        // 日志方法
        public void log(String msg) {
            System.out.println("[Debug] 使用了" + msg + " 方法");
        }
    }
    
    • 测试:
    @Test
    public void testUser() {
        // 真实业务
        UserServiceImpl userService = new UserServiceImpl();
        // 代理类
        UserServiceProxy proxy = new UserServiceProxy();
        // 使用代理类,增加了日志功能的实现
        proxy.setUserService(userService);
        proxy.add();
        proxy.delete();
        proxy.update();
        proxy.query();
    }
    
    • 运行结果:

    • AOP 核心思想

      • 不改变原有代码,通过代理,实现对原有功能的增强

    10.3 动态代理

    • 动态代理和静态代理角色一样;

    • 动态代理的代理类,是动态生成的,不是直接写好的;

    • 动态代理分为两大类:

      • 基于接口的动态代理:JDK 动态代理;
      • 基于类的动态代理:cglib
      • Java 字节码实现:javasisit
    • JDK 动态代理,需要了解两个类:

      • Proxy:代理;

      • InvocationHandler:调用处理程序;

    InvocationHandler

    • 用来生成代理的类,不需要去为每个代理,都单独生成一个类;
    /*
        处理代理实例,并返回结果
        参数:
        proxy:代理类,代理的真实代理对象;
        method:要调用某个对象真实的方法的Method对象;
        args:代理对象方法传递的参数;
    */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(target, args);
        return result;    
    }
    

    Proxy

    • Proxy 类:用来创建一个代理对象的类;
    • 常用方法:newProxyInstance()

    代码实现

    • 抽象角色、真实角色,参照 10.2

    • 创建自动生成代理的类:

    // 用这个类,自动生成代理类
    public class ProxyInvocationHandler implements InvocationHandler {
        // 被代理的接口
        private Object target;
    
        public void setTarget(Object target) {
            this.target = target;
        }
    
        // 生成得到代理类(固定代码)
        public Object getProxy() {
            return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                    target.getClass().getInterfaces(), this);
        }
    
        @Override
        // 处理代理实例,并返回结果
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 动态代理的本质,就是使用反射机制实现
            log(method.getName());
            Object result = method.invoke(target, args);
            return result;
        }
    
        // 日志方法
        public void log(String msg) {
            System.out.println("[Debug] 使用了" + msg + " 方法");
        }
    }
    
    • 测试:
      1. 创建真实角色;
      2. 创建动态代理创建的实体;
      3. 设置;
      4. 使用;
    public class Client {
        public static void main(String[] args) {
            // 真实角色
            UserServiceImpl userService = new UserServiceImpl();
            // 代理角色,不存在,需要动态创建
            ProxyInvocationHandler pih = new ProxyInvocationHandler();
            // 设置要代理的对象
            pih.setTarget(userService);
            // 动态生成代理类
            UserService proxy = (UserService) pih.getProxy();
            proxy.add();
            proxy.delete();
            proxy.update();
            proxy.query();
        }
    }
    
    • 如果需要生成多个代理对象,只需要去创建不同的 userService 和其方法,无需再对每个代理进行设置;

    动态代理的好处

    • 可以使真实角色的操作更加纯粹,不用去关注一些公共的业务;
    • 公共业务交给代理角色,实现了业务的分工;
    • 公共业务发生扩展时,方便集中管理;
    • 一个动态代理类,代理的是一个接口,一般就是对应的一类业务;
    • 一个动态代理类,可以代理多个类,需要实现同一个接口;

    十一、AOP

    11.1 什么是 AOP

    • AOP(Aspect Oriented Programming):面向切面编程

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

    11.2 Aop 在 Spring 中的作用

    • 提供声明式事务,允许用户自定义切面

    • 横切关注点:

      • 跨越应用程序多个模块的方法或功能;
      • 与业务逻辑无关,但是需要关注的部分,就是横切关注点,如日志、安全、缓存、事务,等等;
    • 切面(ASPECT):横切关注点,被模块化的特殊对象,是一个类;

    • 通知(Advice):切面必须要完成的工作,是类中的一个方法;

    • 目标(Target):被通知对象;

    • 代理(Proxy):向目标对象应用通知之后,创建的对象;

    • 切入点(PointCut):切面通知,执行的 地点 的定义;

    • 连接点(JointPoint):与切入点匹配的执行点;

    • Spring AOP 中,通过 Advice 定义横切逻辑,Spring 中支持 5 种类型的 Advice:

    • 即 Aop 在不改变原有代码的情况下,去增加新的功能;

    11.3 使用 Spring 实现 Aop

    • 使用 AOP 织入,需要导入 aspectjweaver 依赖:
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.8</version>    
    </dependency>
    

    第一种方式:通过 Spring API 实现

    • 创建接口:
    public interface UserService {
        public void add();
    
        public void delete();
    
        public void update();
    
        public void select();
    }
    
    • 创建实现类:
    public class UserServiceImpl implements UserService{
        @Override
        public void add() {
            System.out.println("增加一个用户");
        }
    
        @Override
        public void delete() {
            System.out.println("删除一个用户");
        }
    
        @Override
        public void update() {
            System.out.println("修改一个用户");
        }
    
        @Override
        public void select() {
            System.out.println("查询用户");
        }
    }
    
    • 前置增强 MethodBeforeAdvice:AOP 增加的业务(前置日志)
    public class Log implements MethodBeforeAdvice {
        /*
            method:要执行的目标对象的方法
            args:参数
            target:目标对象
         */
        @Override
        public void before(Method method, Object[] args, Object target) throws Throwable {
            System.out.println(target.getClass().getName() + "的" + method.getName() + "被执行了");
        }
    }
    
    • 后置增强 AfterReturningAdvice:AOP 增加的业务(后置日志)
    public class AfterLog implements AfterReturningAdvice {
        @Override
        public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
            // returnValue:返回值
            System.out.println("执行了" + method.getName() + "方法,返回值:" + returnValue);
        }
    }
    
    • 在 spring 的配置文件中,注册 bean,并实现 aop 切入:
      • 头文件,需导入 Aop 约束;
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/aop
           https://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <!--注册bean-->
        <bean id="userService" class="com.study.spring.service.UserServiceImpl"/>
        <bean id="log" class="com.study.spring.log.Log"/>
        <bean id="afterLog" class="com.study.spring.log.AfterLog"/>
    
        <!--方式一:使用原生Spring API接口-->
        <!--配置AOP,需要导入Aop的约束-->
        <aop:config>
            <!--切入点:expression:表达式,execution(要执行的位置:修饰符 返回值 类名 方法名 参数 )-->
            <aop:pointcut id="pointcut" expression="execution(* com.study.spring.service.UserServiceImpl.*(..))"/>
    
            <!--advisor:环绕增加 advice-ref:对应增加方法的bean pointcut-ref:切入点-->
            <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
            <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
        </aop:config>
    </beans>
    
    • 测试:
    @Test
    public void testUser() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 增加 UserService.class 后,不再需要强制转换
        // 注意点:动态代理,代理的是接口,不是实现类
        UserService userService = context.getBean("userService", UserService.class);
        userService.add();
        userService.delete();
        userService.update();
        userService.select();
    }
    
    • 注意点:动态代理,代理的是接口,不是实现类;

    第二种方式:自定义类(切面),实现 Aop

    • 切面定义:自定义一个需要被插入的类;

    • 自定义类(切面):

    // 自定义切面(需要插入的类)
    public class DiyPointCut {
        public void before() {
            System.out.println("=========方法执行前=========");
        }
    
        public void after() {
            System.out.println("=========方法执行后=========");
        }
    }
    
    • 配置 xml 文件:
    <!--方式二:自定义类(切面),实现 Aop-->
    <!--注册bena-->
    <bean id="diy" class="com.study.spring.diy.DiyPointCut"/>
    
    <aop:config>
        <!--自定义切面,ref:要引用的类-->
        <aop:aspect ref="diy">
            <!--切入点:-->
            <aop:pointcut id="point" expression="execution(* com.study.spring.service.UserServiceImpl.*(..))"/>
            <!--通知-->
            <aop:before method="before" pointcut-ref="point"/>
            <aop:after method="after" pointcut-ref="point"/>
        </aop:aspect>
    </aop:config>
    
    • 测试:其它代码不变

    切入点位置表达式

    • 格式:
    execution(* com.service ..*.*(..))
    
    • 解释:
    符号 含义
    execution() 执行,表达式的主体
    第一个 * 表示返回值的类型任意
    com.service AOP 所切入的服务的包名,业务部分
    包名后面的 .. 表示当前包及子包
    第二个 * 表示类名,即所有类
    .*(..) 表示任何方法名,括号表示参数,两个点表示任何参数类型
    • 第二种方式,实现更加简单,但是实现的功能少了,比如:不能使用反射,获取执行方法的名称;

    第三种方式:使用注解实现

    • 创建用注解实现的增强类:
    // @Aspect:标注这个类是一个切面
    @Aspect
    public class AnnotationPointCut {
        @Before("execution(* com.study.spring.service.UserServiceImpl.*(..))")
        public void before() {
            System.out.println("=========方法执行前=========");
        }
    
        @After("execution(* com.study.spring.service.UserServiceImpl.*(..))")
        public void after() {
            System.out.println("=========方法执行后=========");
        }
    
        // 环绕增强:可以给定义一个参数,代表要获取处理切入的点
        @Around("execution(* com.study.spring.service.UserServiceImpl.*(..))")
        public void around(ProceedingJoinPoint jp) throws Throwable {
            System.out.println("环绕前");
            // getSignature():方法签名,查看目标方法名
            System.out.println(jp.getSignature());
            // 执行目标方法
            Object proceed = jp.proceed();
            System.out.println(proceed);
            System.out.println("环绕后");
        }
    }
    
    • 配置 xml 文件,注册 bean,并增加支持注解的配置:
    <!--方式三:使用注解实现-->
    <bean id="annotationPointCut" class="com.study.spring.diy.AnnotationPointCut"/>
    
    <!--
        开启注解支持:两种方式都可以实现
        JDK(默认:proxy-target-class="false")
        cglib:(proxy-target-class="true")
    -->
    <aop:aspectj-autoproxy/>
    

    十二、整合 Mybatis

    12.1 搭建环境

    导入相关依赖

    • junit:
    <!-- junit-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
    </dependency>
    
    • spring 相关:
    <!--spring相关-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.17</version>
    </dependency>
    
    • Java 注解:(JDK11 以上需要)
    <!--Java 注解:JDK11 以上需要-->
    <dependency>
        <groupId>javax.annotation</groupId>
        <artifactId>javax.annotation-api</artifactId>
        <version>1.3.2</version>
    </dependency>
    
    • mybatis:
    <!--mybatis-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.9</version>
    </dependency>
    
    • mybatis-spring 整合包 (重点)
    <!--mybatis-spring 重点-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.7</version>
    </dependency>
    
    • mysql:
    <!--mysql-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.28</version>
    </dependency>
    
    • spring-jdbc:
    <!--spring-jdbc-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.3.17</version>
    </dependency>
    
    • AOP 织入:
    <!--AOP 织入-->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.8</version>
    </dependency>
    
    • Maven 静态资源过滤:
    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>
    

    12.2 MyBatis 项目回顾

    • 创建实体类:
    public class User {
        private int id;
        private String name;
        private String pwd;
        // get、set、toString
    }
    
    • 编写核心配置文件:
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <!--configuration核心配置文件-->
    <configuration>
        <typeAliases>
            <package name="com.study.mybatis.pojo"/>
        </typeAliases>
    
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                    <property name="url"
                              value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=true"/>
                    <property name="username" value="root"/>
                    <property name="password" value="123456"/>
                </dataSource>
            </environment>
        </environments>
        <!--注册 Mapper.xml-->
        <mappers>
            <mapper class="com.study.mybatis.mapper.UserMapper"/>
        </mappers>
    </configuration>
    
    • 创建接口类:
    public interface UserMapper {
        public List<User> selectUser();
    }
    
    • 创建 Mapper.xml
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.study.mybatis.mapper.UserMapper">
        <select id="selectUser" resultType="user">
            select * from user;
        </select>
    </mapper>
    
    • 测试:
    @Test
    public void testUser() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream in = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
    
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = mapper.selectUser();
        for (User user : userList) {
            System.out.println(user);
        }
    
        sqlSession.close();
    }
    
    • 运行结果:

    12.3 MyBatis-Spring

    • 官方文档

    • MyBatis-Spring:将 MyBatis 代码,无缝整合到 Spring 中;

    • MyBatis-Spring 对应版本:

    • MyBatis-Spring 相关依赖:

    <!--mybatis-spring 重点-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.7</version>
    </dependency>
    
    • 要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义:
      • 一个 SqlSessionFactory
        • SqlSessionFactory 需要一个 DataSource(数据源);
      • 至少一个数据映射器类;

    整合实现方式一:SqlSessionTemplate

    • 创建 Mybatis 配置文件:mybatis-config.xml
    <!--Spring配合MyBatis,一般在MyBatis设置别名和set,其它在spring中设置-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    <typeAliases>
        <package name="com.study.mybatis.pojo"/>
    </typeAliases>
    
    • 创建配置文件:spring-dao.xml
      • 替换 mybaits 的数据源;
      • 配置 SqlSessionFactory,关联 MyBatis;
      • 注册 sqlSessionTemplate,关联 sqlSessionFactory;
    <?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">
    
        <!--DataSource:使用Spring的数据源替换Mybatis的配置,如:c3p0 dbcp druid
            这里使用Spring-Jdbc:DriverManagerDataSource
        -->
        <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
            <property name="url"
                      value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=true"/>
            <property name="username" value="root"/>
            <property name="password" value="123456"/>
        </bean>
    
        <!--SqlSessionFactory(固定格式)-->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <!--数据源-->
            <property name="dataSource" ref="dataSource"/>
            <!--绑定Mybatis配置文件-->
            <property name="configLocation" value="classpath:mybatis-config.xml"/>
            <property name="mapperLocations" value="classpath:com/study/mybatis/mapper/*.xml"/>
        </bean>
    
        <!--SqlSessionTemplate:SQLSession(固定格式)-->
        <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
            <!--只能使用构造器注入sqlSessionFactory,因为没有set方法-->
            <constructor-arg index="0" ref="sqlSessionFactory"/>
        </bean>
    </beans>
    
    • 增加 UserMapper 接口的实现类,私有化 sqlSessionTemplate:
    public class UserMapperImpl implements UserMapper {
        // 以前使用SqlSession操作,现在都使用SqlSessionTemplate
        private SqlSessionTemplate sqlSession;
    
        public void setSqlSession(SqlSessionTemplate sqlSession) {
            this.sqlSession = sqlSession;
        }
    
        // 在接口实现类中,实现以前测试类的方法,并将方法值返回(使用时在spring中直接调用方法即可)
        @Override
        public List<User> selectUser() {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            return mapper.selectUser();
        }
    }
    
    • 创建总配置文件:applicationContext.xml
      • 引入 spring-dao.xml
      • 注册实现类:UserMapperImpl
    <?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">
    
        <import resource="spring-dao.xml"/>
    
        <bean id="userMapper" class="com.study.mybatis.mapper.UserMapperImpl">
            <property name="sqlSession" ref="sqlSession"/>
        </bean>
    </beans>
    
    • 测试:
    @Test
    public void testSpringMybatis() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
        for (User user : userMapper.selectUser()) {
            System.out.println(user);
        }
    }
    

    整合实现方式二:继承 SqlSessionDaoSupport

    • mybatis-spring1.2.3 版以上的才有;
    • dao 层通过继承 Support 类,直接利用 getSqlSession() 获得,然后直接注入 SqlSessionFactory;
    • 与方式一相比,不需要管理 SqlSessionTemplate,而且对事务的支持更加友好,可跟踪源码查看;

    代码实现:

    • 创建 UserMapper 接口实现类:
    public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper {
        @Override
        public List<User> selectUser() {
            // getSqlSession()直接获取,不需要创建
            return getSqlSession().getMapper(UserMapper.class).selectUser();
        }
    }
    
    • 注册 bean:
    <!--SqlSessionDaoSupport实现,只需要sqlSessionFactory-->
    <bean id="userMapper2" class="com.study.mybatis.mapper.UserMapperImpl2">
        <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean>
    
    • 测试:
    @Test
    public void testSpringMybatis2() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserMapper userMapper = context.getBean("userMapper2", UserMapper.class);
        for (User user : userMapper.selectUser()) {
            System.out.println(user);
        }
    }
    

    十三、声明式事务

    13.1 回顾事务

    • 事务:把一系列的动作,当成一个独立的工作单元,这些动作要么全部完成,要么全部不起作用;

    • 事务四个属性:ACID

      • 原子性(atomicity):事务是原子性操作,由一系列动作组成,事务的原子性,确保动作要么全部完成,要么完全不起作用;
      • 一致性(consistency):一旦所有事务动作完成,事务就要被提交,数据和资源处于一种,满足业务规则的一致性状态中;
      • 隔离性(isolation):可能多个事务,会同时处理相同的数据,因此每个事务,都应该与其他事务隔离开来,防止数据损坏;
      • 持久性(durability):事务一旦完成,无论系统发生什么错误,结果都不会受到影响,通常情况下,事务的结果,被写到持久化存储器中;

    未开启事务测试:

    • 复制上例中的代码到新项目中;
    • 在接口 UserMapper 中新增两个方法,增加和删除用户:
    // 增加用户
    public int addUser(User user);
    // 删除用户
    public int deleteUser(int id);
    
    • 修改 Mapper.xml 文件,把 deletes 写错(模拟程序出错)
    <insert id="addUser" parameterType="User">
        insert into user (id, name, pwd)
        values (#{id}, #{name}, #{pwd});
    </insert>
    
    <delete id="deleteUser" parameterType="int">
        <!--deletes 写错,模拟程序出错-->
        deletes from user where id = #{id};
    </delete>
    
    • 在接口实现类,添加增加、删除对应的方法:
    public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper {
        @Override
        public List<User> selectUser() {
            User user = new User(4, "小王", "123");
            UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
            mapper.addUser(user);
            mapper.deleteUser(1);
            return mapper.selectUser();
        }
    
        @Override
        public int addUser(User user) {
            return getSqlSession().getMapper(UserMapper.class).addUser(user);
        }
    
        @Override
        public int deleteUser(int id) {
            return getSqlSession().getMapper(UserMapper.class).deleteUser(id);
        }
    }
    
    • 测试:
    @Test
    public void testSpringMybatis() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
        for (User user : userMapper.selectUser()) {
            System.out.println(user);
        }
    }
    
    • 运行结果:

      • 报错:sql 异常,delete 写错了;

      • 结果:插入成功;

    小结:

    • 没有进行事务的管理,程序部分出现错误,依旧可以提交;
    • 如果想让一组程序,都成功时才成功,有一个失败,就都失败,就需要事务;

    13.2 Spring 中的事务管理

    • 需要用到的依赖:

      • aspectjweaver

        <!--AOP 织入-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.8</version>
        </dependency>
        
      • spring-jdbc

        <!--spring-jdbc-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.3.17</version>
        </dependency>
        
    • Spring 在不同的事务管理 API 之上定义了一个抽象层,使得开发人员,不必了解底层的事务管理 API,就可以使用 Spring 的事务管理机制;

    • Spring 支持:

      • 编程式事务管理:
        • 代码嵌到业务方法中,来控制事务的提交和回滚;
        • 缺点:必须在每个事务操作业务逻辑中,包含额外的事务管理代码;
      • 声明式事务管理:AOP
        • 代码与业务方法分离,以声明的方式,来实现;
        • 将事务管理,作为横切关注点,通过 AOP 方法模块化;
        • Spring 中通过 Spring AOP 框架,支持声明式事务 管理;

    spring 七种事务类型

    事务类型 说明
    REQUIRED 默认)支持当前事务,无事务,另起新事物
    SUPPORTS 支持当前事务,无事务,以非事务执行
    MANDATORY 以事务方式执行,无事务,抛异常
    REQUIRES_NEW 新建事务,若有旧事务,挂起
    NOT_SUPPORTED 不支持事务,如有事务,挂起
    NEVER 以非事务执行,有事务,抛异常
    NESTED 内切事务

    配置 spring-dao.xml 文件

    • 使用 Spring 管理事务,注意头文件的约束导入:tx
    xmlns:tx="http://www.springframework.org/schema/tx"
    
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx.xsd
    
    • 配置声明式事务:JDBC 事务
    <!--配置声明式事务-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    • 配置事务的通知:
    <!--结合AOP实现事务的织入-->
    <!--配置事务通知-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--给哪些方法配置事务-->
        <!--配置事务的传播特性,propagation:传播-->
        <tx:attributes>
            <tx:method name="insert" propagation="REQUIRED"/>
            <tx:method name="delete" propagation="REQUIRED"/>
            <tx:method name="update" propagation="REQUIRED"/>
            <tx:method name="query" read-only="true"/>
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>
    
    • 配置事务切入:AOP(注意导入头文件)
    <!--配置事务切入-->
    <aop:config>
        <aop:pointcut id="txPointCut" expression="execution(* com.study.mybatis.mapper.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
    </aop:config>
    
    • 测试:

      • 运行出现异常(deletes 写错);

      • 查看数据库,未插入成功;

      • 更改删除语句后,运行正常;

    小结:

    • 为什么需要配置事务:
      • 如果不配置事务,可能存在数据提交不一致的情况;
      • 如果不在 Spring 中去配置声明式事务,就需要在代码中,手动配置事务;
      • 事务在项目的开发中十分重要,涉及到数据的一致性,和完整性问题,不容马虎;

    相关文章

      网友评论

        本文标题:Spring 5 基础

        本文链接:https://www.haomeiwen.com/subject/vindjrtx.html