Spring5 学习笔记

Spring5 学习笔记

目录

1. 简介

  • Spring:春天 --> 给 Java 程序员带来了春天
  • 2002年,首次推出了 Spring 框架的雏形:interface 21框架
  • Spring框架即以 interface 21 框架为基础,并不断丰富其内涵,于2004年3月24日,发布了1.0正式版
  • Rod Johnson ,Spring Framework 创始人,著名作者,很难想象 Rod Johnson 的学历,真的让很多人大吃一惊,他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学。
  • Spring 理念:使现有的技术更加容易使用,本身就是个大杂烩

常用的与 Spring 整合的框架

  • SSH:Struct2 + Spring + Hibernate

  • SSM:SpringMvc + Spring + MyBatis

官网:https://spring.io/

官方下载地址: http://repo.spring.io/release/org/springframework/spring

GitHub地址:https://github.com/spring-projects/spring-framework

常用依赖配置

<dependencies>
    <!-- Spring MVC -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.2.3.RELEASE</version>
    </dependency>
    <!-- Spring java数据库访问包 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.2.3.RELEASE</version>
    </dependency>
    <!-- 测试 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    <!-- 织入包 -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>LATEST</version>
    </dependency>
    <!-- mysql驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>LATEST</version>
    </dependency>
    <!-- 与MyBatis整合 -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.3</version>
    </dependency>
</dependencies>

1.1 优点

  • Spring 是一个开源免费的框架(容器)!
  • Spring 是一个轻量级,非侵入式的框架!
  • 控制反转(IoC),面向切面编程(AOP)!
  • 支持事务的处理,对框架整合的支持!

总结一句话:Spring 就是一个轻量级的控制反转(IoC)和面向切面编程(AOP)的框架!

1.2 组成

Spring5 学习笔记

1.3 拓展

在 Spring的官网有这个介绍,现代化的 java开发!说白了就是基于 Spring 的开发!

Spring5 学习笔记

  • Spring Boot
    • 一个快速开发的脚手架
    • 基于 SpringBoot可以快速的开发单个微服务
    • 约定大于配置
  • Spring Cloud
    • SpringCloud 是基于 SpringBoot 实现的

现在大多数公司都在使用 SpringBoot 进行快速开发。学习 SpringBoot 的前提,需要完全掌握 Spring 及 SpringMVC 。承上启下的作用!

1.4 弊端

发展了太久之后,违背了原来的理念!配置十分繁琐,人称配置地狱

2. IoC理论推导

以前的写法

  1. UserDao 接口
/**
 * 用户Dao
 */
public interface UserDao {
    void getUser();
}
  1. UserDaoImpl 实现类
/**
 * 用户Dao实现类
 */
public class UserDaoImpl implements UserDao {
    @Override
    public void getUser() {
        System.out.println("获取用户数据");
    }
}
  1. UserService
/**
 * 用户业务接口
 */
public interface UserService {
    void getUser();
}
  1. UserServiceImpl 业务实现类
/**
 * 用户业务层实现类
 */
public class UserServiceImpl implements UserService {
    private UserDao userDao = new UserDaoImpl();

    @Override
    public void getUser() {
        userDao.getUser();
    }
}
  1. 测试
public class UserServiceTest {
    @Test
    public void getUser() {
        UserService userService = new UserServiceImpl();
        
        userService.getUser();
    }
}

但是,当我们需要增加实现类的时候,如增加 UserDaoMySqlImpl 类时,我们要去修改源代码来实现 UserDaoMySqlImpl 类的功能

/**
 * 增加的 UserMySqlImpl 实现类
 */
public class UserMySqlImpl implements UserDao {
    @Override
    public void getUser() {
        System.out.println("mysql");
    }
}

这时我们需要在 UserServiceImpl 类中修改代码

private UserDao userDao = new UserMySqlImpl();

如果我们还需要增加新的实现类 UserDaoOracleImpl ,则仍需要修改源代码。很明显,这样的代码并不是优秀的代码。当代码量十分大的时候,修改一次代码的成本代价就会十分昂贵牵一发而动全身,这并不是我们想要的。

Spring5 学习笔记

现在,我们使用一个Set接口实现,这时已经发生了革命性的变化!

// 聚合UserDao接口
private UserDao userDao ;

// 利用Set动态实现值的注入
public void setUserDao(UserDao userDao) {
    this.userDao = userDao;
}

增加Set方法的前后对比

  • 在没有增加这个Set方法时,程序是主动创建对象的。控制权在程序员手上!

  • 使用了Set注入后,程序不再具有主动性,而是变成了被动接收对象!这就是最开始的控制反转,这就是IoC的原型

很明显,增加了 set 方法后,代码变得灵活了。对象不再是程序员来控制或者修改,而是将主动权交由用户,按照用户的需要去创建和修改对象。

这种思想从本质上解决了问题,我们程序员不用再去管理对象的创建了。系统的耦合性大大降低,可以更加专注的在业务的实现上。

Spring5 学习笔记

IoC本质

控制反转 IoC (Inversion of Control),是一种设计思想,DI(依赖注入)是实现 IoC 的一种方法,也有人认为 DI 只是 IoC 的另一种说法。没有 IoC 的程序中,我们使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制。控制反转后,将对象的创建转移给第三方。个人认为所谓控制反转,就是:获得依赖对象的方式反转了。

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

控制反转是一种一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在 Spring 中实现控制反转的是 IoC 容器,其实现方法是依赖注入(Dependency Injection,DI)。

3. 创建 Spring 项目

  1. 导入依赖
<dependencies>
    <!-- Spring MVC -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.2.3.RELEASE</version>
    </dependency>
    <!-- 测试 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>
  1. 创建实体类
