Spring_day01

今日目标

  • 掌握 Spring 相关概念
  • 完成 IOC/DI 的入门案例编写
  • 掌握 IOC 的相关配置与使用
  • 掌握 DI 的相关配置与使用

1,课程介绍

对于一门新技术,我们需要从为什么要学学什么以及怎么学这三个方向入手来学习。那对于 Spring 来说:

1.1 为什么要学?

  • 从使用和占有率看

    • Spring 在市场的占有率与使用率高

    • Spring 在企业的技术选型命中率高

    • 所以说,Spring 技术是 JavaEE 开发必备技能,企业开发技术选型命中率>==90%==

      image-20210729171139088

      说明:对于未使用 Spring 的项目一般都是些比较老的项目,大多都处于维护阶段。

  • 从专业角度看

    • 随着时代发展,软件规模与功能都呈几何式增长,开发难度也在不断递增,该如何解决?
      • Spring 可以==简化开发==,降低企业级开发的复杂性,使开发变得更简单快捷
    • 随着项目规模与功能的增长,遇到的问题就会增多,为了解决问题会引入更多的框架,这些框架如何协调工作?
      • Spring 可以==框架整合==,高效整合其他技术,提高企业级应用开发与运行效率

    综上所述,==Spring 是一款非常优秀而且功能强大的框架,不仅要学,而且还要学好。==

1.2 学什么?

从上面的介绍中,我们可以看到 Spring 框架主要的优势是在简化开发框架整合上,至于如何实现就是咱们要学习 Spring 框架的主要内容:

  • 简化开发: Spring 框架中提供了两个大的核心技术,分别是:

    • ==IOC==
    • ==AOP==

      • ==事务处理==

        1.Spring 的简化操作都是基于这两块内容,所以这也是 Spring 学习中最为重要的两个知识点。

        2.事务处理属于 Spring 中 AOP 的具体应用,可以简化项目中的事务管理,也是 Spring 技术中的一大亮点。

  • 框架整合: Spring 在框架整合这块已经做到了极致,它可以整合市面上几乎所有主流框架,比如:

    • ==MyBatis==
    • MyBatis-plus
    • Struts
    • Struts2
    • Hibernate
    • ……

    这些框架中,我们目前只学习了 MyBatis,所以在 Spring 框架的学习中,主要是学习如何整合 MyBatis。

    综上所述,对于 Spring 的学习,主要学习四块内容:

    ==(1)IOC,(2)整合 Mybatis(IOC 的具体应用),(3)AOP,(4)声明式事务(AOP 的具体应用)==

1.3 怎么学?

  • 学习 Spring 框架设计思想
    • 对于 Spring 来说,它能迅速占领全球市场,不只是说它的某个功能比较强大,更重要是在它的思想上。
  • 学习基础操作,思考操作与思想间的联系
    • 掌握了 Spring 的设计思想,然后就需要通过一些基础操作来思考操作与思想之间的关联关系
  • 学习案例,熟练应用操作的同时,体会思想
    • 会了基础操作后,就需要通过大量案例来熟练掌握框架的具体应用,加深对设计思想的理解。

介绍完为什么要学学什么怎么学Spring 框架后,大家需要重点掌握的是:

  • Spring 很优秀,需要认真重点的学习
  • Spring 的学习主线是 IOC、AOP、声明式事务和整合 MyBais

接下来,咱们就开始进入 Spring 框架的学习。

2,Spring 相关概念

2.1 初识 Spring

在这一节,主要通过以下两个点来了解下 Spring:

2.1.1 Spring 家族

  • 官网:https://spring.io,从官网我们可以大概了解到:

    • Spring 能做什么:用以开发 web、微服务以及分布式系统等,光这三块就已经占了 JavaEE 开发的九成多。
    • Spring 并不是单一的一个技术,而是一个大家族,可以从官网的Projects中查看其包含的所有技术。
  • Spring 发展到今天已经形成了一种开发的生态圈,Spring 提供了若干个项目,每个项目用于完成特定的功能。

    • Spring 已形成了完整的生态圈,也就是说我们可以完全使用 Spring 技术完成整个项目的构建、设计与开发。

    • Spring 有若干个项目,可以根据需要自行选择,把这些个项目组合起来,起了一个名称叫==全家桶==,如下图所示

      image-20210729171850181

      说明:

      图中的图标都代表什么含义,可以进入https://spring.io/projects网站进行对比查看。

      这些技术并不是所有的都需要学习,额外需要重点关注Spring FrameworkSpringBootSpringCloud:

      1629714811435

      • Spring Framework:Spring 框架,是 Spring 中最早最核心的技术,也是所有其他技术的基础。
      • SpringBoot:Spring 是来简化开发,而 SpringBoot 是来帮助 Spring 在简化的基础上能更快速进行开发。
      • SpringCloud:这个是用来做分布式之微服务架构的相关开发。

      除了上面的这三个技术外,还有很多其他的技术,也比较流行,如 SpringData,SpringSecurity 等,这些都可以被应用在我们的项目中。我们今天所学习的 Spring 其实指的是==Spring Framework==。

2.1.2 了解 Spring 发展史

接下来我们介绍下 Spring Framework 这个技术是如何来的呢?

image-20210729171926576

Spring 发展史

  • IBM(IT 公司-国际商业机器公司)在 1997 年提出了 EJB 思想,早期的 JAVAEE 开发大都基于该思想。
  • Rod Johnson(Java 和 J2EE 开发领域的专家)在 2002 年出版的Expert One-on-One J2EE Design and Development,书中有阐述在开发中使用 EJB 该如何做。
  • Rod Johnson 在 2004 年出版的Expert One-on-One J2EE Development without EJB,书中提出了比 EJB 思想更高效的实现方案,并且在同年将方案进行了具体的落地实现,这个实现就是 Spring1.0。
  • 随着时间推移,版本不断更新维护,目前最新的是 Spring5
    • Spring1.0 是纯配置文件开发
    • Spring2.0 为了简化开发引入了注解开发,此时是配置文件加注解的开发方式
    • Spring3.0 已经可以进行纯注解开发,使开发效率大幅提升,我们的课程会以注解开发为主
    • Spring4.0 根据 JDK 的版本升级对个别 API 进行了调整
    • Spring5.0 已经全面支持 JDK8,现在 Spring 最新的是 5 系列所以建议大家把 JDK 安装成 1.8 版

本节介绍了 Spring 家族与 Spring 的发展史,需要大家重点掌握的是:

  • 今天所学的 Spring 其实是 Spring 家族中的 Spring Framework
  • Spring Framework 是 Spring 家族中其他框架的底层基础,学好 Spring 可以为其他 Spring 框架的学习打好基础

2.2 Spring 系统架构

前面我们说 spring 指的是 Spring Framework,那么它其中都包含哪些内容以及我们该如何学习这个框架?

针对这些问题,我们将从系统架构图课程学习路线来进行说明:

2.2.1 系统架构图

  • Spring Framework 是 Spring 生态圈中最基础的项目,是其他项目的根基。

  • Spring Framework 的发展也经历了很多版本的变更,每个版本都有相应的调整

    image-20210729172153796

  • Spring Framework 的 5 版本目前没有最新的架构图,而最新的是 4 版本,所以接下来主要研究的是 4 的架构图

    1629720945720

    (1)核心层

    • Core Container:核心容器,这个模块是 Spring 最核心的模块,其他的都需要依赖该模块

    (2)AOP 层

    • AOP:面向切面编程,它依赖核心层容器,目的是==在不改变原有代码的前提下对其进行功能增强==
    • Aspects:AOP 是思想,Aspects 是对 AOP 思想的具体实现

    (3)数据层

    • Data Access:数据访问,Spring 全家桶中有对数据访问的具体实现技术
    • Data Integration:数据集成,Spring 支持整合其他的数据层解决方案,比如 Mybatis
    • Transactions:事务,Spring 中事务管理是 Spring AOP 的一个具体实现,也是后期学习的重点内容

    (4)Web 层

    • 这一层的内容将在 SpringMVC 框架具体学习

    (5)Test 层

    • Spring 主要整合了 Junit 来完成单元测试和集成测试

