Spring 5 基础

一、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 中去配置声明式事务,就需要在代码中,手动配置事务;
    • 事务在项目的开发中十分重要,涉及到数据的一致性,和完整性问题,不容马虎;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容