public class Hello {
    private String str;

    public Hello() {
    }

    public Hello(String str) {
        this.str = str;
    }

    public String getStr() {
        return str;
    }

    public void setStr(String str) {
        this.str = str;
    }

    @Override
    public String toString() {
        return "Hello{" +
                "str='" + str + '\'' +
                '}';
    }
}
  1. 配置Bean

在 resource 目录下创建 applicationContext.xml,并配置Bean

<?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">

    <!-- 使用Spring来创建对象,在Spring这些都称为Bean
        bean = 对象 new Hello();
        id = 变量名
        class = new 的对象
        property 相当于给对象中的属性赋值
    -->
    <bean class="com.xp.pojo.Hello" id="hello">
        <property name="str" value="Spring"/>
    </bean>

</beans>
  1. 测试
public class MyTest {
    @Test
    public void main(){
        // 获取Spring上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        // 我们的对象现在都在Spring中被管理了,我们要使用,直接去里面取出来就可以了
        Hello hello = context.getBean("hello", Hello.class);
        System.out.println(hello.toString());
    }
}

将之前的项目修改成Spring项目

  1. 创建 applicationContext.xml
  2. 配置 Bean
<?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 id="userDaoImpl" name="user" class="com.xp.dao.impl.UserDaoImpl"/>
    <bean id="userDaoMySqlImpl" name="mysql" class="com.xp.dao.impl.UserMySqlImpl"/>
    <bean id="userDaoOracleImpl" name="oracle" class="com.xp.dao.impl.UserOracleImpl"/>
    
    <!--
        ref:引用Spring容器中创建好的对象
        value:具体的值,基本数据类型!
    -->
    <bean id="userServiceImpl" name="userServiceImpl" class="com.xp.service.impl.UserServiceImpl">
        <property name="userDao" ref="mysql"/>
    </bean>

</beans>
  1. 测试
public class MyTest {
    public static void main(String[] args) {
        // 获取ApplicationContext,拿到Spring的IoC容器
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
       // 容器在手,天下我有,需要什么,就直接get什么
        UserService userService = context.getBean("userServiceImpl", UserServiceImpl.class);

        userService.getUser();
    }
}

由上可以看出,我们创建对象不需要自己手动 new 一个对象出来,而是通过配置 Bean ,交由 Spring 的 IoC 容器去创建。并且创建哪个对象这是由用户决定的,用户不需要修改代码,只需要在 xml 配置文件中修改属性即可。

到了现在,我们彻底不用在程序中改动了,要实现不同的操作,只需要在 xml 配置文件中进行修改,所谓的 IoC ,一句话搞定:对象由 Spring 来创建,管理,装配!

4. IoC创建对象的方式

实体类如下

public class User {
    private String name;

    public User(){
        System.out.println("User 的无参构造");
    }

    public User(String name) {
        this.name = name;
        System.out.println("User 的有参构造");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
  1. 使用无参构造创建对象,(默认)

    <bean id="user" name="user" class="com.xp.pojo.User">
        <property name="name" value="xp"/>
    </bean>
    
  2. 要使用有参构造创建对象

    1. 下标赋值

      <bean id="user" class="com.xp.pojo.User">
          <constructor-arg index="0" value="java"/>
      </bean>
      
    2. 类型赋值

      这种方法具有局限性,不适用有多个同类型的参数的实体类

      <bean id="user" class="com.xp.pojo.User">
          <constructor-arg type="java.lang.String" value="string"/>
      </bean>
      
    3. 参数名赋值

      <bean id="user" class="com.xp.pojo.User">
          <constructor-arg name="name" value="name"/>
      </bean>
      

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

配置文件 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
        https://www.springframework.org/schema/beans/spring-beans.xsd">
	<!-- 对象1,调用了有参构造创建对象 -->
    <bean id="user1" class="com.xp.pojo.User">
        <constructor-arg index="0" value="java"/>
    </bean>
	<!-- 对象2,调用了有参构造创建对象 -->
    <bean id="user2" class="com.xp.pojo.User">
        <constructor-arg name="name" value="name"/>
    </bean>

</beans>

测试类如下

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        User user = context.getBean("user1",User.class);

        System.out.println(user.getName());
    }
}

控制台输出如下

有参构造调用了两次,代表我们刚刚配置的两个 Bean 全部被 Spring 创建了

Spring5 学习笔记

debug时参数

IoC 容器默认会创建、管理的所有 Bean 的单例对象

Spring5 学习笔记

可以看出,只要是在配置文件中配置了 Bean 并交给Spring管理,则在配置文件加载时,容器中管理的对象就已经初始化了

5. Spring配置

5.1 别名

<!-- 别名,如果添加了别名,我们也可以使用别名获取到这个对象 -->
<alias name="user1" alias="user"/>

5.2 Bean的配置

<!--
 id:bean 的唯一标识符,也就是相当于我们平时写的对象名
 class:bean 对象所对应的全限定名 : 包名+类型
 name:也是别名,而且name可以取多个别名,空格,逗号,分号都可以做分隔符
 -->
<bean id="user2" class="com.xp.pojo.User" name="user"/>

5.3 import

一般用于团队开发使用,他可以将多个配置文件,导入合并为一个

假设:现在项目中有多个人开发,这三个人负责不同的类开发,不同类需要注册在不同的 Bean 中,我们可以利用 import 将所有人的 bean.xml 合并为一个总的

  • 张三开发 bean1.xml

  • 李四开发 bean2.xml

  • 王五开发 bean3.xml

  • applicationContext.xml

    <import resource="bean1.xml"/>
    <import resource="bean2.xml"/>
    <import resource="bean3.xml"/>
    

使用的时候,使用总的配置 applicationContext.xml 就可以了