2.2.2 课程学习路线

介绍完 Spring 的体系结构后,从中我们可以得出对于 Spring 的学习主要包含四部分内容,分别是:

  • ==Spring 的 IOC/DI==
  • ==Spring 的 AOP==
  • ==AOP 的具体应用,事务管理==
  • ==IOC/DI 的具体应用,整合 Mybatis==

1629722300996

对于这节的内容,大家重点要记住的是 Spring 需要学习的四部分内容。接下来就从第一部分开始学起。

2.3 Spring 核心概念

在 Spring 核心概念这部分内容中主要包含IOC/DIIOC容器Bean,那么问题就来了,这些都是什么呢?

2.3.1 目前项目中的问题

要想解答这个问题,就需要先分析下目前咱们代码在编写过程中遇到的问题:

1629723232339

(1)业务层需要调用数据层的方法,就需要在业务层 new 数据层的对象

(2)如果数据层的实现类发生变化,那么业务层的代码也需要跟着改变,发生变更后,都需要进行编译打包和重部署

(3)所以,现在代码在编写的过程中存在的问题是:==耦合度偏高==

针对这个问题,该如何解决呢?

1629724206002

我们就想,如果能把框中的内容给去掉,不就可以降低依赖了么,但是又会引入新的问题,去掉以后程序能运行么?

答案肯定是不行,因为 bookDao 没有赋值为 Null,强行运行就会出空指针异常。

所以现在的问题就是,业务层不想 new 对象,运行的时候又需要这个对象,该咋办呢?

针对这个问题,Spring 就提出了一个解决方案:

  • 使用对象时,在程序中不要主动使用 new 产生对象,转换为由==外部==提供对象

这种实现思就是 Spring 的一个核心概念

2.3.2 IOC、IOC 容器、Bean、DI

  1. ==IOC(Inversion of Control)控制反转==

(1)什么是控制反转呢?

  • 使用对象时,由主动 new 产生对象转换为由==外部==提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转。
    • 业务层要用数据层的类对象,以前是自己new
    • 现在自己不 new 了,交给别人[外部]来创建对象
    • 别人[外部]就反转控制了数据层对象的创建权
    • 这种思想就是控制反转
    • 别人[外部]指定是什么呢?继续往下学

(2)Spring 和 IOC 之间的关系是什么呢?

  • Spring 技术对 IOC 思想进行了实现
  • Spring 提供了一个容器,称为==IOC 容器==,用来充当 IOC 思想中的”外部”
  • IOC 思想中的别人[外部]指的就是 Spring 的 IOC 容器

(3)IOC 容器的作用以及内部存放的是什么?

  • IOC 容器负责对象的创建、初始化等一系列工作,其中包含了数据层和业务层的类对象
  • 被创建或被管理的对象在 IOC 容器中统称为==Bean==
  • IOC 容器中放的就是一个个的 Bean 对象

(4)当 IOC 容器中创建好 service 和 dao 对象后,程序能正确执行么?

  • 不行,因为 service 运行需要依赖 dao 对象
  • IOC 容器中虽然有 service 和 dao 对象
  • 但是 service 对象和 dao 对象没有任何关系
  • 需要把 dao 对象交给 service,也就是说要绑定 service 和 dao 对象之间的关系

像这种在容器中建立对象与对象之间的绑定关系就要用到 DI:

  1. ==DI(Dependency Injection)依赖注入==

1629735078619

(1)什么是依赖注入呢?

  • 在容器中建立 bean 与 bean 之间的依赖关系的整个过程,称为依赖注入
    • 业务层要用数据层的类对象,以前是自己new
    • 现在自己不 new 了,靠别人[外部其实指的就是IOC容器]来给注入进来
    • 这种思想就是依赖注入

(2)IOC 容器中哪些 bean 之间要建立依赖关系呢?

  • 这个需要程序员根据业务需求提前建立好关系,如业务层需要依赖数据层,service 就要和 dao 建立依赖关系

介绍完 Spring 的 IOC 和 DI 的概念后,我们会发现这两个概念的最终目标就是:==充分解耦==,具体实现靠:

  • 使用 IOC 容器管理 bean(IOC)
  • 在 IOC 容器内将有依赖关系的 bean 进行关系绑定(DI)
  • 最终结果为:使用对象时不仅可以直接从 IOC 容器中获取,并且获取到的 bean 已经绑定了所有的依赖关系.

2.3.3 核心概念小结

这节比较重要,重点要理解什么是IOC/DI思想什么是IOC容器什么是Bean

(1)什么 IOC/DI 思想?

  • IOC:控制反转,控制反转的是对象的创建权
  • DI:依赖注入,绑定对象与对象之间的依赖关系

(2)什么是 IOC 容器?

Spring 创建了一个容器用来存放所创建的对象,这个容器就叫 IOC 容器

(3)什么是 Bean?

容器中所存放的一个个对象就叫 Bean 或 Bean 对象

3,入门案例

介绍完 Spring 的核心概念后,接下来我们得思考一个问题就是,Spring 到底是如何来实现 IOC 和 DI 的,那接下来就通过一些简单的入门案例,来演示下具体实现过程:

3.1 IOC 入门案例

对于入门案例,我们得先分析思路然后再代码实现

3.1.1 入门案例思路分析

(1)Spring 是使用容器来管理 bean 对象的,那么管什么?

  • 主要管理项目中所使用到的类对象,比如(Service 和 Dao)

(2)如何将被管理的对象告知 IOC 容器?

  • 使用配置文件

(3)被管理的对象交给 IOC 容器,要想从容器中获取对象,就先得思考如何获取到 IOC 容器?

  • Spring 框架提供相应的接口

(4)IOC 容器得到后,如何从容器中获取 bean?

  • 调用 Spring 框架提供对应接口中的方法

(5)使用 Spring 导入哪些坐标?

  • 用别人的东西,就需要在 pom.xml 添加对应的依赖

3.1.2 入门案例代码实现

需求分析:将 BookServiceImpl 和 BookDaoImpl 交给 Spring 管理,并从容器中获取对应的 bean 对象进行方法调用。

1.创建 Maven 的 java 项目

2.pom.xml 添加 Spring 的依赖 jar 包

3.创建 BookService,BookServiceImpl,BookDao 和 BookDaoImpl 四个类

4.resources 下添加 spring 配置文件,并完成 bean 的配置

5.使用 Spring 提供的接口完成 IOC 容器的创建

6.从容器中获取对象进行方法调用

步骤 1:创建 Maven 项目

1629734010072

步骤 2:添加 Spring 的依赖 jar 包

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
步骤 3:添加案例中需要的类

创建 BookService,BookServiceImpl,BookDao 和 BookDaoImpl 四个类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public interface BookDao {
public void save();
}
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
public interface BookService {
public void save();
}
public class BookServiceImpl implements BookService {
private BookDao bookDao = new BookDaoImpl();
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
步骤 4:添加 spring 配置文件

resources 下添加 spring 配置文件 applicationContext.xml,并完成 bean 的配置

1629734336440

步骤 5:在配置文件中完成 bean 的配置
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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<!--bean标签标示配置bean
id属性标示给bean起名字
class属性表示给bean定义类型
-->
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"/>

</beans>

==注意事项:bean 定义时 id 属性在同一个上下文中(配置文件)不能重复==

步骤 6:获取 IOC 容器

使用 Spring 提供的接口完成 IOC 容器的创建,创建 App 类,编写 main 方法

1
2
3
4
5
6
public class App {
public static void main(String[] args) {
//获取IOC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
}
}
步骤 7:从容器中获取对象进行方法调用
1
2
3
4
5
6
7
8
9
10
public class App {
public static void main(String[] args) {
//获取IOC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// BookDao bookDao = (BookDao) ctx.getBean("bookDao");
// bookDao.save();
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}
步骤 8:运行程序

测试结果为:

image-20210729184337603

Spring 的 IOC 入门案例已经完成,但是在BookServiceImpl的类中依然存在BookDaoImpl对象的 new 操作,它们之间的耦合度还是比较高,这块该如何解决,就需要用到下面的DI:依赖注入

3.2 DI 入门案例

对于 DI 的入门案例,我们依然先分析思路然后再代码实现

3.2.1 入门案例思路分析

(1)要想实现依赖注入,必须要基于 IOC 管理 Bean

