Spring

Spring

Spring Framework 是一个功能强大的 Java 应用程序框架,旨在提供高效且可扩展的开发环境。它结合了轻量级的容器和依赖注入功能,提供了一种使用 POJO 进行容器配置和面向切面的编程的简单方法,以及一组用于AOP的模块。Spring 框架还支持各种移动应用开发技术,如 Android 和 iOS。此外,它还提供了对事务管理、对象/关系映射、JavaBeans、JDBC、JMS 和其他技术的支持,从而确保高效开发。

1. 简介

1.1 历史

  • Spring 在起源可以追溯到 Rod Johnson 于2002年出版的《Expert One-to-One J2EE Design and Development》一书 。在这本书中,Rod 展示了他的 interface21 框架,他为自己的应用编写了这一框架。这一框架被发布到开源世界后,组成了现在我们所知的 Spring 框架的基础。
  • 第一个正式版本 1.0 在 2004 年 3 月 24 日 发布。Spring 是指一个用于构造 Java 应用程序轻量级框架,这句话两层意思:

    • 首先,你可以采用 Spring 来构造任何程序,这和Apache Struts 这样的框架不同,仅仅被限定于WEB 应用
    • 其次,“轻量级”并不意味着类的数量很少,或者发行包尺寸很小。实际上,指的是Spring哲学原则——最少的侵入

设计理念:

  • 在每个层面上提供选择。Spring让你尽可能晚地推迟设计决策。例如,你可以通过配置来切换持久化供应商,而不需要改变你的代码。对于许多其他基础设施问题和与第三方API的集成也是如此。
  • 适应不同的观点。Spring拥抱灵活性,对事情应该如何做不持意见。它支持具有不同视角的广泛的应用需求。
  • 保持强大的后向兼容性。Spring的演进是经过精心管理的,在不同的版本之间几乎不存在破坏性的变化。Spring支持一系列精心选择的JDK版本和第三方库,以方便维护依赖Spring的应用程序和库。
  • 关心API的设计。Spring团队花了很多心思和时间来制作直观的API,并且在很多版本和很多年中都能保持良好的效果。
  • 为代码质量设定高标准。Spring框架非常强调有意义的、最新的和准确的javadoc。它是为数不多的可以宣称代码结构干净、包与包之间没有循环依赖关系的项目之一。
1
2
graph TB
id1("SSH(Struct2+Spring+Hibernate)") --> id2("SSM(SpringMVC+Spring+Mybatis)")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>6.0.11</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>6.0.11</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.19</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.7</version>
</dependency>

1.2 优点

  • Spring是一个开源的免费的框架(容器)!
  • Spring是一个轻量级的、非入侵式的框架!
  • 控制反转(IOC, Inversion of Control),面向切面编程(AOP, Aspect-oriented programming)
    • IOC:不是什么技术,而是一种设计思想!在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。
    • AOP:面向切面编程,通过预编译方 式和运行期动态代理实现程序功能的统一维护的一种技术。利用AOP可以对业务逻辑 的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高 了开发的效率。
  • 支持事务的处理,对框架整合的支持!

1.3 组成

image-20230718214516875

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

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

1.4 扩展

  • 现代化的Java开发!说白就是基于Spring的开发!

  • Spring Boot

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

    • 因为现在大多数公司都在使用SpringBoot进行快速开发,学习SpringBoot的前提,需要完全掌握Spring及SpringMVC!承上启下
  • 弊端:发展了太久之后,违背了原来的理念!配置十分繁琐,人称:“配置地狱!”

2. IOC理论推导

控制反转(IOC, Inversion of Control)

不是什么技术,而是一种设计思想!在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。

  • 之前实现

    • ```java
      public class UserServiceImpl implements UserService {

      private UserDao userDao = new UserDaoImpl();    // 程序是主动创建对象!控制权在程序猿手上!
      
      public void getUser() {
          userDao.getUser();
      }
      

      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10

      - 现在实现

      - ```java
      private UserDao userDao;

      //利用set进行动态实现值的注入!
      public void setUserDao(UserDao userDao) {
      this.userDao = userDao; // 程序不再具有主动性,而是变成了被动的接收对象!
      }
  • IOC本质

    • 控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法。没有IoC的程序中,我们使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了
    • 采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
    • 控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)

image-20230718222331294

3. HelloSpring

  • pojo.Hello.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Hello {
private String text;

public String getText() {
return text;
}

public void setText(String text) {
this.text = text;
}

@Override
public String toString() {
return "Hello{" +
"text='" + text + '\'' +
'}';
}
}
  • beans.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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
类型 变量名 = new 类型();
Hello hello = new Hello();

id = 变量名
class = new的对象
property 相当于给对象中的属性设置一个值!
-->
<bean id="hello" class="com.bayyy.pojo.Hello">
<property name="text" value="Spring"/>
</bean>
</beans>
  • 测试