6. 依赖注入

6.1 构造器注入

上面第四点 4. IoC创建对象的方式 已经讲了

6.2 Set方式注入【重点】

  • 依赖注入:本质就是set注入
    • 依赖:Bean 对象的创建依赖于容器
    • 注入:Bean 对象中的所有属性,由容器注入
  1. 创建实体类

    Student.java

    @data
    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;
    }
    

    Address.java

    @data
    public class Address {
        private String address;
    }
    
  2. 配置 Bean

    <beans>
        <bean id="student" class="com.xp.pojo.Student">
            <!-- 第一种注入:普通注入,value -->
            <property name="name" value="xp"/>
    
            <!-- 第二种注入:Bean 注入 ,ref-->
            <property ref="address" name="address"/>
    
            <!-- 数组注入 -->
            <property name="books">
                <array>
                    <value>红楼梦</value>
                    <value>西游记</value>
                    <value>水浒传</value>
                    <value>三国演义</value>
                </array>
            </property>
    
            <!-- List -->
            <property name="hobbys">
                <list>
                    <value>听歌</value>
                    <value>敲代码</value>
                    <value>看电影</value>
                </list>
            </property>
    
            <!-- Map -->
            <property name="card">
                <map>
                    <entry key="身份证" value="441622111111111111"/>
                    <entry key="银行卡" value="111111111111111111"/>
                </map>
            </property>
    
            <!-- Set -->
            <property name="games">
                <set>
                    <value>LOL</value>
                    <value>DNF</value>
                    <value>CF</value>
                </set>
            </property>
    
            <!-- null -->
            <property name="wife">
                <null/>
            </property>
    
            <!--properties-->
            <property name="info">
                <props>
                    <prop key="学号">1740111111</prop>
                    <prop key="年龄">22222222</prop>
                    <prop key="性别">男</prop>
                </props>
            </property>
        </bean>
    
        <bean id="address" class="com.xp.pojo.Address">
            <property name="address" value="广东省"/>
        </bean>
    </beans>
    
  3. 测试

    public class MyTest2 {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            Student student = context.getBean("student", Student.class);
    
            System.out.println(student.toString());
        }
    }
    

6.3 拓展方式注入

我们可以使用 p 命名空间和 c 命名空间进行注入

6.3.1 p 命名空间

  1. 导入 xml 约束
xmlns:p="http://www.springframework.org/schema/p"
  1. 配置 Bean
<!-- p命名空间注入,可以直接注入属性的值:property -->
<bean id="user" class="com.xp.pojo.User" p:name="p namespace"/>
  1. 测试
public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        User user = context.getBean("user",User.class);

        System.out.println(user.getName());
    }
}

6.3.2 c 命名空间

  1. 导入 xml 约束
xmlns:p="http://www.springframework.org/schema/p"
  1. 配置 Bean
<!-- c命名空间注入,通过构造器注入 :constructor-arg -->
<bean id="user0" class="com.xp.pojo.User" c:name="c namespace"/>
  1. 测试
public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        User user = context.getBean("user0",User.class);

        System.out.println(user.getName());
    }
}

注意点:p 命名空间和 c 命名空间不能直接使用,需要导入 xml 约束

6.4 Bean 的作用域

作用域 描述
singleton(单例) (默认)singleton(单例)的 Bean 作用域为每个 Spring IoC 容器的单个对象实例
prototype(原型) 将单个 Bean 的作用域限定为任意数量的对象
request(请求) 将单个 Bean 的作用域限定为单个 HTTP 请求的声明周期。也就是说,每个HTTP请求都有自己的 Bean 实例,这些实例是在单个 Bean 定义的基础上创建的。仅在可感知web的Spring应用程序上下文中有效。
session(会话) 将单个 Bean 作用域限定为 HTTP Session 的生命周期。仅在可感知 web 的 Spring 应用程序上下文中有效。
application(应用) 将单个 Bean 作用域限定为 ServletContext 的生命周期。仅在可感知 web 的 Spring 应用程序上下文中有效。
websocket(web套接字) 将单个 Bean 作用域限定为 websocket 的生命周期。仅在可感知 web 的 Spring 应用程序上下文中有效。

官网原文

Spring5 学习笔记

6.4.1 单例模式(Spring默认机制)

Spring 配置的 Bean 默认是单例模式

Spring5 学习笔记

<!-- p命名空间注入,可以直接注入属性的值:property,作用域设置为单例模式(默认也是单例模式) -->
<bean id="user" class="com.xp.pojo.User" p:name="p namespace" scope="singleton"/>

测试

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        User user = context.getBean("user",User.class);
        User user1 = context.getBean("user",User.class);

        System.out.println(user == user1);
    }
}

当 Bean 的作用域设置为单例模式(singleton)或默认不设置作用域时,通过 getBean() 方法获取的 Bean 一直是同一个对象,控制台输出为 true

6.4.2 原型模式

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

Spring5 学习笔记

<!-- c命名空间注入,通过构造器注入 :constructor-arg,作用域设置为原型模式 -->
<bean id="user0" class="com.xp.pojo.User" c:name="c namespace" scope="prototype"/>

测试

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        User user = context.getBean("user0",User.class);
        User user1 = context.getBean("user0",User.class);

        System.out.println(user == user1);
    }
}

当 Bean 的作用域设置为原型模式(prototype)时,每次调用 getBean() 方法获取的 Bean 都是不同的对象。控制台输出为 false。

6.4.3 其余的 request、session、application、websocket

这些只能在 web 开发中使用!

7. Bean的自动装配

  • 自动装配是 Spring 满足 Bean 依赖的一种方式
  • Spring 会再上下文中自动寻找,并自动给 Bean 装配属性