  • DI 的入门案例要依赖于前面 IOC 的入门案例

(2)Service 中使用 new 形式创建的 Dao 对象是否保留?

  • 需要删除掉,最终要使用 IOC 容器中的 bean 对象

(3)Service 中需要的 Dao 对象如何进入到 Service 中?

  • 在 Service 中提供方法,让 Spring 的 IOC 容器可以通过该方法传入 bean 对象

(4)Service 与 Dao 间的关系如何描述?

  • 使用配置文件

3.2.2 入门案例代码实现

需求:基于 IOC 入门案例,在 BookServiceImpl 类中删除 new 对象的方式,使用 Spring 的 DI 完成 Dao 层的注入

1.删除业务层中使用 new 的方式创建的 dao 对象

2.在业务层提供 BookDao 的 setter 方法

3.在配置文件中添加依赖注入的配置

4.运行程序调用方法

步骤 1: 去除代码中的 new

在 BookServiceImpl 类中,删除业务层中使用 new 的方式创建的 dao 对象

1
2
3
4
5
6
7
8
9
public class BookServiceImpl implements BookService {
//删除业务层中使用new的方式创建的dao对象
private BookDao bookDao;

public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
步骤 2:为属性提供 setter 方法

在 BookServiceImpl 类中,为 BookDao 提供 setter 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class BookServiceImpl implements BookService {
//删除业务层中使用new的方式创建的dao对象
private BookDao bookDao;

public void save() {
System.out.println("book service save ...");
bookDao.save();
}
//提供对应的set方法
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}

步骤 3:修改配置完成注入

在配置文件中添加依赖注入的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?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标签标示配置bean
id属性标示给bean起名字
class属性表示给bean定义类型
-->
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>

<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<!--配置server与dao的关系-->
<!--property标签表示配置当前bean的属性
name属性表示配置哪一个具体的属性
ref属性表示参照哪一个bean
-->
<property name="bookDao" ref="bookDao"/>
</bean>

</beans>

==注意:配置中的两个 bookDao 的含义是不一样的==

  • name=”bookDao”中bookDao的作用是让 Spring 的 IOC 容器在获取到名称后,将首字母大写,前面加 set 找对应的setBookDao()方法进行对象注入
  • ref=”bookDao”中bookDao的作用是让 Spring 能在 IOC 容器中找到 id 为bookDao的 Bean 对象给bookService进行注入
  • 综上所述,对应关系如下:

1629736314989

步骤 4:运行程序

运行,测试结果为:

image-20210729184337603

4,IOC 相关内容

通过前面两个案例,我们已经学习了bean如何定义配置DI如何定义配置以及容器对象如何获取的内容,接下来主要是把这三块内容展开进行详细的讲解,深入的学习下这三部分的内容,首先是 bean 基础配置。

4.1 bean 基础配置

对于 bean 的配置中,主要会讲解bean基础配置,bean的别名配置,bean的作用范围配置==(重点)==,这三部分内容:

4.1.1 bean 基础配置(id 与 class)

对于 bean 的基础配置,在前面的案例中已经使用过:

1
<bean id="" class=""/>

其中,bean 标签的功能、使用方式以及 id 和 class 属性的作用,我们通过一张图来描述下

image-20210729183500978

这其中需要大家重点掌握的是:==bean 标签的 id 和 class 属性的使用==。

思考:

  • class 属性能不能写接口如BookDao的类全名呢?

答案肯定是不行,因为接口是没办法创建对象的。

  • 前面提过为 bean 设置 id 时,id 必须唯一,但是如果由于命名习惯而产生了分歧后,该如何解决?

在解决这个问题之前,我们需要准备下开发环境,对于开发环境我们可以有两种解决方案:

  • 使用前面 IOC 和 DI 的案例

  • 重新搭建一个新的案例环境,目的是方便大家查阅代码

    • 搭建的内容和前面的案例是一样的,内容如下:

      1629769227068

4.1.2 bean 的 name 属性

环境准备好后,接下来就可以在这个环境的基础上来学习下 bean 的别名配置,

首先来看下别名的配置说明:

image-20210729183558051

步骤 1:配置别名

打开 spring 的配置文件 applicationContext.xml

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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<!--name:为bean指定别名,别名可以有多个,使用逗号,分号,空格进行分隔-->
<bean id="bookService" name="service service4 bookEbi" class="com.itheima.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>

<!--scope:为bean设置作用范围,可选值为单例singloton,非单例prototype-->
<bean id="bookDao" name="dao" class="com.itheima.dao.impl.BookDaoImpl"/>
</beans>

说明:Ebi 全称 Enterprise Business Interface,翻译为企业业务接口

步骤 2:根据名称容器中获取 bean 对象
1
2
3
4
5
6
7
8
public class AppForName {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//此处根据bean标签的id属性和name属性的任意一个值来获取bean对象
BookService bookService = (BookService) ctx.getBean("service4");
bookService.save();
}
}
步骤 3:运行程序

测试结果为:

image-20210729184337603

==注意事项:==

  • bean 依赖注入的 ref 属性指定 bean,必须在容器中存在

    1629771744003

  • 如果不存在,则会报错,如下:

    1629771880920

    这个错误大家需要特别关注下:

    1629771972886

    获取 bean 无论是通过 id 还是 name 获取,如果无法获取到,将抛出异常==NoSuchBeanDefinitionException==

4.1.3 bean 作用范围 scope 配置

关于 bean 的作用范围是 bean 属性配置的一个==重点==内容。

看到这个作用范围,我们就得思考 bean 的作用范围是来控制 bean 哪块内容的?

我们先来看下bean作用范围的配置属性:

image-20210729183628138

4.1.3.1 验证 IOC 容器中对象是否为单例
验证思路

​ 同一个 bean 获取两次,将对象打印到控制台,看打印出的地址值是否一致。

具体实现
  • 创建一个 AppForScope 的类,在其 main 方法中来验证

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class AppForScope {
    public static void main(String[] args) {
    ApplicationContext ctx = new
    ClassPathXmlApplicationContext("applicationContext.xml");

    BookDao bookDao1 = (BookDao) ctx.getBean("bookDao");
    BookDao bookDao2 = (BookDao) ctx.getBean("bookDao");
    System.out.println(bookDao1);
    System.out.println(bookDao2);
    }
    }
  • 打印,观察控制台的打印结果

    1629772538893

  • 结论:默认情况下,Spring 创建的 bean 对象都是单例的

获取到结论后,问题就来了,那如果我想创建出来非单例的 bean 对象,该如何实现呢?

4.1.3.2 配置 bean 为非单例

在 Spring 配置文件中,配置 scope 属性来实现 bean 的非单例创建

  • 在 Spring 的配置文件中,修改<bean>的 scope 属性

    1
    <bean id="bookDao" name="dao" class="com.itheima.dao.impl.BookDaoImpl" scope=""/>
  • 将 scope 设置为singleton

    1
    <bean id="bookDao" name="dao" class="com.itheima.dao.impl.BookDaoImpl" scope="singleton"/>

    运行 AppForScope,打印看结果

    1629772538893

  • 将 scope 设置为prototype

    1
    <bean id="bookDao" name="dao" class="com.itheima.dao.impl.BookDaoImpl" scope="prototype"/>

    运行 AppForScope,打印看结果

    1629772928714

  • 结论,使用 bean 的scope属性可以控制 bean 的创建是否为单例:

    • singleton默认为单例
    • prototype为非单例
4.1.3.3 scope 使用后续思考

介绍完scope属性以后,我们来思考几个问题:

  • 为什么 bean 默认为单例?
    • bean 为单例的意思是在 Spring 的 IOC 容器中只会有该类的一个对象
    • bean 对象只有一个就避免了对象的频繁创建与销毁,达到了 bean 对象的复用,性能高
  • bean 在容器中是单例的,会不会产生线程安全问题?
    • 如果对象是有状态对象,即该对象有成员变量可以用来存储数据的,
    • 因为所有请求线程共用一个 bean 对象,所以会存在线程安全问题。
    • 如果对象是无状态对象,即该对象没有成员变量没有进行数据存储的,
    • 因方法中的局部变量在方法调用完成后会被销毁,所以不会存在线程安全问题。
  • 哪些 bean 对象适合交给容器进行管理?
    • 表现层对象
    • 业务层对象
    • 数据层对象
    • 工具对象
  • 哪些 bean 对象不适合交给容器进行管理?
    • 封装实例的域对象,因为会引发线程安全问题,所以不适合。

4.14 bean 基础配置小结

关于 bean 的基础配置中,需要大家掌握以下属性:

1631529887695

4.2 bean 实例化

对象已经能交给 Spring 的 IOC 容器来创建了,但是容器是如何来创建对象的呢?

就需要研究下bean的实例化过程,在这块内容中主要解决两部分内容,分别是

  • bean 是如何创建的
  • 实例化 bean 的三种方式,构造方法,静态工厂实例工厂

在讲解这三种创建方式之前,我们需要先确认一件事:

bean 本质上就是对象,对象在 new 的时候会使用构造方法完成,那创建 bean 也是使用构造方法完成的。

基于这个知识点出发,我们来验证 spring 中 bean 的三种创建方式,

4.2.1 环境准备

为了方便大家阅读代码,重新准备个开发环境,

  • 创建一个 Maven 项目
  • pom.xml 添加依赖
  • resources 下添加 spring 的配置文件 applicationContext.xml

这些步骤和前面的都一致,大家可以快速的拷贝即可,最终项目的结构如下:

1629775585694

4.2.2 构造方法实例化

在上述的环境下,我们来研究下 Spring 中的第一种 bean 的创建方式构造方法实例化:

步骤 1:准备需要被创建的类

准备一个 BookDao 和 BookDaoImpl 类

1
2
3
4
5
6
7
8
9
10
11

public interface BookDao {
public void save();
}

public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}

}
步骤 2:将类配置到 Spring 容器
1
2
3
4
5
6
7
8
<?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="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>

</beans>
步骤 3:编写运行程序
1
2
3
4
5
6
7
8
9
public class AppForInstanceBook {
public static void main(String[] args) {
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();

}
}
步骤 4:类中提供构造函数测试

在 BookDaoImpl 类中添加一个无参构造函数,并打印一句话,方便观察结果。

1
2
3
4
5
6
7
8
9
public class BookDaoImpl implements BookDao {
public BookDaoImpl() {
System.out.println("book dao constructor is running ....");
}
public void save() {
System.out.println("book dao save ...");
}

}

运行程序,如果控制台有打印构造函数中的输出,说明 Spring 容器在创建对象的时候也走的是构造函数

1629775972507

步骤 5:将构造函数改成 private 测试
1
2
3
4
5
6
7
8
9
public class BookDaoImpl implements BookDao {
private BookDaoImpl() {
System.out.println("book dao constructor is running ....");
}
public void save() {
System.out.println("book dao save ...");
}

}

运行程序,能执行成功,说明内部走的依然是构造函数,能访问到类中的私有构造方法,显而易见 Spring 底层用的是反射

1629775972507

步骤 6:构造函数中添加一个参数测试
1
2
3
4
5
6
7
8
9
public class BookDaoImpl implements BookDao {
private BookDaoImpl(int i) {
System.out.println("book dao constructor is running ....");
}
public void save() {
System.out.println("book dao save ...");
}

}

运行程序,

程序会报错,说明 Spring 底层使用的是类的无参构造方法。

1629776331499

4.2.3 分析 Spring 的错误信息

接下来,我们主要研究下 Spring 的报错信息来学一学如阅读。

  • 错误信息从下往上依次查看,因为上面的错误大都是对下面错误的一个包装,最核心错误是在最下面
  • Caused by: java.lang.NoSuchMethodException: com.itheima.dao.impl.BookDaoImpl.<init>()
    • Caused by 翻译为引起,即出现错误的原因
    • java.lang.NoSuchMethodException:抛出的异常为没有这样的方法异常
    • com.itheima.dao.impl.BookDaoImpl.<init>():哪个类的哪个方法没有被找到导致的异常,<init>()指定是类的构造方法,即该类的无参构造方法

如果最后一行错误获取不到错误信息,接下来查看第二层:

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.itheima.dao.impl.BookDaoImpl]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.itheima.dao.impl.BookDaoImpl.<init>()

  • nested:嵌套的意思,后面的异常内容和最底层的异常是一致的
  • Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.itheima.dao.impl.BookDaoImpl]: No default constructor found;
    • Caused by: 引发
    • BeanInstantiationException:翻译为bean实例化异常
    • No default constructor found:没有一个默认的构造函数被发现

看到这其实错误已经比较明显,给大家个练习,把倒数第三层的错误分析下吧:

Exception in thread “main” org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘bookDao’ defined in class path resource [applicationContext.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.itheima.dao.impl.BookDaoImpl]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.itheima.dao.impl.BookDaoImpl.<init>()。

至此,关于 Spring 的构造方法实例化就已经学习完了,因为每一个类默认都会提供一个无参构造函数,所以其实真正在使用这种方式的时候,我们什么也不需要做。这也是我们以后比较常用的一种方式。

4.2.4 静态工厂实例化

接下来研究 Spring 中的第二种 bean 的创建方式静态工厂实例化:

4.2.4.1 工厂方式创建 bean

在讲这种方式之前,我们需要先回顾一个知识点是使用工厂来创建对象的方式:

(1)准备一个 OrderDao 和 OrderDaoImpl 类

1
2
3
4
5
6
7
8
9
public interface OrderDao {
public void save();
}

public class OrderDaoImpl implements OrderDao {
public void save() {
System.out.println("order dao save ...");
}
}

(2)创建一个工厂类 OrderDaoFactory 并提供一个==静态方法==

1
2
3
4
5
6
//静态工厂创建对象
public class OrderDaoFactory {
public static OrderDao getOrderDao(){
return new OrderDaoImpl();
}
}

(3)编写 AppForInstanceOrder 运行类,在类中通过工厂获取对象

1
2
3
4
5
6
7
public class AppForInstanceOrder {
public static void main(String[] args) {
//通过静态工厂创建对象
OrderDao orderDao = OrderDaoFactory.getOrderDao();
orderDao.save();
}
}

(4)运行后,可以查看到结果

1629786862329

如果代码中对象是通过上面的这种方式来创建的,如何将其交给 Spring 来管理呢?

4.2.4.2 静态工厂实例化

这就要用到 Spring 中的静态工厂实例化的知识了,具体实现步骤为:

(1)在 spring 的配置文件 application.properties 中添加以下内容:

1
<bean id="orderDao" class="com.itheima.factory.OrderDaoFactory" factory-method="getOrderDao"/>

class:工厂类的类全名

factory-mehod:具体工厂类中创建对象的方法名

对应关系如下图:

image-20210729195248948

(2)在 AppForInstanceOrder 运行类,使用从 IOC 容器中获取 bean 的方法进行运行测试

1
2
3
4
5
6
7
8
9
10
public class AppForInstanceOrder {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

OrderDao orderDao = (OrderDao) ctx.getBean("orderDao");

orderDao.save();

}
}

(3)运行后,可以查看到结果

1629786862329

看到这,可能有人会问了,你这种方式在工厂类中不也是直接 new 对象的,和我自己直接 new 没什么太大的区别,而且静态工厂的方式反而更复杂,这种方式的意义是什么?

主要的原因是:

  • 在工厂的静态方法中,我们除了 new 对象还可以做其他的一些业务操作,这些操作必不可少,如:
1
2
3
4
5
6
public class OrderDaoFactory {
public static OrderDao getOrderDao(){
System.out.println("factory setup....");//模拟必要的业务操作
return new OrderDaoImpl();
}
}

之前 new 对象的方式就无法添加其他的业务内容,重新运行,查看结果:

1629788036885

介绍完静态工厂实例化后,这种方式一般是用来兼容早期的一些老系统,所以==了解为主==。

4.2.5 实例工厂与 FactoryBean