1
2
3
4
5
// 获取Spring的上下文对象
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// 我们的对象现在都在Spring中管理了,我们要使用,直接去里面取出来就可以了
Hello hello = (Hello) context.getBean("hello");
System.out.println(hello.toString());
  • 控制:谁来控制对象的创建,传统应用程序的对象是由程序本身控制创建的,使用Spring后,对象是由Spring来创建的。

  • 反转:程序本身不创建对象,而变成被动的接收对象。

  • 依赖注入:就是利用set方法来进行注入的。
  • IOC是一种编程思想,由主动的编程变成被动的接收。

  • 可以通过new ClassPathXmlApplicationContext去浏览一下底层源码。

  • ==IoC== -> 对象由Spring来创建,管理,装配!

4. IOC创建对象的方式

4.1 使用无参构造方法来创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// User.java
// User.java
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;
}

@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
1
2
3
<bean id="user1" class="com.bayyy.pojo.User">
<property name="name" value="Bayyy"/>
</bean>

4.2 通过有参构造方法来创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 下标赋值 -->
<bean id="user2" class="com.bayyy.pojo.User">
<constructor-arg index="0" value="Bayyy"/>
</bean>

<!-- 类型赋值 -->
<bean id="user3" class="com.bayyy.pojo.User">
<constructor-arg type="java.lang.String" value="Bayyy"/>
</bean>