在Spring中有三种装配方式

  1. 在 xml 中显示地配置
  2. 在 java 中显示地配置
  3. 隐式地自动装配 Bean 【重要】

7.1 环境搭建

一个人有两个宠物,宠物都有 shot 方法

创建实体类

/**
 * 猫
 */
class Cat {
    public void shot(){
        System.out.println("miao~");
    }
}

/**
 * 狗
 */
class Dog {
    public void shot(){
        System.out.println("wang~");
    }
}

/**
 * 人
 */
public class People {
    private String name;
    private Cat cat;
    private Dog dog;

    public People() {
    }

    public People(String name, Cat cat) {
        this.name = name;
        this.cat = cat;
    }

    public String getName() {
        return name;
    }

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

    public Cat getCat() {
        return cat;
    }

    public void setCat(Cat cat) {
        this.cat = cat;
    }

    public Dog getDog() {
        return dog;
    }

    public void setDog(Dog dog) {
        this.dog = dog;
    }

    @Override
    public String toString() {
        return "People{" +
                "name='" + name + '\'' +
                ", cat=" + cat +
                ", dog=" + dog +
                '}';
    }
}

7.2 通过byName自动装配

<bean id="dog" class="com.xp.pojo.Dog"/>
<bean id="cat" class="com.xp.pojo.Cat"/>

<!-- byName:会自动在容器上下文中查找,和自己对象set方法后面值对应的 Bean id -->
<bean id="person" class="com.xp.pojo.People" autowire="byName">
    <property name="name" value="xp"/>
</bean>

需要保证所有 Bean 的id唯一,并且这个 Bean 需要和自动注入的属性的 set 方法的值一致

7.3 通过byType自动装配

<bean id="dog22" class="com.xp.pojo.Dog"/>
<bean id="cat11" class="com.xp.pojo.Cat"/>

<!-- byType:会自动在容器上下文中查找,和自己对象类型相同的 Bean id -->
<bean id="person" class="com.xp.pojo.People" autowire="byType">
    <property name="name" value="xp"/>
</bean>

需要保证所有 Bean 的 class 唯一,并且这个 Bean 需要和自动注入的属性的类型一致

7.4 使用注解自动装配

jdk 1.5 开始支持注解,Spring 2.5 开始支持注解。

Are annotations better than XML for configuring Spring?
The introduction of annotation-based configuration raised the question of whether this approach is “better” than XML. The short answer is “it depends.” The long answer is that each approach has its pros and cons, and, usually, it is up to the developer to decide which strategy suits them better. Due to the way they are defined, annotations provide a lot of context in their declaration, leading to shorter and more concise configuration. However, XML excels at wiring up components without touching their source code or recompiling them. Some developers prefer having the wiring close to the source while others argue that annotated classes are no longer POJOs and, furthermore, that the configuration becomes decentralized and harder to control.

No matter the choice, Spring can accommodate both styles and even mix them together. It is worth pointing out that through its JavaConfig option, Spring lets annotations be used in a non-invasive way, without touching the target components source code and that, in terms of tooling, all configuration styles are supported by the Spring Tools for Eclipse.

在配置Spring时,注释是否比XML更好?

基于注释的配置的引入提出了一个问题,即这种方法是否优于 XML 。简而言之,这要视情况而定。长篇大论的回答是,每种方法都有其优点和缺点,通常由开发人员决定哪种策略更适合他们。由于它们的定义方式,注释在其声明中提供了大量上下文,从而使配置更短、更简洁。然而,XML擅长在不接触源代码或不重新编译它们的情况下连接组件。然而,XML擅长在不接触源代码或不重新编译它们的情况下连接组件。一些开发人员喜欢将连接放在接近源的地方,而另一些人则认为带注释的类不再是 pojo,而且配置变得分散,更难控制。

无论选择什么,Spring 都可以容纳两种样式,甚至可以将它们混合在一起。 值得指出的是,通过其 JavaConfig 选项,Spring 允许以非侵入方式使用批注,而无需接触目标组件的源代码,并且就工具而言,Spring Tools for Eclipse 支持所有配置样式。

7.4.1 导入context约束,并开启注解支持

<?xml version="1.0" encoding="UTF-8"?>

<!--
 导入context约束
 xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation=" http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
 -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        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/>
    <!-- 扫描指定包下的类,并注册到Spring容器中 -->
    <context:component-scan base-package="com.xp"/>

</beans>

7.4.2 @Autowired

直接在属性上使用,也可在set方法上使用

使用 Autowired 我们可以不用编写 set 方法了,前提是你这个自动装配的属性 在 IoC(Spring)容器中存在,且符合 byName 名字

  1. 在实体类上增加注解
/**
 * 人
 */
public class People {
    private String name;
    @Autowired
    private Cat cat;
    @Autowired
    private Dog dog;

    public Cat getCat() {
        return cat;
    }

    public Dog getDog() {
        return dog;
    }
}
  1. 配置 bean
<?xml version="1.0" encoding="UTF-8"?>

<!--
 导入context约束
 xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation=" http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
 -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        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/>

    <bean id="dog" class="com.xp.pojo.Dog"/>
    <bean id="cat" class="com.xp.pojo.Cat"/>

    <!-- byType:会自动在容器上下文中查找,和自己对象类型相同的 Bean id -->
    <bean id="people" class="com.xp.pojo.People">
        <property name="name" value="xp"/>
    </bean>

</beans>
  1. 测试
public class MyTest3 {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans2.xml");
        People people = context.getBean("people", People.class);

        people.getCat().shot();
        people.getDog().shot();
    }
}

科普

@Nullable 字段标记了这个注解,说明这个字段可以为null

进入 @Autowired 源码,里面有个 required 属性,默认为true。

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    boolean required() default true;
}