接下来继续来研究 Spring 的第三种 bean 的创建方式实例工厂实例化:

4.2.3.1 环境准备

(1)准备一个 UserDao 和 UserDaoImpl 类

1
2
3
4
5
6
7
8
9
10
public interface UserDao {
public void save();
}

public class UserDaoImpl implements UserDao {

public void save() {
System.out.println("user dao save ...");
}
}

(2)创建一个工厂类 OrderDaoFactory 并提供一个普通方法,注意此处和静态工厂的工厂类不一样的地方是方法不是静态方法

1
2
3
4
5
public class UserDaoFactory {
public UserDao getUserDao(){
return new UserDaoImpl();
}
}

(3)编写 AppForInstanceUser 运行类,在类中通过工厂获取对象

1
2
3
4
5
6
7
8
public class AppForInstanceUser {
public static void main(String[] args) {
//创建实例工厂对象
UserDaoFactory userDaoFactory = new UserDaoFactory();
//通过实例工厂对象创建对象
UserDao userDao = userDaoFactory.getUserDao();
userDao.save();
}

(4)运行后,可以查看到结果

1629788769436

对于上面这种实例工厂的方式如何交给 Spring 管理呢?

4.2.3.2 实例工厂实例化

具体实现步骤为:

(1)在 spring 的配置文件中添加以下内容:

1
2
<bean id="userFactory" class="com.itheima.factory.UserDaoFactory"/>
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>

实例化工厂运行的顺序是:

  • 创建实例化工厂对象,对应的是第一行配置

  • 调用对象中的方法来创建 bean,对应的是第二行配置

    • factory-bean:工厂的实例对象

    • factory-method:工厂对象中的具体创建对象的方法名,对应关系如下:

      image-20210729200203249

factory-mehod:具体工厂类中创建对象的方法名

(2)在 AppForInstanceUser 运行类,使用从 IOC 容器中获取 bean 的方法进行运行测试

1
2
3
4
5
6
7
8
public class AppForInstanceUser {
public static void main(String[] args) {
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) ctx.getBean("userDao");
userDao.save();
}
}

(3)运行后,可以查看到结果

1629788769436

实例工厂实例化的方式就已经介绍完了,配置的过程还是比较复杂,所以 Spring 为了简化这种配置方式就提供了一种叫FactoryBean的方式来简化开发。

4.2.3.3 FactoryBean 的使用

具体的使用步骤为:

(1)创建一个 UserDaoFactoryBean 的类,实现 FactoryBean 接口,重写接口的方法

1
2
3
4
5
6
7
8
9
10
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
//代替原始实例工厂中创建对象的方法
public UserDao getObject() throws Exception {
return new UserDaoImpl();
}
//返回所创建类的Class对象
public Class<?> getObjectType() {
return UserDao.class;
}
}

(2)在 Spring 的配置文件中进行配置

1
<bean id="userDao" class="com.itheima.factory.UserDaoFactoryBean"/>

(3)AppForInstanceUser 运行类不用做任何修改,直接运行

1629788769436

这种方式在 Spring 去整合其他框架的时候会被用到,所以这种方式需要大家理解掌握。

查看源码会发现,FactoryBean 接口其实会有三个方法,分别是:

1
2
3
4
5
6
7
T getObject() throws Exception;

Class<?> getObjectType();

default boolean isSingleton() {
return true;
}

方法一:getObject(),被重写后,在方法中进行对象的创建并返回

方法二:getObjectType(),被重写后,主要返回的是被创建类的 Class 对象

方法三:没有被重写,因为它已经给了默认值,从方法名中可以看出其作用是设置对象是否为单例,默认 true,从意思上来看,我们猜想默认应该是单例,如何来验证呢?

思路很简单,就是从容器中获取该对象的多个值,打印到控制台,查看是否为同一个对象。

1
2
3
4
5
6
7
8
9
10
public class AppForInstanceUser {
public static void main(String[] args) {
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao1 = (UserDao) ctx.getBean("userDao");
UserDao userDao2 = (UserDao) ctx.getBean("userDao");
System.out.println(userDao1);
System.out.println(userDao2);
}
}

打印结果,如下:

1629790070607

通过验证,会发现默认是单例,那如果想改成单例具体如何实现?

只需要将 isSingleton()方法进行重写,修改返回为 false,即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//FactoryBean创建对象
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
//代替原始实例工厂中创建对象的方法
public UserDao getObject() throws Exception {
return new UserDaoImpl();
}

public Class<?> getObjectType() {
return UserDao.class;
}

public boolean isSingleton() {
return false;
}
}

重新运行 AppForInstanceUser,查看结果

1629790197860

从结果中可以看出现在已经是非单例了,但是一般情况下我们都会采用单例,也就是采用默认即可。所以 isSingleton()方法一般不需要进行重写。

4.2.6 bean 实例化小结

通过这一节的学习,需要掌握:

(1)bean 是如何创建的呢?

1
构造方法

(2)Spring 的 IOC 实例化对象的三种方式分别是:

  • 构造方法(常用)
  • 静态工厂(了解)
  • 实例工厂(了解)
    • FactoryBean(实用)

这些方式中,重点掌握构造方法FactoryBean即可。

需要注意的一点是,构造方法在类中默认会提供,但是如果重写了构造方法,默认的就会消失,在使用的过程中需要注意,如果需要重写构造方法,最好把默认的构造方法也重写下。

4.3 bean 的生命周期

关于 bean 的相关知识还有最后一个是bean的生命周期,对于生命周期,我们主要围绕着bean生命周期控制来讲解:

  • 首先理解下什么是生命周期?
    • 从创建到消亡的完整过程,例如人从出生到死亡的整个过程就是一个生命周期。
  • bean 生命周期是什么?
    • bean 对象从创建到销毁的整体过程。
  • bean 生命周期控制是什么?
    • 在 bean 创建后到销毁前做一些事情。

现在我们面临的问题是如何在 bean 的创建之后和销毁之前把我们需要添加的内容添加进去。

4.3.1 环境准备

还是老规矩,为了方便大家后期代码的阅读,我们重新搭建下环境:

  • 创建一个 Maven 项目
  • pom.xml 添加依赖
  • resources 下添加 spring 的配置文件 applicationContext.xml

这些步骤和前面的都一致,大家可以快速的拷贝即可,最终项目的结构如下:

1629791473409

(1)项目中添加 BookDao、BookDaoImpl、BookService 和 BookServiceImpl 类

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
public interface BookDao {
public void save();
}

public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}

public interface BookService {
public void save();
}

public class BookServiceImpl implements BookService{
private BookDao bookDao;

public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}

public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}

(2)resources 下提供 spring 的配置文件

1
2
3
4
5
6
7
<?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="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
</beans>

(3)编写 AppForLifeCycle 运行类,加载 Spring 的 IOC 容器,并从中获取对应的 bean 对象

1
2
3
4
5
6
7
8
public class AppForLifeCycle {
public static void main( String[] args ) {
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
}
}

4.3.2 生命周期设置

接下来,在上面这个环境中来为 BookDao 添加生命周期的控制方法,具体的控制有两个阶段:

  • bean 创建之后,想要添加内容,比如用来初始化需要用到资源
  • bean 销毁之前,想要添加内容,比如用来释放用到的资源
步骤 1:添加初始化和销毁方法

针对这两个阶段,我们在 BooDaoImpl 类中分别添加两个方法,==方法名任意==

1
2
3
4
5
6
7
8
9
10
11
12
13
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
//表示bean初始化对应的操作
public void init(){
System.out.println("init...");
}
//表示bean销毁前对应的操作
public void destory(){
System.out.println("destory...");
}
}
步骤 2:配置生命周期

在配置文件添加配置,如下:

1
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"/>
步骤 3:运行程序

运行 AppForLifeCycle 打印结果为:

1629792339889

从结果中可以看出,init 方法执行了,但是 destroy 方法却未执行,这是为什么呢?

  • Spring 的 IOC 容器是运行在 JVM 中
  • 运行 main 方法后,JVM 启动,Spring 加载配置文件生成 IOC 容器,从容器获取 bean 对象,然后调方法执行
  • main 方法执行完后,JVM 退出,这个时候 IOC 容器中的 bean 还没有来得及销毁就已经结束了
  • 所以没有调用对应的 destroy 方法