<!-- 参数名赋值 -->
<bean id="user4" class="com.bayyy.pojo.User">
<constructor-arg name="name" value="Bayyy"/>
</bean>
  • 测试

    • ```java
      ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
      User user = (User) context.getBean("user1");
      System.out.println(user);
      User user2 = (User) context.getBean("user2");
      System.out.println(user2);
      Object user3 = context.getBean("user3");
      System.out.println(user3);
      Object user4 = context.getBean("user4");
      System.out.println(user4);
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12

      - 测试结果:![image-20230719201421158](https://s2.loli.net/2023/07/19/RDP1KTf4jEvxFW3.png)

      - :fire: 初始化时机:在配置文件加载的时候,其中管理的对象都已经初始化了!

      # 5. Spring配置

      ## 5.1 别名

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

5.2 Bean的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- bean就是java对象,由Spring创建和管理 -->

<!--
id 是bean的标识符,要唯一
如果没有配置id,name就是默认标识符
如果配置id,又配置了name,那么name是别名
name 可以设置多个别名,可以用逗号,分号,空格隔开
如果不配置id和name,可以根据applicationContext.getBean(.class)获取对象;
class 是bean的全限定名=包名+类名
-->
<bean id="hello" name="hello2 h2,h3;h4" class="com.bayyy.pojo.Hello">
<property name="name" value="Spring"/>
</bean>

5.3 import

团队开发使用,可将多个配置文件合并为一个

1
<import resource="{path}/beans.xml"/>

6. DI注入依赖

6.1 构造器注入

即IOC对象创建

6.2 Set方式注入

要求被注入的属性 , 必须有set方法 , set方法的方法名由set + 属性首字母大写 , 如果属性是boolean类型 , 没有set方法 , 是 is

  • 依赖注入:Set注入
    • 依赖:bean对象的创建依赖于容器!
    • 注入:bean对象中的所有属性,由容器来注入
1
2
3
4
5
6
7
8
9
10
public class Student {
private String name;
private Address address;
private String[] books;
private List<String> hobby;
private Map<String, String> card;
private Set<String> games;
private String wife;
private Properties info;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<bean id="address" class="com.bayyy.pojo.Address" name="address">
<property name="address" value="西安"/>
</bean>

<bean id="student" class="com.bayyy.pojo.Student">
<!-- 第一种:普通值注入, value -->
<property name="name" value="Bayyy"/>

<!-- 第二种:Bean注入, ref -->
<property name="address" ref="address"/>

<!-- 数组: <array> -->
<property name="books">
<array>
<value>红楼梦</value>
<value>西游记</value>
<value>水浒传</value>
<value>三国演义</value>
</array>
</property>

<!-- List: <list> -->
<property name="hobby">
<list>
<value>打篮球</value>
<value>看电影</value>
<value>敲代码</value>
</list>
</property>

<!-- Map: <map> -->
<property name="card">
<map>
<entry key="身份证" value="123456789987456321"/>
<entry key="银行卡" value="359419496419481649"/>
</map>
</property>

<!-- Set: <set> -->
<property name="games">
<set>
<value>LOL</value>
<value>COC</value>
<value>BOB</value>
</set>
</property>

<!-- NULL -->
<property name="wife">
<null/>
</property>

<!-- Properties: <props> -->
<property name="info">
<props>
<prop key="driver">xxx</prop>
<prop key="url">102.0913.524.4585</prop>
<prop key="user">root</prop>
<prop key="password">123456</prop>
</props>
</property>
</bean>

6.3 扩展方式注入

p命名空间 ->set注入(需要设置有参构造器)

c命名空间 -> 构造器注入

1
2
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
1
2
3
4
5
6
7
8
9
10
11
12
13
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- p 命名空间注入属性-property -->
<bean id="user1" class="com.bayyy.pojo.User" p:name="bay1"/>

<!-- c 命名空间注入属性-constructor-arg -->
<bean id="user2" class="com.bayyy.pojo.User" c:name="bay2"/>
</beans>
1
2
3
4
5
6
7
8
9
    public void test() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

User user1 = context.getBean("user1", User.class);
System.out.println(user1.toString());
User user2 = context.getBean("user2", User.class);
System.out.println(user2.toString());
}
}
  • 测试结果:image-20230719205045214

6.4 Bean的作用域

bean就是由IoC容器初始化、装配及管理的对象

几种作用域中,request、session作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),只能用在基于web的Spring ApplicationContext环境。

Scope 说明
singleton default)为每个Spring IoC容器将单个Bean定义的Scope扩大到单个对象实例。
prototype 将单个Bean定义的Scope扩大到任何数量的对象实例。
request 将单个Bean定义的Scope扩大到单个HTTP请求的生命周期。也就是说,每个HTTP请求都有自己的Bean实例,该实例是在单个Bean定义的基础上创建的。只在Web感知的Spring ApplicationContext 的上下文中有效。
session 将单个Bean定义的Scope扩大到一个HTTP Session 的生命周期。只在Web感知的Spring ApplicationContext 的上下文中有效。
application 将单个Bean定义的 Scope 扩大到 ServletContext 的生命周期中。只在Web感知的Spring ApplicationContext 的上下文中有效。
websocket 将单个Bean定义的 Scope 扩大到 WebSocket 的生命周期。仅在具有Web感知的 Spring ApplicationContext 的上下文中有效。

6.4.1 Singleton

当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。

Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。注意,Singleton作用域是Spring中的缺省作用域。

singleton

1
<bean id="user" class="com.bayyy.pojo.User" scope="singleton">

6.4.2 Prototype

当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。

Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。

根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域

prototype

1
2
3
<bean id="user" class="com.bayyy.pojo.User" scope="prototype"/>  
或者
<bean id="user" class="com.bayyy.pojo.User" singleton="false"/>

6.4.3 Request

当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。

针对每次HTTP请求,Spring容器会根据loginAction bean的定义创建一个全新的LoginAction bean实例,且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。当处理请求结束,request作用域的bean实例将被销毁。

1
<bean id="user" class="com.bayyy.pojo.User" scope="request"/>

6.4.4 Session

当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。

针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化。当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。

1
<bean id="user" class="com.bayyy.pojo.User" scope="session"/>

7. Bean的自动装配

7.1 自动装配说明

  • Spring中bean有三种装配机制,分别是:
    1. 在xml中显式配置;
    2. 在java中显式配置;
    3. 隐式的bean发现机制和自动装配。
  • Spring的自动装配需要从两个角度来实现,或者说是两个操作:

    1. 组件扫描(component scanning):spring会自动发现应用上下文中所创建的bean;
    2. 自动装配(autowiring):spring自动满足bean之间的依赖,也就是我们说的IoC/DI;
  • 自动装配是使用spring满足bean依赖的一种方法, spring会在应用上下文中为某个bean寻找其依赖的bean

1
2
3
private Cat cat;
private Dog dog;
private String name;

7.2 byName

1
2
3
4
5
6
7
<bean id="dog" class="com.bayyy.pojo.Dog"/>
<bean id="cat" class="com.bayyy.pojo.Cat"/>
<bean id="people" class="com.bayyy.pojo.People" autowire="byName">
<property name="name" value="Bayyy"/>
<!-- <property name="cat" ref="cat"/>-->
<!-- <property name="dog" ref="dog"/>-->
</bean>
  • autowire="byName"
    • 将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat
    • 去spring容器中寻找是否有此字符串名称id的对象。
    • 如果有,就取出注入;如果没有,就报空指针异常

7.3 byType

:pushpin: 使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常

名称不一样,也可以按照类型装配,但是不能有多个相同属性

1
2
3
4
5
6
7
<bean id="dog111" class="com.bayyy.pojo.Dog"/>
<bean id="cat222" class="com.bayyy.pojo.Cat"/>
<bean id="people" class="com.bayyy.pojo.People" autowire="byType">
<property name="name" value="Bayyy"/>
<!-- <property name="cat" ref="cat"/>-->
<!-- <property name="dog" ref="dog"/>-->
</bean>

7.4 使用注解实现自动装配

  • jdk1.5开始支持注解,spring2.5开始全面支持注解。
  1. 在spring配置文件中引入context文件头
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
xmlns:context="http://www.springframework.org/schema/context"

http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd

<!-- 完整配置头 -->
<?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"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
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>
  1. 开启属性注解支持
1
<context:annotation-config/>

7.4.1 @Autowired

  • @Autowired 是按类型自动转配的,不支持id匹配==byType==
    • 此时set方法也不需要了
  • 需要导入 spring-aop的包!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class User {
@Autowired
private Cat cat;
@Autowired
private Dog dog;
private String str;

public Cat getCat() {
return cat;
}
public Dog getDog() {
return dog;
}
public String getStr() {
return str;
}
}
  • @Autowired(required=false) 说明:false,对象可以为null;true,对象必须存对象,不能为null

    1
    2
    3
    //如果允许对象为null,设置required = false,默认为true
    @Autowired(required = false)
    private Cat cat;
  • @Nullable 字段标记了了这个注解,说明这个字段可以为null;

7.4.2 @Qualifier

  • @Autowired是根据类型自动装配的,加上@Qualifier则可以根据==byName==的方式自动装配
  • @Qualifier不能单独使用。
1
2
3
4
5
6
@Autowired
@Qualifier(value = "cat2")
private Cat cat;
@Autowired
@Qualifier(value = "dog2")
private Dog dog;

7.4.3 @Resource

  • @Resource如有指定的name属性,先按该属性进行byName方式查找装配;
  • 其次再进行默认的byName方式进行装配;
  • 如果以上都不成功,则按byType的方式自动装配。
  • 都不成功,则报异常。
1
2
3
4
5
6
7
8
public class User {
//如果允许对象为null,设置required = false,默认为true
@Resource(name = "cat2")
private Cat cat;
@Resource
private Dog dog;
private String str;
}

7.4.4 @Autowired/@Resource

  1. @Autowired与@Resource都可以用来装配bean。都可以写在字段上,或写在setter方法上。

  2. @Autowired 默认按类型装配(属于spring规范),默认情况下必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用

  3. @Resource(属于J2EE复返),默认按照名称进行装配,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。

  4. 它们的作用相同都是用注解方式注入对象,但执行顺序不同。@Autowired先byType,@Resource先byName。

8. 使用注解开发

8.1 说明

  1. 在spring4之后,想要使用注解形式,必须得要引入aop的包

    image-20230719221320119

  2. 在配置文件当中,要引入一个context约束

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <!-- 完整配置头 -->
    <?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"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:c="http://www.springframework.org/schema/c"
    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.2 Bean的实现

8.2.1 配置扫描那些包的注解

1
2
3
4
5
<!-- 指定要扫描的包,这个包下的注解都会生效 -->
<context:component-scan base-package="com.bayyy.pojo"/>

<!-- 通过注释,无需手动装配 -->
<!-- <bean id="user" class="com.bayyy.pojo.User"/>-->

8.2.2 类+注释

1
2
3
4
5
6
// 等价于 <bean id="user" class="com.bayyy.pojo.User"/>
// @Component 组件
@Component
public class User {
public String name="Bayyy";
}

8.3 属性注入

8.3.1 不用set()方法

1
2
3
4
5
@Component("user")
public class User {
@Value("Bayyy")
public String name;
}

8.3.2 提供set方法

1
2
3
4
5
6
7
8
9
@Component("user")
public class User {
public String name;

@Value("Bayyy")
public void setName(String name) {
this.name = name;
}
}

8.4 衍生注解

为了更好的进行分层,Spring可以使用其它三个注解,功能一样,目前使用哪一个功能都一样

==@Component三个衍生注解==

  • @Controller:web层
  • @Service:service层
  • @Repository:dao层

标注以上注解,相当于将这个类交给Spring管理装配

8.5 自动装配注解

  • @Autowired
  • @Qulifier
  • @Resource

8.6 作用域

==@Scope==

  • singleton:默认的,Spring会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会销毁。
  • prototype:多例模式。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收
1
2
3
4
5
6
@Controller("user")
@Scope("prototype")
public class User {
@Value("Bayyy")
public String name;
}

8.7 小结

XML与注解比较

  • XML可以适用任何场景 ,结构清晰,维护方便
  • 注解不是自己提供的类使用不了,开发简单方便

xml与注解整合开发 :推荐最佳实践

  • xml管理Bean
  • 注解完成属性注入
  • 使用过程中, 可以不用扫描,扫描是为了类上的注解
1
<context:annotation-config/>  

作用:

  • 进行注解驱动注册,从而使注解生效
  • 用于激活那些已经在spring容器里注册过的bean上面的注解,也就是显式的向Spring注册
  • 如果不扫描包,就需要手动配置bean
  • 如果不加注解驱动,则注入的值为null!

9. 使用Java的方式配置Spring

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

  • 完全不使用xml配置
  • SpringBoot & SpringBoot 常见
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// pojo.Dog.java
@Component // 将这个类标注为Spring的一个组件,放到容器中!
public class Dog {
@Value("小白")
public String name;

public String getName() {
return name;
}
}


// config.MyConfig.java
@Configuration // 代表这是一个配置类, 也会由Spring托管, 注册到容器类
@ComponentScan("com.bayyy.pojo") // 配置类扫描
@Import(MyConfig2.class) // 导入合并其他配置类,类似于配置文件中的 inculde 标签
public class MyConfig {

@Bean // 通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id!
public Dog dog() {
return new Dog();
}
}


// test.java
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
Dog dog = context.getBean("dog", Dog.class);
System.out.println(dog.getName());

10. 代理模式

AOP的底层机制就是动态代理!【SpringAOP和SpringMVC】

1
2
3
4
5
6
7
8
graph LR
Client --> Proxy
subgraph Subject
direction TB
RealSubject --> AbstractSubject
end
Proxy ==> AbstractSubject
Proxy -.-> RealSubject

10.1 静态代理

静态代理角色分析

  • 抽象角色 : 一般使用接口或者抽象类来实现
  • 真实角色 : 被代理的角色
  • 代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些==附属的操作==
  • 客户 : 使用代理角色来进行一些操作
1
2
3
4
graph LR
买家 -.-> 房东
买家 ===> 中介
中介 ==> 房东

静态代理的好处:

  • 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
  • 公共的业务由代理来完成 . 实现了业务的分工 ,
  • 公共业务发生扩展时变得更加集中和方便
  • 在不改变源代码的情况下,使用代理增加额外功能!

缺点 :

  • 类多了 , 多了代理类 , 工作量变大了,开发效率降低

10.2 动态代理

1
2
3
4
5
6
7
8
9
10
11
12
13
---
title:AOP-面向切面编程
---
graph LR
subgraph 纵向开发
direction TB
dao --> Service --> Servlet --> 前端
end
subgraph AOP面向切面编程
direction RL
AdditionalRequirements --> UserService
end
UserService --> 纵向开发
  • 动态代理的角色和静态代理的一样
  • 动态代理的代理类是动态生成的, 静态代理的代理类是我们提前写好的
  • 动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理
    • 基于接口的动态代理——JDK动态代理
    • 基于类的动态代理——cglib
    • 现在用的比较多的是 javasist 来生成动态代理
    • 我们这里使用JDK的原生代码来实现,其余的道理都是一样的!

10.2.1 InvocationHandler

1
2
// java.lang.reflect
public interface InvocationHandler
  • InvocationHandler是由代理实例的调用处型程序实现的接口,每个代理实例都有一个关联的调用处理程序
  • 当在代理实例上调用方法时,方法调用将被编码并分派到其调用的 invoke 方法
1
2
3
4
Object invoke(Object proxy, 方法 method, Object[] args)
// proxy - 调用该方法的代理实例
// method -所述方法对应于调用代理实例上的接口方法的实例。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。
// args -包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数。原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer或java.lang.Boolean 。

10.2.2 Proxy

1
2
3
public class Proxy
extends Object
implements Serializable
  • Proxy 提供了创建动态代理类和实例的静态方法,它也是由这些方法创建的所有动态代理的超类
  • 每个代理实例都有一个关联的 调用处理程序 对象,其实现了接口 InvocatioHandler
1
2
3
4
5
6
7
public statix Object newProxyInstance(ClassLoader loader,
claass<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
// loader - 类加载器来定义代理类
// interfaces - 代理类实现的接口列表
// h - 调用方法调用的调用处理函数
1
2
3
4
5
// 生成代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
rent.getClass().getInterfaces(),this);
}

10.2.3 代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// Rent.java - 抽象对象
// 抽象角色:租房
public interface Rent {
public void rent();
}

// Host.java - 真实角色
// 真实角色: 房东,房东要出租房子
public class Host implements Rent{
public void rent() {
System.out.println("房屋出租");
}
}

// ProxyInvocationHandler.java - 即代理角色
public class ProxyInvocationHandler implements InvocationHandler {
private Rent rent;
public void setRent(Rent rent) {
this.rent = rent;
}

public Object getProxy() {
return java.lang.reflect.Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(), this);
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
seeHouse();
Object result = method.invoke(rent, args);
fare();
return result;
}

public void seeHouse() {
System.out.println("see house");
}

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

// Client.java - 租客找代理
public static void main(String[] args) {
// 真实角色
Host host = new Host();

// 代理角色
ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler();
proxyInvocationHandler.setRent(host); // 将真实角色放置进去!
Rent proxy = (Rent) proxyInvocationHandler.getProxy(); // 动态生成对应的代理类!
proxy.rent();
}

核心:一个动态代理 , 一般代理某一类业务 , 一个动态代理可以代理多个类,代理的是接口!

10.3 提取出通用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ProxyInvocationHandler implements InvocationHandler {
private Object target;
public void setTarget(Object target) {
this.target = target;
}

// 生成代理类
public Object getProxy() {
return java.lang.reflect.Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}

// proxy: 代理类
// method: 代理类的调用处理程序的方法对象
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(target, args);
return result;
}
}
  • 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public static void main(String[] args) {
    // 真实对象
    UserServiceImpl userService = new UserServiceImpl();
    // 代理对象的调用处理程序
    ProxyInvocationHandler pih = new ProxyInvocationHandler();
    pih.setTarget(userService); // 设置要代理的对象
    UserService proxy = (UserService)pih.getProxy(); // 动态生成代理类!
    proxy.delete();
    }

拥有静态代理,且增加了动态的实现

  • 可以使得我们的真实角色更加纯粹,不再去关注一些公共的事情
  • 公共的业务由代理来完成,实现了业务的分工
  • 公共业务发生扩展时变得更加集中和方便
  • 一个动态代理 , 一般代理某一类业务
  • 一个动态代理可以代理多个类,代理的是接口!

11. AOP

11.1 简介

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

    • 通过预编译方式运行期动态代理实现程序功能的统一维护的一种技术;
    • AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型

    • 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率

image-20230722141413755

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

11.2 名词解释

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

image-20230722141521776

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

    • | 通知类型 | 连接点 | 实现接口 |
      | —————— | —————————— | ————————————————————————- |
      | 前置通知 | 方法前 | org.springframework.aop.MethodBeforeAdvice |
      | 后置通知 | 方法后 | org.springframework.aop.AfterReturningAdvice |
      | 环绕通知 | 方法前后 | org.springframework.aop.MethodInterceptor |
      | 异常抛出通知 | 方法抛出异常 | org.springframework.aop.ThrowsAdvice |
      | 引介通知 | 类中增加新的方法属性 | org.springframework.aop.IntroductionInterceptor |
  • AOP - ==不改变原有代码的情况下 , 去增加新的功能==

11.3 代码实现

11.3.1 相关说明

1)导包

1
2
3
4
5
6
7
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.19</version>
<scope>runtime</scope>
</dependency>

2)切点函数

1
execution(* com.bayyy.service.impl..*.*(..))
  • execution():表达式主体
  • 第一个 * :表示返回类型,*代表所有的类型
  • 包名:表示需要拦截的包名,后面两个.表示当前包和当前包的所有子包
  • 第二个 *:表示类名,*表示所有的类
  • *(..):表示方法名,*表示所有方法,()表示方法的参数,..表示任何参数

11.3.2 方式一:使用Spring的API接口

1)业务接口和实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// UserService.java
public interface UserService {
public void add();
public void delete();
public void update();
public void select();
}

// UserServiceImpl.java
public class UserServiceImpl implements UserService {
public void add() {
System.out.println("add a user");
}

public void delete() {
System.out.println("delete a user");
}

public void update() {
System.out.println("update a user");
}

public void select() {
System.out.println("select a user");
}
}

2)增强类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// BeforeLog.java
import org.springframework.aop.MethodBeforeAdvice;

public class BeforeLog 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() + "方法被执行了");
}
}

// AfterLog.java
import org.springframework.aop.AfterReturningAdvice;

public class AfterLog implements AfterReturningAdvice {
/*
* returnValue: 返回值
* method: 要执行的目标对象的方法
* args: 参数
* target: 目标对象
*/
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName() + "的" + method.getName() + "方法被执行了, 返回值为:" + returnValue);
}
}

3)Spring配置

  • ==需要加载配置文件==

    • ```xml
      <beans
         xmlns:aop="http://www.springframework.org/schema/aop"
         xsi:schemaLocation="
          http://www.springframework.org/schema/aop
          https://www.springframework.org/schema/aop/spring-aop.xsd"></beans>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27

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

      <!-- 方式一: 使用原生Spring API接口 -->
      <!-- 注册bean -->
      <bean id="userService" class="com.bayyy.service.UserServiceImpl"/>
      <bean id="beforeLog" class="com.bayyy.log.BeforeLog"/>
      <bean id="afterLog" class="com.bayyy.log.AfterLog"/>

      <!-- 配置aop -->
      <aop:config>
      <!-- 切入点 expression:表达式 execution:要执行的位置 -->
      <aop:pointcut id="pointcut" expression="execution(* com.bayyy.service.UserServiceImpl.*(..))"/>
      <!-- 执行环绕增强 -->
      <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
      <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
      </aop:config>

      </beans>