如果显示定义了 Autowired 的 required 属性为 false,说明这个对象可以为 null,否则不允许为空

@Autowired(required = false)
private Cat cat;

7.4.3 @Qualifier

@Autowired 和 @Qualifier(value="xxx") 结合使用

如果 @Autowired 自动装配的环境比较复杂,自动装配无法通过一个注解【@Autowired】完成的时候,我们可以使用 @Qualifier(value="xxx") 去配置

给实体类增加 @Qualifier(value="xxx") 注解

    @Autowired
    @Qualifier(value = "dog11")
    private Dog dog;

xml 中 Bean 的配置

<!-- 开启注解支持 -->
<context:annotation-config/>

<bean id="dog11" class="com.xp.pojo.Dog"/>
<bean id="cat" class="com.xp.pojo.Cat"/>

<!-- byType:会自动在容器上下文中查找,和自己对象类型相同的 Bean id -->
<bean id="people" class="com.xp.pojo.People">
    <property name="name" value="xp"/>
</bean>

7.4.4 @Resource 注解

@Resource(name = "cat11")
private Cat cat;
@Resource
private Dog dog;

@Resource 注解和 @Autowired 的区别

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

  • @Autowired 默认通过 byType 的方式注入的,但当类型大于1时,会以 byName 的方式注入。必须保证该对象存在

  • @Resource 默认通过 byName 的方式注入,如果找不到名字,则通过 byType 注入。如果两个都找不到的情况下,就报错

8. 使用注解开发

在 Spring4 之后,要使用注解开发,必须要保证导入了 AOP 的依赖

在使用注解需要导入context约束,增加注解的支持

  1. bean

    @Component
    public class User {
    	// java代码
    }
    
  2. 属性如何注入

    直接在属性上面添加 @value 注解

    // 等价于 <property name="name" value="xp"/>
    @Value("xp")
    private String name;
    @Value("admin")
    private String account;
    @Value("123")
    private String password;
    

    在 set 方法上面添加 @value 注解

    // 等价于 <property name="name" value="xp"/>
    @Value("xp")
    public void setName(String name) {
        this.name = name;
    }
    

    注:若使用 xml 配置 Bean,属性上面添加 @value 注解的优先级高于在 set 方法上添加 @value 注解。若使用配置类配置 Bean,则 set 方法上的@value 注解优先级高于在属性上面添加 @value

  3. 衍生的注解

    @Component 有几个衍生的注解,我们在 web 开发中,会按照 mvc 三层架构分层

    • @Repository --> dao 层注解

      @Repository
      public class UserDao {
          // java 代码
      }
      
    • @Service --> service 层注解

      @Service
      public class UserService {
          // java 代码
      }
      
    • @Controller --> web 层注解

      @Controller
      public class UserController {
          // java代码
      }
      

      这四个注解功能都是一样的,都是代表将某个类注册到 Spring 中,自动装配 Bean

  4. 自动装配

    • @Autowired:自动装配通过类型、名字

      如果 Autowired 不能唯一自动装配上属性,则需要通过 @Qualifier(value="xxx")

    • @Nullable 字段标记了这个注解,说明这个字段可以为null

    • @Resource:自动装配通过名字、类型

  5. 作用域

    @scope 设置作用域,相当于 <bean class="com.xp.pojo.User" scope="prototype">

    // 相当于 <bean class="com.xp.pojo.User" scope="prototype"\>
    @Scope("prototype")
    public class User {
    	// java代码
    }
    
  6. 小结

    xml 与 注解:

    • xml:xml更加万能,适用于任何场合。维护简单方便
    • 注解:不是自己的类,使用不了。维护相对复杂。

    xml 和注解的最佳时间:

    • xml 只用来管理 Bean
    • 注解只负责完成属性的注入
    • 我们在使用的过程中,只需要注意一个问题:必须让注解生效,就需要开启注解的支持

9. 使用Java 的方式配置 Spring

我们下载完全不适用 Spring 的 xml 配置了,全权交给 Java 来做

JavaConfig 是Spring 的一个子项目,在 Spring4 之后,它成为了一个核心功能!

配置类

// 这个也会被Spring容器托管,注册到容器中,因为他本来就是一个@Componment
// Configuration代表这是一个配置类,就和我们之前看得applicationContext.xml
@Configuration
public class SpringConfig {

    // 注册一个Bean,就相当于我们之前写的一个bean标签
    // 这个方法的名字,就相当于bean标签中的id属性
    // 这个方法的返回值,就相当于bean标签中的class属性
    @Bean
    public User getUser(){
        return new User();
    }

}

测试

public class ConfigTest {
    public static void main(String[] args) {
        // 如果完全使用了配置类的方式去做,我们就只能通过 ApplicationContext 上下问来获取容器,通过配置类的class对象加载
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        User user = context.getBean("getUser", User.class);

        System.out.println(user.toString());
    }
}

10. 代理模式

为什么要学习代理模式?

因为这就是 Spring AOC 的底层!

代理模式的分类:

  • 静态代理
  • 动态代理

Spring5 学习笔记

10.1 静态代理

角色分析:

  • 抽象角色:一般会使用接口或者抽象类来解决
  • 真实角色:被代理的角色
  • 代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属的操作
  • 客户:访问代理对象的人