知道了出现问题的原因,具体该如何解决呢?

4.3.3 close 关闭容器

  • ApplicationContext 中没有 close 方法

  • 需要将 ApplicationContext 更换成 ClassPathXmlApplicationContext

    1
    2
    ClassPathXmlApplicationContext ctx = new
    ClassPathXmlApplicationContext("applicationContext.xml");
  • 调用 ctx 的 close()方法

    1
    ctx.close();
  • 运行程序,就能执行 destroy 方法的内容

    1629792857608

4.3.4 注册钩子关闭容器

  • 在容器未关闭之前,提前设置好回调函数,让 JVM 在退出之前回调此函数来关闭容器

  • 调用 ctx 的 registerShutdownHook()方法

    1
    ctx.registerShutdownHook();

    注意:registerShutdownHook 在 ApplicationContext 中也没有

  • 运行后,查询打印结果

    1629792857608

两种方式介绍完后,close 和 registerShutdownHook 选哪个?

相同点:这两种都能用来关闭容器

不同点:close()是在调用的时候关闭,registerShutdownHook()是在 JVM 退出前调用关闭。

分析上面的实现过程,会发现添加初始化和销毁方法,即需要编码也需要配置,实现起来步骤比较多也比较乱。

Spring 提供了两个接口来完成生命周期的控制,好处是可以不用再进行配置init-methoddestroy-method

接下来在 BookServiceImpl 完成这两个接口的使用:

修改 BookServiceImpl 类,添加两个接口InitializingBeanDisposableBean并实现接口中的两个方法afterPropertiesSetdestroy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
public void destroy() throws Exception {
System.out.println("service destroy");
}
public void afterPropertiesSet() throws Exception {
System.out.println("service init");
}
}

重新运行 AppForLifeCycle 类,

1629794527419

那第二种方式的实现,我们也介绍完了。

小细节

  • 对于 InitializingBean 接口中的 afterPropertiesSet 方法,翻译过来为属性设置之后

  • 对于 BookServiceImpl 来说,bookDao 是它的一个属性

  • setBookDao 方法是 Spring 的 IOC 容器为其注入属性的方法

  • 思考:afterPropertiesSet 和 setBookDao 谁先执行?

    • 从方法名分析,猜想应该是 setBookDao 方法先执行

    • 验证思路,在 setBookDao 方法中添加一句话

      1
      2
      3
      4
      5
      public void setBookDao(BookDao bookDao) {
      System.out.println("set .....");
      this.bookDao = bookDao;
      }

    • 重新运行 AppForLifeCycle,打印结果如下:

      1629794928636

      验证的结果和我们猜想的结果是一致的,所以初始化方法会在类中属性设置之后执行。

4.3.5 bean 生命周期小结

(1)关于 Spring 中对 bean 生命周期控制提供了两种方式:

  • 在配置文件中的 bean 标签中添加init-methoddestroy-method属性
  • 类实现InitializingBeanDisposableBean接口,这种方式了解下即可。

(2)对于 bean 的生命周期控制在 bean 的整个生命周期中所处的位置如下:

  • 初始化容器
    • 1.创建对象(内存分配)
    • 2.执行构造方法
    • 3.执行属性注入(set 操作)
    • ==4.执行 bean 初始化方法==
  • 使用 bean
    • 1.执行业务操作
  • 关闭/销毁容器
    • ==1.执行 bean 销毁方法==

(3)关闭容器的两种方式:

  • ConfigurableApplicationContext 是 ApplicationContext 的子类
    • close()方法
    • registerShutdownHook()方法

5,DI 相关内容

前面我们已经完成了 bean 相关操作的讲解,接下来就进入第二个大的模块DI依赖注入,首先来介绍下 Spring 中有哪些注入方式?

我们先来思考

  • 向一个类中传递数据的方式有几种?
    • 普通方法(set 方法)
    • 构造方法
  • 依赖注入描述了在容器中建立 bean 与 bean 之间的依赖关系的过程,如果 bean 运行需要的是数字或字符串呢?
    • 引用类型
    • 简单类型(基本数据类型与 String)

Spring 就是基于上面这些知识点,为我们提供了两种注入方式,分别是:

  • setter 注入
    • 简单类型
    • ==引用类型==
  • 构造器注入
    • 简单类型
    • 引用类型

依赖注入的方式已经介绍完,接下来挨个学习下:

5.1 setter 注入

  1. 对于 setter 方式注入引用类型的方式之前已经学习过,快速回顾下:
  • 在 bean 中定义引用类型属性,并提供可访问的==set==方法
1
2
3
4
5
6
public class BookServiceImpl implements BookService {
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
  • 配置中使用==property==标签==ref==属性注入引用类型对象
1
2
3
4
5
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>

<bean id="bookDao" class="com.itheima.dao.imipl.BookDaoImpl"/>

5.1.1 环境准备

为了更好的学习下面内容,我们依旧准备一个新环境:

  • 创建一个 Maven 项目
  • pom.xml 添加依赖
  • resources 下添加 spring 的配置文件

这些步骤和前面的都一致,大家可以快速的拷贝即可,最终项目的结构如下:

1629799214191

(1)项目中添加 BookDao、BookDaoImpl、UserDao、UserDaoImpl、BookService 和 BookServiceImpl 类

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
public interface BookDao {
public void save();
}

public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
public interface UserDao {
public void save();
}
public class UserDaoImpl implements UserDao {
public void save() {
System.out.println("user dao save ...");
}
}

public interface BookService {
public void save();
}

public class BookServiceImpl implements BookService{
private BookDao bookDao;

public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}

public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}

(2)resources 下提供 spring 的配置文件

1
2
3
4
5
6
7
8
9
10
<?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="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>
</beans>

(3)编写 AppForDISet 运行类,加载 Spring 的 IOC 容器,并从中获取对应的 bean 对象

1
2
3
4
5
6
7
public class AppForDISet {
public static void main( String[] args ) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}

接下来,在上面这个环境中来完成 setter 注入的学习:

5.1.2 注入引用数据类型

需求:在 bookServiceImpl 对象中注入 userDao

1.在 BookServiceImpl 中声明 userDao 属性

2.为 userDao 属性提供 setter 方法

3.在配置文件中使用 property 标签注入

步骤 1:声明属性并提供 setter 方法

在 BookServiceImpl 中声明 userDao 属性,并提供 setter 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class BookServiceImpl implements BookService{
private BookDao bookDao;
private UserDao userDao;

public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}

public void save() {
System.out.println("book service save ...");
bookDao.save();
userDao.save();
}
}
步骤 2:配置文件中进行注入配置

在 applicationContext.xml 配置文件中使用 property 标签注入

1
2
3
4
5
6
7
8
9
10
11
12
<?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="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
<property name="userDao" ref="userDao"/>
</bean>
</beans>
步骤 3:运行程序

运行 AppForDISet 类,查看结果,说明 userDao 已经成功注入。

1629799873386

5.1.3 注入简单数据类型

需求:给 BookDaoImpl 注入一些简单数据类型的数据

参考引用数据类型的注入,我们可以推出具体的步骤为:

1.在 BookDaoImpl 类中声明对应的简单数据类型的属性

2.为这些属性提供对应的 setter 方法

3.在 applicationContext.xml 中配置

思考:

引用类型使用的是<property name="" ref=""/>,简单数据类型还是使用 ref 么?

ref 是指向 Spring 的 IOC 容器中的另一个 bean 对象的,对于简单数据类型,没有对应的 bean 对象,该如何配置?

步骤 1:声明属性并提供 setter 方法

在 BookDaoImpl 类中声明对应的简单数据类型的属性,并提供对应的 setter 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class BookDaoImpl implements BookDao {

private String databaseName;
private int connectionNum;

public void setConnectionNum(int connectionNum) {
this.connectionNum = connectionNum;
}

public void setDatabaseName(String databaseName) {
this.databaseName = databaseName;
}

public void save() {
System.out.println("book dao save ..."+databaseName+","+connectionNum);
}
}
步骤 2:配置文件中进行注入配置