4)测试

1
2
3
4
5
6
7
8
public void test() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.add();
userService.delete();
userService.update();
userService.select();
}
  • 测试结果:image-20230722154029985

5)分析

  • Spring的Aop就是将公共的业务 (日志,安全等) 和领域业务结合起来,当执行领域业务时,将会把公共业务加进来
  • 实现公共业务的重复利用,领域业务更纯粹,程序猿专注领域业务
  • 其本质还是动态代理

11.3.3 方式二:自定义类来实现AOP

1)自己实现切入类

1
2
3
4
5
6
7
8
9
public class DiyPointCut {
public void before() {
System.out.println("====== method before ======");
}

public void after() {
System.out.println("====== method after ======");
}
}

2)spring配置

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 方式二: 使用自定义类 -->
<bean id="diy" class="com.bayyy.diy.DiyPointCut"/>

<aop:config>
<!-- aspect:切面(类+通知) ref:引用切面 -->
<aop:aspect ref="diy">
<!-- 切入点 expression:表达式 execution:要执行的位置 -->
<aop:pointcut id="pointcut" expression="execution(* com.bayyy.service.UserServiceImpl.*(..))"/>
<!-- 通知 -->
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>

3)测试

  • 测试结果:image-20230722155444058

11.3.4 方式三:使用注解实现