代码步骤:

  1. 接口

    public interface Service {
        void rent();
    }
    
  2. 真实角色

    public class Host implements Service {
        @Override
        public void rent() {
            System.out.println("房东出租房子");
        }
    }
    
  3. 代理角色

    public class Proxy implements Service {
    
        private Host host;
    
        public Proxy() {
        }
    
        public Proxy(Host host) {
            this.host = host;
        }
    
        @Override
        public void rent() {
            host.rent();
        }
    
        // 看房
        public void seeHouse(){
            System.out.println("中介带客户看房");
        }
    
        // 收中介费
        public void fare(){
            System.out.println("收中介费");
        }
    
        // 签合同
        public void sign(){
            System.out.println("签合同");
        }
    
    }
    
  4. 客户端访问代理角色

    public class Client {
        public static void main(String[] args) {
            // 房东要出租房子
            Host host = new Host();
            // 代理,中介帮房东出租房子,但是,代理角色一般会有一些附属操作
            Proxy proxy = new Proxy(host);
    
            // 不需要面对房东,直接找中介即可
            proxy.rent();
        }
    }
    

代理模式的好处:

  • 可以使真实角色的操作更加纯粹 ,不用再去关注一些公共的业务
  • 公共业务就交给了代理角色,实现了业务的分工
  • 公共业务发生拓展的时候,方便集中管理

代理模式的缺点:

  • 一个真实角色就会产生一个代理角色。代码量会翻倍,开发效率会变低

10.2 AOP的实现机制

在以前的开发中,我们是进行一个纵向开发,从 dao 层到 service 层,从 service 层到 controller 层,从 controller 到前端页面。当我们开发完成后,需要加入新的需求。如果我们继续按照纵向开发的方式去修改原来的代码,则会非常繁琐,代码量巨大。而且,修改原来的代码是公司的大忌!这时候使用 AOP 进行切面编程的横向开发就显得非常友好了。我们只需要横向增加一个代理,将增加的功能交由代理去完成。

Spring5 学习笔记

10.3 动态代理

  • 动态代理和静态代理角色一样
  • 动态代理的代理类是动态生成的,不是我们直接写好的
  • 动态代理分为两大类:基于接口的动态代理,基于类的动态代理
    • 基于接口:JDK 的动态代理
    • 基于类:cglib
    • Java字节码实现:JAVAssist

需要了解两个类:Proxy(代理),InvocationHandler(调用处理程序)

  1. 接口

    public interface Service {
        void rent();
    }
    
  2. 动态代理类

    public class ProxyInvocationHandler implements InvocationHandler {
    
        // 被代理的接口
        private Service service;
    
        public void setService(Service service) {
            this.service = service;
        }
    
        // 生成得到代理类
        public Object getProxy() {
            return Proxy.newProxyInstance(this.getClass().getClassLoader(), service.getClass().getInterfaces(), this);
        }
    
        // 处理代理实例,并返回结果
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            seeHouse();
            // 动态代理的本质,就是使用反射机制实现
            Object result = method.invoke(service,args);
            fare();
            return result;
        }
    
        public void seeHouse(){
            System.out.println("中介带看房");
        }
    
        public void fare(){
            System.out.println("收中介费");
        }
    
    }
    
  3. 测试

    public class Client {
        public static void main(String[] args) {
            // 真实角色
            Host host = new Host();
    
            // 代理角色:现在没有
            ProxyInvocationHandler pih = new ProxyInvocationHandler();
            // 通过调用程序处理角色来处理我们要调用的接口对象
            pih.setService(host);
    
            Service proxy = (Service) pih.getProxy();// 这里的proxy就是动态生成的,我们并没有写
            proxy.rent();
        }
    }
    

根据上面说写的,我们可以将动态代理的类改成一个通用模板。只需将聚合的的类型改成 Object 类型

public class ProxyInvocationHandler implements InvocationHandler {

    // 被代理的接口
    private Object proxy;

    public void setService(Object proxy) {
        this.proxy = proxy;
    }

    // 生成得到代理类
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), proxy.getClass().getInterfaces(), this);
    }

    // 处理代理实例,并返回结果
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        seeHouse();
        // 动态代理的本质,就是使用反射机制实现
        Object result = method.invoke(proxy,args);
        fare();
        return result;
    }
        private void seeHouse(){
        System.out.println("中介带看房");
    }

    private void fare(){
        System.out.println("收中介费");
    } 
}

动态代理的好处:

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

11. AOP

11.1 什么是AOP

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

Spring5 学习笔记

11.2 AOP在Spring中的作用

  • 横切关注点:跨越程序多歌模块的方法或功能,即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如:日志,安全,缓存,事务等。。
  • 切面(Aspect):横切关注点被模块化的特殊对象。即,它是一个类。
  • 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
  • 目标(Target):被通知的对象。
  • 代理(Proxy):向目表对象应用通知之后创建的对象。
  • 切入点(PointCut):切面通知执行的“地点”的定义。
  • 连接点(JoinPoint):与切入点匹配的执行点。

Spring5 学习笔记

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

通知类型 连接点 实现接口
前置通知 方法前 org.springframework.aop.MethodBeforeAdvice
后置通知 方法后 org.springframework.aop.AfterReturningAdvice
环境通知 方法前后 org.aopalliance.intercept.MethodInterceptor
异常抛出通知 方法抛出异常 org.springframework.aop.ThrowsAdvice
引介通知 类中增加新的方法属性 org.springframework.aop.IntroductionInterceptor

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

11.3 使用Spring实现AOP

使用AOP前,需要导入织入依赖包

<!-- 织入包 -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.5</version>
</dependency>