在 applicationContext.xml 配置文件中使用 property 标签注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
<property name="databaseName" value="mysql"/>
<property name="connectionNum" value="10"/>
</bean>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
<property name="userDao" ref="userDao"/>
</bean>
</beans>

说明:

value:后面跟的是简单数据类型,对于参数类型,Spring 在注入的时候会自动转换,但是不能写成

1
<property name="connectionNum" value="abc"/>

这样的话,spring 在将abc转换成 int 类型的时候就会报错。

步骤 3:运行程序

运行 AppForDISet 类,查看结果,说明 userDao 已经成功注入。

1629800324721

注意:两个 property 注入标签的顺序可以任意。

对于 setter 注入方式的基本使用就已经介绍完了,

  • 对于引用数据类型使用的是<property name="" ref=""/>
  • 对于简单数据类型使用的是<property name="" value=""/>

5.2 构造器注入

5.2.1 环境准备

构造器注入也就是构造方法注入,学习之前,还是先准备下环境:

  • 创建一个 Maven 项目
  • pom.xml 添加依赖
  • resources 下添加 spring 的配置文件

这些步骤和前面的都一致,大家可以快速的拷贝即可,最终项目的结构如下:

1629800748639

(1)项目中添加 BookDao、BookDaoImpl、UserDao、UserDaoImpl、BookService 和 BookServiceImpl 类

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
public interface BookDao {
public void save();
}

public class BookDaoImpl implements BookDao {

private String databaseName;
private int connectionNum;

public void save() {
System.out.println("book dao save ...");
}
}
public interface UserDao {
public void save();
}
public class UserDaoImpl implements UserDao {
public void save() {
System.out.println("user dao save ...");
}
}

public interface BookService {
public void save();
}

public class BookServiceImpl implements BookService{
private BookDao bookDao;

public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}

public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}

(2)resources 下提供 spring 的配置文件

1
2
3
4
5
6
7
8
9
10
<?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="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>
</beans>

(3)编写 AppForDIConstructor 运行类,加载 Spring 的 IOC 容器,并从中获取对应的 bean 对象

1
2
3
4
5
6
7
public class AppForDIConstructor {
public static void main( String[] args ) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}

5.2.2 构造器注入引用数据类型

接下来,在上面这个环境中来完成构造器注入的学习:

需求:将 BookServiceImpl 类中的 bookDao 修改成使用构造器的方式注入。

1.将 bookDao 的 setter 方法删除掉

2.添加带有 bookDao 参数的构造方法

3.在 applicationContext.xml 中配置

步骤 1:删除 setter 方法并提供构造方法

在 BookServiceImpl 类中将 bookDao 的 setter 方法删除掉,并添加带有 bookDao 参数的构造方法

1
2
3
4
5
6
7
8
9
10
11
12
public class BookServiceImpl implements BookService{
private BookDao bookDao;

public BookServiceImpl(BookDao bookDao) {
this.bookDao = bookDao;
}

public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
步骤 2:配置文件中进行配置构造方式注入

在 applicationContext.xml 中配置

1
2
3
4
5
6
7
8
9
10
<?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="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<constructor-arg name="bookDao" ref="bookDao"/>
</bean>
</beans>

说明:

标签

  • name 属性对应的值为构造函数中方法形参的参数名,必须要保持一致。

  • ref 属性指向的是 spring 的 IOC 容器中其他 bean 对象。

步骤 3:运行程序

运行 AppForDIConstructor 类,查看结果,说明 bookDao 已经成功注入。

1629802656916

5.2.3 构造器注入多个引用数据类型

需求:在 BookServiceImpl 使用构造函数注入多个引用数据类型,比如 userDao

1.声明 userDao 属性

2.生成一个带有 bookDao 和 userDao 参数的构造函数

3.在 applicationContext.xml 中配置注入

步骤 1:提供多个属性的构造函数

在 BookServiceImpl 声明 userDao 并提供多个参数的构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class BookServiceImpl implements BookService{
private BookDao bookDao;
private UserDao userDao;

public BookServiceImpl(BookDao bookDao,UserDao userDao) {
this.bookDao = bookDao;
this.userDao = userDao;
}

public void save() {
System.out.println("book service save ...");
bookDao.save();
userDao.save();
}
}

步骤 2:配置文件中配置多参数注入

在 applicationContext.xml 中配置注入

1
2
3
4
5
6
7
8
9
10
11
12
<?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="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<constructor-arg name="bookDao" ref="bookDao"/>
<constructor-arg name="userDao" ref="userDao"/>
</bean>
</beans>

说明:这两个<contructor-arg>的配置顺序可以任意

步骤 3:运行程序

运行 AppForDIConstructor 类,查看结果,说明 userDao 已经成功注入。

1629802697318

5.2.4 构造器注入多个简单数据类型

需求:在 BookDaoImpl 中,使用构造函数注入 databaseName 和 connectionNum 两个参数。

参考引用数据类型的注入,我们可以推出具体的步骤为:

1.提供一个包含这两个参数的构造方法

2.在 applicationContext.xml 中进行注入配置

步骤 1:添加多个简单属性并提供构造方法

修改 BookDaoImpl 类,添加构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
public class BookDaoImpl implements BookDao {
private String databaseName;
private int connectionNum;

public BookDaoImpl(String databaseName, int connectionNum) {
this.databaseName = databaseName;
this.connectionNum = connectionNum;
}

public void save() {
System.out.println("book dao save ..."+databaseName+","+connectionNum);
}
}
步骤 2:配置完成多个属性构造器注入

在 applicationContext.xml 中进行注入配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
<constructor-arg name="databaseName" value="mysql"/>
<constructor-arg name="connectionNum" value="666"/>
</bean>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<constructor-arg name="bookDao" ref="bookDao"/>
<constructor-arg name="userDao" ref="userDao"/>
</bean>
</beans>

说明:这两个<contructor-arg>的配置顺序可以任意

步骤 3:运行程序

运行 AppForDIConstructor 类,查看结果

1629803111769

上面已经完成了构造函数注入的基本使用,但是会存在一些问题:

1629803529598

  • 当构造函数中方法的参数名发生变化后,配置文件中的 name 属性也需要跟着变
  • 这两块存在紧耦合,具体该如何解决?

在解决这个问题之前,需要提前说明的是,这个参数名发生变化的情况并不多,所以上面的还是比较主流的配置方式,下面介绍的,大家都以了解为主。

方式一:删除 name 属性,添加 type 属性,按照类型注入

1
2
3
4
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
<constructor-arg type="int" value="10"/>
<constructor-arg type="java.lang.String" value="mysql"/>
</bean>
  • 这种方式可以解决构造函数形参名发生变化带来的耦合问题
  • 但是如果构造方法参数中有类型相同的参数,这种方式就不太好实现了

方式二:删除 type 属性,添加 index 属性,按照索引下标注入,下标从 0 开始

1
2
3
4
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
<constructor-arg index="1" value="100"/>
<constructor-arg index="0" value="mysql"/>
</bean>
  • 这种方式可以解决参数类型重复问题
  • 但是如果构造方法参数顺序发生变化后,这种方式又带来了耦合问题

介绍完两种参数的注入方式,具体我们该如何选择呢?

  1. 强制依赖使用构造器进行,使用 setter 注入有概率不进行注入导致 null 对象出现
    • 强制依赖指对象在创建的过程中必须要注入指定的参数
  2. 可选依赖使用 setter 注入进行,灵活性强
    • 可选依赖指对象在创建过程中注入的参数可有可无
  3. Spring 框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨
  4. 如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用 setter 注入完成可选依赖的注入
  5. 实际开发过程中还要根据实际情况分析,如果受控对象没有提供 setter 方法就必须使用构造器注入
  6. ==自己开发的模块推荐使用 setter 注入==

这节中主要讲解的是 Spring 的依赖注入的实现方式:

  • setter 注入

    • 简单数据类型

      1
      2
      3
      <bean ...>
      <property name="" value=""/>
      </bean>
    • 引用数据类型