:no_entry_sign: @Aspect 不生效或直接报错

  • 错误重现

image-20230722160035945

  • 错误原因:maven导包是作用域标签问题
    • image-20230722160146522
  • 解决办法:去掉作用域即可(不要忘记重新加载maven

1)编写注解实现的增强类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Aspect
public class AnnotationPointCut {
@Before("execution(* com.bayyy.service.UserServiceImpl.*(..))")
public void before() {
System.out.println("====== method before ======");
}

@After("execution(* com.bayyy.service.UserServiceImpl.*(..))")
public void after() {
System.out.println("====== method after ======");
}

@Around("execution(* com.bayyy.service.UserServiceImpl.*(..))")
public void round(ProceedingJoinPoint jp) throws Throwable {
System.out.println("====== method around before ======");
Object proceed = jp.proceed(); // 如果不执行这个方法,业务层的方法就不会被执行
System.out.println("signature: " + jp.getSignature());
System.out.println("====== method around after ======");
}
}

2)Spring配置

1
2
3
4
<!-- 方式三: 使用注解 -->
<bean id="annotationPointCut" class="com.bayyy.diy.AnnotationPointCut"/>
<!-- 开启注解支持: 会自动扫描注解 -->
<aop:aspectj-autoproxy/>
  • 通过aop命名空间的 <aop:aspectj-autoproxy /> 声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />隐藏起来了
  • <aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class="true"/>时,表示使用CGLib动态代理技术织入增强。
    • 不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。