方式一:使用Spring的API接口实现AOP

  1. 创建接口

    public interface UserService {
        void add();
        void delete();
        void update();
        void select();
    }
    
  2. 创建实现类

    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("查询");
        }
    }
    
  3. 创建切面

    public class BeforeLog implements MethodBeforeAdvice {
        /**
         *
         * @param method    需要执行得目表对象的方法
         * @param objects   参数
         * @param o         目标对象
         * @throws Throwable
         */
        @Override
        public void before(Method method, Object[] objects, Object o) throws Throwable {
            System.out.println(o.getClass().getName()+" 的 "+method.getName()+" 被执行了");
        }
    }
    
    public class AfterLog implements AfterReturningAdvice {
    
        // o : 返回值
        @Override
        public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
            System.out.println(o1.getClass().getName()+" 的 "+method.getName()+" 被执行了,返回结果为"+o);
        }
    }
    
  4. 注册 Bean 和 配置 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
            https://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="afterLog" class="com.xp.log.BeforeLog"/>
        <bean id="beforeLog" class="com.xp.log.BeforeLog"/>
        <bean id="userService" class="com.xp.service.UserServiceImpl"/>
    
        <!-- 方式一:使用原生Spring API接口 -->
        <!-- 配置aop:需要导入aop的约束 -->
        <aop:config>
            <!-- 切入点:execution:表达式 execution(需要配置的位置 * * * * *) -->
            <aop:pointcut id="pointCut" expression="execution(* com.xp.service.UserServiceImpl.*(..))"/>
    
            <!-- 执行环境增加 -->
            <aop:advisor advice-ref="afterLog" pointcut-ref="pointCut"/>
            <aop:advisor advice-ref="beforeLog" pointcut-ref="pointCut"/>
        </aop:config>
        
    </beans>
    
  5. 测试

    public class Test {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            // 代理的是接口,所以返回的是接口而不是实现类
            UserService userService = context.getBean("userService", UserService.class);
    
            userService.add();
        }
    }
    

方式二:自定义实现AOP的类

和方式一不同的地方

  1. 创建切入面的类

    public class DiyPointCut  {
    
        public void before(){
            System.out.println("---------方法执行前---------");
        }
    
        public void after(){
            System.out.println("---------方法执行后---------");
        }
    
    }
    
    1. 注册 Bean 和配置 AOP
    <!-- 方式二:组定义类 -->
    <bean id="diy" class="com.xp.diy.DiyPointCut"/>
    
    <aop:config>
        <!-- 自定义切面,ref:要引用的类 -->
        <aop:aspect ref="diy">
            <aop:pointcut id="pointcut" expression="execution(* com.xp.service.UserServiceImpl.*(..))"/>
            <!-- 通知 -->
            <aop:before method="before" pointcut-ref="pointcut"/>
            <aop:after method="after" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>
    

方式三:使用注解方式实现 AOP

和前两种方式不同的地方

  1. 注册 Bean 和配置 aop

    <!-- 方式三:使用注解实现aop -->
    <bean id="annotationPointCut" class="com.xp.annotation.AnnotationPointCut"/>
    <!-- 开启注解支持  JDK(默认) cglib -->
    <!-- proxy-target-class 默认是false -->
    <aop:aspectj-autoproxy proxy-target-class="false"/>
    
  2. 增加注解

    @Aspect // 标注这个类是一个切面
    public class AnnotationPointCut {
        @Before("execution(* com.xp.service.UserServiceImpl.*(..))")
        public void before(){
            System.out.println("``````````方法执行前``````````");
        }
    
        @After("execution(* com.xp.service.UserServiceImpl.*(..))")
        public void after(){
            System.out.println("``````````方法执行后``````````");
        }
    
        @Around("execution(* com.xp.service.UserServiceImpl.*(..))")
        public void around(ProceedingJoinPoint procedingJoinPoint) throws Throwable {
            System.out.println("环绕前");
            Object proceed = procedingJoinPoint.proceed();
            System.out.println("环绕后");
    
            System.out.println(proceed);
        }
    }
    

12. 整合MyBatis

导入相关 jar包

<!-- mysql驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>LATEST</version>
</dependency>
<!-- 与MyBatis整合 -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.3</version>
</dependency>