      1
      2
      3
      <bean ...>
      <property name="" ref=""/>
      </bean>
  • 构造器注入

    • 简单数据类型

      1
      2
      3
      <bean ...>
      <constructor-arg name="" index="" type="" value=""/>
      </bean>
    • 引用数据类型

      1
      2
      3
      <bean ...>
      <constructor-arg name="" index="" type="" ref=""/>
      </bean>
  • 依赖注入的方式选择上

    • 建议使用 setter 注入
    • 第三方技术根据情况选择

5.3 自动配置

前面花了大量的时间把 Spring 的注入去学习了下,总结起来就一个字==麻烦==。

问:麻烦在哪?

答:配置文件的编写配置上。

问:有更简单方式么?

答:有,自动配置

什么是自动配置以及如何实现自动配置,就是接下来要学习的内容:

5.3.1 什么是依赖自动装配?

  • IoC 容器根据 bean 所依赖的资源在容器中自动查找并注入到 bean 中的过程称为自动装配

5.3.2 自动装配方式有哪些?

  • ==按类型(常用)==
  • 按名称
  • 按构造方法
  • 不启用自动装配

5.3.3 准备下案例环境

  • 创建一个 Maven 项目
  • pom.xml 添加依赖
  • resources 下添加 spring 的配置文件

这些步骤和前面的都一致,大家可以快速的拷贝即可,最终项目的结构如下:

1629805387647

(1)项目中添加 BookDao、BookDaoImpl、BookService 和 BookServiceImpl 类

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
public interface BookDao {
public void save();
}

public class BookDaoImpl implements BookDao {

private String databaseName;
private int connectionNum;

public void save() {
System.out.println("book dao save ...");
}
}
public interface BookService {
public void save();
}

public class BookServiceImpl implements BookService{
private BookDao bookDao;

public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}

public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}

(2)resources 下提供 spring 的配置文件

1
2
3
4
5
6
7
8
9
10
<?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="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>
</beans>

(3)编写 AppForAutoware 运行类,加载 Spring 的 IOC 容器,并从中获取对应的 bean 对象

1
2
3
4
5
6
7
public class AppForAutoware {
public static void main( String[] args ) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}

5.3.4 完成自动装配的配置

接下来,在上面这个环境中来完成自动装配的学习:

自动装配只需要修改 applicationContext.xml 配置文件即可:

(1)将<property>标签删除

(2)在<bean>标签中添加 autowire 属性

首先来实现按照类型注入的配置

1
2
3
4
5
6
7
8
9
10
<?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 class="com.itheima.dao.impl.BookDaoImpl"/>
<!--autowire属性:开启自动装配,通常使用按类型装配-->
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl" autowire="byType"/>

</beans>

==注意事项:==

  • 需要注入属性的类中对应属性的 setter 方法不能省略
  • 被注入的对象必须要被 Spring 的 IOC 容器管理
  • 按照类型在 Spring 的 IOC 容器中如果找到多个对象,会报NoUniqueBeanDefinitionException

一个类型在 IOC 中有多个对象,还想要注入成功,这个时候就需要按照名称注入,配置方式为:

1
2
3
4
5
6
7
8
9
10
<?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 class="com.itheima.dao.impl.BookDaoImpl"/>
<!--autowire属性:开启自动装配,通常使用按类型装配-->
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl" autowire="byName"/>

</beans>

==注意事项:==

  • 按照名称注入中的名称指的是什么?

    1629806856156

    • bookDao 是 private 修饰的,外部类无法直接方法
    • 外部类只能通过属性的 set 方法进行访问
    • 对外部类来说,setBookDao 方法名,去掉 set 后首字母小写是其属性名
      • 为什么是去掉 set 首字母小写?
      • 这个规则是 set 方法生成的默认规则,set 方法的生成是把属性名首字母大写前面加 set 形成的方法名
    • 所以按照名称注入,其实是和对应的 set 方法有关,但是如果按照标准起名称,属性名和 set 对应的名是一致的
  • 如果按照名称去找对应的 bean 对象,找不到则注入 Null

  • 当某一个类型在 IOC 容器中有多个对象,按照名称注入只找其指定名称对应的 bean 对象,不会报错

两种方式介绍完后,以后用的更多的是==按照类型==注入。

最后对于依赖注入,需要注意一些其他的配置特征:

  1. 自动装配用于引用类型依赖注入,不能对简单类型进行操作
  2. 使用按类型装配时(byType)必须保障容器中相同类型的 bean 唯一,推荐使用
  3. 使用按名称装配时(byName)必须保障容器中具有指定名称的 bean,因变量名与配置耦合,不推荐使用
  4. 自动装配优先级低于 setter 注入与构造器注入,同时出现时自动装配配置失效

5.4 集合注入

前面我们已经能完成引入数据类型和简单数据类型的注入,但是还有一种数据类型==集合==,集合中既可以装简单数据类型也可以装引用数据类型,对于集合,在 Spring 中该如何注入呢?

先来回顾下,常见的集合类型有哪些?

  • 数组
  • List
  • Set
  • Map
  • Properties

针对不同的集合类型,该如何实现注入呢?

5.4.1 环境准备

  • 创建一个 Maven 项目
  • pom.xml 添加依赖
  • resources 下添加 spring 的配置文件 applicationContext.xml

这些步骤和前面的都一致,大家可以快速的拷贝即可,最终项目的结构如下:

1629807579330

(1)项目中添加添加 BookDao、BookDaoImpl 类

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
public interface BookDao {
public void save();
}

public class BookDaoImpl implements BookDao {

public class BookDaoImpl implements BookDao {

private int[] array;

private List<String> list;

private Set<String> set;

private Map<String,String> map;

private Properties properties;

public void save() {
System.out.println("book dao save ...");

System.out.println("遍历数组:" + Arrays.toString(array));

System.out.println("遍历List" + list);

System.out.println("遍历Set" + set);

System.out.println("遍历Map" + map);

System.out.println("遍历Properties" + properties);
}
//setter....方法省略,自己使用工具生成
}

(2)resources 下提供 spring 的配置文件,applicationContext.xml

1
2
3
4
5
6
7
<?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="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
</beans>

(3)编写 AppForDICollection 运行类,加载 Spring 的 IOC 容器,并从中获取对应的 bean 对象

1
2
3
4
5
6
7
public class AppForDICollection {
public static void main( String[] args ) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
}
}

接下来,在上面这个环境中来完成集合注入的学习:

下面的所以配置方式,都是在 bookDao 的 bean 标签中使用进行注入

1
2
3
4
5
6
7
8
9
<?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="bookDao" class="com.itheima.dao.impl.BookDaoImpl">

</bean>
</beans>

5.4.2 注入数组类型数据

1
2
3
4
5
6
7
<property name="array">
<array>
<value>100</value>
<value>200</value>
<value>300</value>
</array>
</property>

5.4.3 注入 List 类型数据

1
2
3
4
5
6
7
8
<property name="list">
<list>
<value>itcast</value>
<value>itheima</value>
<value>boxuegu</value>
<value>chuanzhihui</value>
</list>
</property>

5.4.4 注入 Set 类型数据

1
2
3
4
5
6
7
8
<property name="set">
<set>
<value>itcast</value>
<value>itheima</value>
<value>boxuegu</value>
<value>boxuegu</value>
</set>
</property>

5.4.5 注入 Map 类型数据

1
2
3
4
5
6
7
<property name="map">
<map>
<entry key="country" value="china"/>
<entry key="province" value="henan"/>
<entry key="city" value="kaifeng"/>
</map>
</property>

5.4.6 注入 Properties 类型数据

1
2
3
4
5
6
7
<property name="properties">
<props>
<prop key="country">china</prop>
<prop key="province">henan</prop>
<prop key="city">kaifeng</prop>
</props>
</property>

配置完成后,运行下看结果:

1629808046783

说明:

  • property 标签表示 setter 方式注入,构造方式注入 constructor-arg 标签内部也可以写<array><list><set><map><props>标签
  • List 的底层也是通过数组实现的,所以<list><array>标签是可以混用
  • 集合中要添加引用类型,只需要把<value>标签改成<ref>标签,这种方式用的比较少