3)测试

  • 测试结果:image-20230722161133514

12. 整合MyBatis

12.1 步骤

  1. 导入jar包

    • ```xml
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.13</version>
      
      </dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.19</version>
      
      </dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.28</version>
      
      </dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>6.0.11</version>
      
      </dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>6.0.11</version>
      
      </dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.19</version>
      <scope>runtime</scope>
      
      </dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>2.0.7</version>
      
      </dependency>
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38

      | MyBatis-Spring | MyBatis | Spring Framework | Spring Batch | Java |
      | :------------- | :------ | :--------------- | :----------- | :------- |
      | **3.0** | 3.5+ | 6.0+ | 5.0+ | Java 17+ |
      | **2.1** | 3.5+ | 5.x | 4.x | Java 8+ |
      | **2.0** | 3.5+ | 5.x | 4.x | Java 8+ |
      | **1.3** | 3.4+ | 3.2.2+ | 2.1+ | Java 6+ |

      2. 编写配置文件

      - db.properties
      - mybatis-config.xml

      3. 测试

      ## 12.2 Spring-MyBatis

      > MyBatis-Spring 可以将 MyBatis 代码无缝地整合到 Spring 中

      1. SqlSessionFactory

      ```xml
      <!-- DataSource: 使用Spring提供的数据源替换MyBatis的数据源 -->
      <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=utf-8&amp;useSSL=false&amp;serverTimezone=UTC"/>
      <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/bayyy/mapper/*.xml"/>
      </bean>
    • dataSource:可以是任意的 DataSource,只需要和配置其它 Spring 数据库连接一样配置它就可以了。
    • configLocation:它用来指定 MyBatis 的 XML 配置文件路径
      • 它在需要修改 MyBatis 的基础配置非常有用
      • 通常,其中添加基础配置 < settings> 或 < typeAliases>元素
    • 其中的环境配置、数据源、事务管理器都会被忽略,SqlSessionFactoryBean 会创建它自有的 MyBatis 环境配置(Environment),并按要求设置自定义环境的值
  2. SqlSessionTemplate

    1
    2
    3
    4
    5
    <!-- SqlSessionTemplate -->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
    <!-- 只能通过构造器注入: 其没有set方法 -->
    <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>
    • SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession。
  3. 实现类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class UserMapperImpl implements UserMapper{
    // 原来我们所有操作都使用sqlSession来执行,现在都使用sqlSessionTemplate;
    private SqlSessionTemplate sqlSession;

    public void setSqlSession(SqlSessionTemplate sqlSession) {
    this.sqlSession = sqlSession;
    }

    @Override
    public List<User> selectUser() {
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    return mapper.selectUser();
    }
    }
  4. Bean中注入SqlSessionTemplate

    1
    2
    3
    4
    <!-- 注册bean -->
    <bean id="userMapper" class="com.bayyy.mapper.UserMapperImpl">
    <property name="sqlSession" ref="sqlSession"/>
    </bean>

12.3 整合实现 SqlSessionTemplate

12.3.1 spring-dao.xml

:key: 这部分基本是通用的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?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">


<!-- DataSource: 使用Spring提供的数据源替换MyBatis的数据源 -->
<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=utf-8&amp;useSSL=false&amp;serverTimezone=UTC"/>
<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"/>
<!-- mapper可以写在mybatis-config.xml中,但是只能保留唯一一处。为了更加方便且mybatis仅做设置使用,故在此处指定 -->
<property name="mapperLocations" value="classpath:com/bayyy/mapper/*.xml"/>
</bean>

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!-- 只能通过构造器注入: 其没有set方法 -->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
</beans>

12.3.2 接口实现类

因为spring需要对SqlSession进行注入,因此需要接口的实现类,增加SqlSessionTemplate的set方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class UserMapperImpl implements UserMapper{
// 原来我们所有操作都使用sqlSession来执行,现在都使用sqlSessionTemplate;
private SqlSessionTemplate sqlSession;

public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}

@Override
public List<User> selectUser() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.selectUser();
}
}

12.3.3 Bean注册

  • 在applicationContext.xml 中,使用 import 导入spring-Mapper.xml
  • 注册实现类的Bean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?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"
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/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- 指定要扫描的包,这个包下的注解都会生效 -->
<context:component-scan base-package="com.bayyy"/>
<context:annotation-config/>

<!-- 开启注解支持: 会自动扫描注解 -->
<aop:aspectj-autoproxy/>

<!-- 加载外部配置文件 -->
<import resource="spring-dao.xml"/>

<!-- 注册bean -->
<bean id="userMapper" class="com.bayyy.mapper.UserMapperImpl">
<property name="sqlSession" ref="sqlSession"/>
</bean>
</beans>

12.3.4 mybatis-config.xml

mybatis中只保留 settingalias 设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<!--核心配置文件-->
<configuration>
<!-- 设置 -->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

<!-- 别名 -->
<typeAliases>
<!-- 给包起别名: 扫描实体类的包, 默认别名就是类名首字母小写 -->
<package name="com.bayyy.pojo"/>
</typeAliases>
</configuration>

12.3.5 测试

1
2
3
4
5
@Test
public void test() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
context.getBean("userMapper", UserMapper.class).selectUser().forEach(System.out::println);
}

12.4 整合实现 SqlSessionDaoSupport

  • ==SqlSessionDaoSupport==

12.4.1 接口实现类

继承 SqlSessionDaoSupport ,其中实现了 SqlSessionTemplate 的创建

1
2
3
4
5
6
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper {
@Override
public List<User> selectUser() {
return getSqlSession().getMapper(UserMapper.class).selectUser();
}
}

12.4.2 Bean注册

这种方法下,SqlSessionTemplate Bean的实现都可以不设置

  • 其余内容并无改动
1
2
3
<bean id="userMapper" class="com.bayyy.mapper.UserMapperImpl">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

13. 声明式事务

  • 事务管理是企业级应用程序开发中必备技术,用来确保数据的完整性和一致性 ==ACID==

    • 原子性(Atomicity):事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用
    • 一致性(Consistency):一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中
    • 隔离性(Isolation):可能多个事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏
    • 持久性(Durability):事务一旦完成,无论系统发生什么错误,结果都不会受到影响。通常情况下,事务的结果被写到持久化存储器中
  • Spring在不同的事务管理API之上定义了一个抽象层,使得开发人员不必了解底层的事务管理API就可以使用Spring的事务管理机制

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

13.1 使用

13.1.1 tx 约束导入

1
2
3
4
xmlns:tx="http://www.springframework.org/schema/tx"

http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">

13.1.2 事务管理器

  • 无论使用Spring的哪种事务管理策略(编程式或者声明式)事务管理器都是必须的。
  • 就是 Spring的核心事务管理抽象,管理封装了一组独立于技术的方法。
1
2
3
4
<!-- 配置声明式事务 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource"/>
</bean>

13.1.3 配置事务通知

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 结合AOP实现事务的自动管理 -->
<!-- 配置事务通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 给多个方法配置事务 -->
<!-- propagation: 事务的传播行为(default:REQUIRED) -->
<tx:attributes>
<tx:method name="add" propagation="REQUIRED"/>
<tx:method name="delete" propagation="REQUIRED"/>
<tx:method name="update" propagation="REQUIRED"/>
<tx:method name="select" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
  • spring事务传播特性

    事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播

    1. propagation_requierd:(default) 如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。
    2. propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
    3. propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
    4. propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
    5. propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
    6. propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
    7. propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作

13.1.4 配置AOP

  1. aop约束导入

    1
    2
    3
    4
    5
    xmlns:aop="http://www.springframework.org/schema/aop"


    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop.xsd
  2. 配置aop织入事务

    1
    2
    3
    4
    <aop:config>
    <aop:pointcut id="txPointCut" expression="execution(* com.bayyy.mapper..*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
    </aop:config>

13.2 整合

  • applicationContext.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?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"
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/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- 指定要扫描的包,这个包下的注解都会生效 -->
<context:component-scan base-package="com.bayyy"/>
<context:annotation-config/>

<!-- 开启注解支持: 会自动扫描注解 -->
<aop:aspectj-autoproxy/>

<!-- 加载外部配置文件 -->
<import resource="spring-mapper.xml"/>

<!-- 注册bean -->
...
</beans>
  • mybatis-config.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<!--核心配置文件-->
<configuration>
<!-- 设置 -->
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

<!-- 别名 -->
<typeAliases>
<!-- 给包起别名: 扫描实体类的包, 默认别名就是类名首字母小写 -->
<package name="com.bayyy.pojo"/>
</typeAliases>
</configuration>
  • spring-mapper.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<?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:tx="http://www.springframework.org/schema/tx"
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/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">


<!-- DataSource: 使用Spring提供的数据源替换MyBatis的数据源 -->
<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=utf-8&amp;useSSL=false&amp;serverTimezone=UTC"/>
<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/bayyy/mapper/*.xml"/>
</bean>

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!-- 只能通过构造器注入: 其没有set方法 -->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>

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

<!-- 结合AOP实现事务的自动管理 -->
<!-- 配置事务通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 给多个方法配置事务 -->
<!-- propagation: 事务的传播行为(default:REQUIRED) -->
<tx:attributes>
<tx:method name="addUser" propagation="REQUIRED"/>
<tx:method name="deleteUser" propagation="REQUIRED"/>
<tx:method name="select" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>

<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.bayyy.mapper..*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>

</beans>