12.1 回忆MyBatis

  1. 编写实体类

    // 使用了lombok插件,也可以自己手动写getter,setter和toString方法
    @Data
    public class User {
        private Integer id;
    
        private String name;
    
        private String password;
    }
    
  2. 编写核心配置文件

    <?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>
        <!-- 导入db.properties的配置内容 -->
        <properties resource="db.properties"/>
    
        <!-- 别名 -->
        <typeAliases>
            <package name="com.xp.pojo"/>
        </typeAliases>
    
        <!-- 配置环境 -->
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="${driver}"/>
                    <property name="url" value="${url}"/>
                    <property name="username" value="${username}"/>
                    <property name="password" value="${password}"/>
                </dataSource>
            </environment>
        </environments>
    
        <!-- 绑定注册mapper -->
        <mappers>
            <mapper resource="mapper/UserMapper.xml"/>
        </mappers>
        
    </configuration>
    
  3. 编写接口

    public interface UserMapper {
        User getUserById(int id);
    }
    
  4. 编写 mapper.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.xp.mapper.UserMapper">
        <select id="getUserById" resultType="user">
            select * from user where id = #{id};
        </select>
    </mapper>
    
  5. 测试

    public class MyBatisSpringTest {
        public static void main(String[] args) {
            String resources = "mybatis-config.xml";
            try {
                // 读取核心配置文件输入流
                InputStream in = Resources.getResourceAsStream(resources);
                // 通过 SqlSessionFactoryBuilder 获取 SqlSessionFactory对象
                SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
                // 获取SqlSession对象
                SqlSession sqlSession = sqlSessionFactory.openSession();
    
                // 获取mapper,并执行sql语句
                UserMapper mapper = sqlSession.getMapper(UserMapper.class);
                User user = mapper.getUserById(2);
    
                System.out.println(user);
                // 关闭sqlSession
                sqlSession.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

12.2 MyBatis-Spring

12.2.1 简介

什么是 MyBatis-Spring?

MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring中。它将允许MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 sqlSession 并注入到 Bean 中,以及将 MyBatis 的异常转换为 Spring 的 DataAccessException。最终,可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring。

官网

http://mybatis.org/spring/zh/index.html

动机

Spring 2.0 只支持 iBatis 2.0。那么我们就想 MyBatis3 的支持添加到 Spring 3.0 中(参见 Spring Jira 中的问题)。不幸的是,Spring 3.0 的开发在 MyBatis 3.0官方发布前就结束了。由于 SPringle 开发团队不想发布一个基于未发布版的 MyBatis 的整合支持,如果要获得Spring 官方的支持,只能等待下一次的发布了。基于 Spring 中对 MyBatis 提供支持的兴趣,MyBatis社区认为,应该开始召集又兴趣参与其中的贡献者们,将对Spring 的集成作为 MyBatis 的一个社区子项目

基础知识

在开始使用 MyBatis-Spring 之前,你需要线熟悉 Spring 和 MyBatis 这两个框架和它们的术语。

MyBatis-Spring 需要以下版本:

MyBatis-Spring MyBatis Spring框架 Spring Batch Java
2.0 3.5+ 5.0+ 4.0+ Java 8+
1.3 3.4+ 3.2.2+ 2.1+ Java 6+

12.2.2 步骤

  1. 编写数据源配置

    <!-- DataSource:使用Spring的数据源替换MyBatis的配置 c3p0 dbcp druid
        我们这里使用Spring提供的JDBC
     -->
    <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/study?useTimezone=true&amp;serverTimezone=GMT%2b8&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;useSSL=false"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>
    
  2. SqlSessionFactory

    <!-- 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:mapper/UserMapper.xml"/>
    </bean>
    
  3. SqlSessionTemplate

    <!-- SqlSessionTemplate 就是我们以前使用的SqlSession -->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <!-- 只能使用构造器注入sqlSessionFactory,因为它没有set方法 -->
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>
    
  4. 绑定mapper

    <!-- 绑定mapper -->
    <bean id="userMapper" class="com.xp.mapper.impl.UserMapperImpl">
        <property name="sqlSession" ref="sqlSession"/>
    </bean>
    
  5. 给接口加实现类

    public class UserMapperImpl implements UserMapper {
    
        // 在以前,我们所有操作都是使用sqlSession来执行,现在都使用SqlSessionTemplate
        private SqlSessionTemplate sqlSession;
    
        public void setSqlSession(SqlSessionTemplate sqlSession) {
            this.sqlSession = sqlSession;
        }
    
        @Override
        public User getUserById(int id) {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            return mapper.getUserById(id);
        }
    }
    
  6. 将自己写的实现类注入到 Spring 中,测试

    @Test
    public void getUserById(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
    
        User user = userMapper.getUserById(3);
        System.out.println(user);
    }
    

12.2.3 使用SqlSession

在MyBatis中,你可以使用 SqlSessionFactory 来创建 SqlSession。一旦你获得一个 session 之后,你可以使用它来执行映射的语句,提交或回滚连接,最后,当不再需要它的时候,你可以关闭 session。使用 MyBatis-Spring 之后,你不再需要直接使用 SqlSessionFactory 了,因为你的 bean 可以被注入一个线程安全Sqlsession,它能基于SPringle的事务配置来自动提交、回滚、关闭 session。

SqlSessionTemplate

SqlSessionTemplate 是 MyBatis-Spring 的核心,作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经使用的 SqlsessionSqlSessionTemplate 是线程安全的,可以被多个 Dao 或映射器共享使用。

当调用 SQL 方法时(包括由 getMapper() 方法返回的映射器中的方法),SqlSessionTemplate 将会保证使用的 SqlSession 与当前 Spring 的事务相关。此外,它管理 session 的生命周期,包含必要的关闭、提交或回滚操作。另外,它也负责将 MyBatis 的异常翻译成 Spring 中的 DataAccessExceptions

由于模板可以参与到 Spring 的事务管理中,并且由于其是线程安全的,可以供多个映射器类使用,你应该总是SqlSessionTmplate 来替换 MyBatis 默认的 DefaultSqlSession 实现。在同一应用程序中的不同类之间混杂使用可能会引起数据一致性的问题。

可以使用 SqlSessionFactory 作为构造方法的参数来创建 SqlSessionTemplate 对象

13. 声明式事务

13.1. 回顾事务

  • 把一组业务当成一个业务来做。要么都成功,要么都失败
  • 事务在项目开发中,十分的重要,设计到数据的一致性的问题,不能马虎
  • 确保完整性和一致性

事务的ACID原则:

  • 原子性(Atomicity)
  • 一致性(Consistency)
  • 隔离性(Isolation)
    • 多个业务可能操作同一个资源,防止数据损坏
  • 持久性(Durability)
    • 事务一旦提交,无论系统发生什么问题,结果都不会在被影响,被持久化写到存储器中

13.2 Spring中的事务管理

  • 声明式事务:AOP【推荐】
  • 编程式事务:需要在代码中进行事务的管理

思考:

为什么需要事务?

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

事务管理配置

<!-- 配置声明式事务 -->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

<!-- 结合AOP实现事务的织入 -->

<!-- 配置事务的类 -->
<tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager">
    <!-- 给方法配置事务
        配置事务的传播特性: propagation 默认 REQUIRED
    -->
    <tx:attributes>
        <tx:method name="selectUser" read-only="true"/>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

<!-- 配置事务切入 -->
<aop:config>
    <aop:pointcut id="txPoint" expression="execution(* com.xp.mapper.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPoint"/>
</aop:config>
上一篇:理论与工程实践相结合,全面阐述Spring5的新特性;


下一篇:spring5--整合mybatis