Spring-day01
Last updated
Was this helpful?
Last updated
Was this helpful?
对于一门新技术,我们需要从为什么要学
、学什么
以及怎么学
这三个方向入手来学习。那对于 Spring 来说:
从使用和占有率看
Spring 在市场的占有率与使用率高
Spring 在企业的技术选型命中率高
所以说,Spring 技术是 JavaEE 开发必备技能,企业开发技术选型命中率>90%
说明:对于未使用 Spring 的项目一般都是些比较老的项目,大多都处于维护阶段。
从专业角度看
随着时代发展,软件规模与功能都呈几何式增长,开发难度也在不断递增,该如何解决?
Spring 可以简化开发,降低企业级开发的复杂性,使开发变得更简单快捷
随着项目规模与功能的增长,遇到的问题就会增多,为了解决问题会引入更多的框架,这些框架如何协调工作?
Spring 可以框架整合,高效整合其他技术,提高企业级应用开发与运行效率
综上所述,Spring 是一款非常优秀而且功能强大的框架,不仅要学,而且还要学好。
从上面的介绍中,我们可以看到 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 的具体应用)
学习 Spring 框架设计思想
对于 Spring 来说,它能迅速占领全球市场,不只是说它的某个功能比较强大,更重要是在它的思想
上。
学习基础操作,思考操作与思想间的联系
掌握了 Spring 的设计思想,然后就需要通过一些基础操作来思考操作与思想之间的关联关系
学习案例,熟练应用操作的同时,体会思想
会了基础操作后,就需要通过大量案例来熟练掌握框架的具体应用,加深对设计思想的理解。
介绍完为什么要学
、学什么
和怎么学
Spring 框架后,大家需要重点掌握的是:
Spring 很优秀,需要认真重点的学习
Spring 的学习主线是 IOC、AOP、声明式事务和整合 MyBais
接下来,咱们就开始进入 Spring 框架的学习。
在这一节,主要通过以下两个点来了解下 Spring:
2.1.1 Spring 家族
官网:https://spring.io,从官网我们可以大概了解到:
Spring 能做什么:用以开发 web、微服务以及分布式系统等,光这三块就已经占了 JavaEE 开发的九成多。
Spring 并不是单一的一个技术,而是一个大家族,可以从官网的Projects
中查看其包含的所有技术。
Spring 发展到今天已经形成了一种开发的生态圈,Spring 提供了若干个项目,每个项目用于完成特定的功能。
Spring 已形成了完整的生态圈,也就是说我们可以完全使用 Spring 技术完成整个项目的构建、设计与开发。
Spring 有若干个项目,可以根据需要自行选择,把这些个项目组合起来,起了一个名称叫==全家桶==,如下图所示
说明:
图中的图标都代表什么含义,可以进入https://spring.io/projects
网站进行对比查看。
这些技术并不是所有的都需要学习,额外需要重点关注Spring Framework
、SpringBoot
和SpringCloud
:
Spring Framework:Spring 框架,是 Spring 中最早最核心的技术,也是所有其他技术的基础。
SpringBoot:Spring 是来简化开发,而 SpringBoot 是来帮助 Spring 在简化的基础上能更快速进行开发。
SpringCloud:这个是用来做分布式之微服务架构的相关开发。
除了上面的这三个技术外,还有很多其他的技术,也比较流行,如 SpringData,SpringSecurity 等,这些都可以被应用在我们的项目中。我们今天所学习的 Spring 其实指的是==Spring Framework==。
2.1.2 了解 Spring 发展史
接下来我们介绍下 Spring Framework 这个技术是如何来的呢?
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 框架的学习打好基础
前面我们说 spring 指的是 Spring Framework,那么它其中都包含哪些内容以及我们该如何学习这个框架?
针对这些问题,我们将从系统架构图
和课程学习路线
来进行说明:
2.2.1 系统架构图
Spring Framework 是 Spring 生态圈中最基础的项目,是其他项目的根基。
Spring Framework 的发展也经历了很多版本的变更,每个版本都有相应的调整
Spring Framework 的 5 版本目前没有最新的架构图,而最新的是 4 版本,所以接下来主要研究的是 4 的架构图
(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==
对于这节的内容,大家重点要记住的是 Spring 需要学习的四部分内容。接下来就从第一部分开始学起。
在 Spring 核心概念这部分内容中主要包含IOC/DI
、IOC容器
和Bean
,那么问题就来了,这些都是什么呢?
2.3.1 目前项目中的问题
要想解答这个问题,就需要先分析下目前咱们代码在编写过程中遇到的问题:
(1)业务层需要调用数据层的方法,就需要在业务层 new 数据层的对象
(2)如果数据层的实现类发生变化,那么业务层的代码也需要跟着改变,发生变更后,都需要进行编译打包和重部署
(3)所以,现在代码在编写的过程中存在的问题是:==耦合度偏高==
针对这个问题,该如何解决呢?
我们就想,如果能把框中的内容给去掉,不就可以降低依赖了么,但是又会引入新的问题,去掉以后程序能运行么?
答案肯定是不行,因为 bookDao 没有赋值为 Null,强行运行就会出空指针异常。
所以现在的问题就是,业务层不想 new 对象,运行的时候又需要这个对象,该咋办呢?
针对这个问题,Spring 就提出了一个解决方案:
使用对象时,在程序中不要主动使用 new 产生对象,转换为由==外部==提供对象
这种实现思就是 Spring 的一个核心概念
2.3.2 IOC、IOC 容器、Bean、DI
==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:
==DI(Dependency Injection)依赖注入==
(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 对象
介绍完 Spring 的核心概念后,接下来我们得思考一个问题就是,Spring 到底是如何来实现 IOC 和 DI 的,那接下来就通过一些简单的入门案例,来演示下具体实现过程:
对于入门案例,我们得先分析思路
然后再代码实现
,
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 项目
步骤 2:添加 Spring 的依赖 jar 包
pom.xml
步骤 3:添加案例中需要的类
创建 BookService,BookServiceImpl,BookDao 和 BookDaoImpl 四个类
步骤 4:添加 spring 配置文件
resources 下添加 spring 配置文件 applicationContext.xml,并完成 bean 的配置
步骤 5:在配置文件中完成 bean 的配置
==注意事项:bean 定义时 id 属性在同一个上下文中(配置文件)不能重复==
步骤 6:获取 IOC 容器
使用 Spring 提供的接口完成 IOC 容器的创建,创建 App 类,编写 main 方法
步骤 7:从容器中获取对象进行方法调用
步骤 8:运行程序
测试结果为:
Spring 的 IOC 入门案例已经完成,但是在BookServiceImpl
的类中依然存在BookDaoImpl
对象的 new 操作,它们之间的耦合度还是比较高,这块该如何解决,就需要用到下面的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 对象
步骤 2:为属性提供 setter 方法
在 BookServiceImpl 类中,为 BookDao 提供 setter 方法
步骤 3:修改配置完成注入
在配置文件中添加依赖注入的配置
==注意:配置中的两个 bookDao 的含义是不一样的==
name="bookDao"中bookDao
的作用是让 Spring 的 IOC 容器在获取到名称后,将首字母大写,前面加 set 找对应的setBookDao()
方法进行对象注入
ref="bookDao"中bookDao
的作用是让 Spring 能在 IOC 容器中找到 id 为bookDao
的 Bean 对象给bookService
进行注入
综上所述,对应关系如下:
步骤 4:运行程序
运行,测试结果为:
通过前面两个案例,我们已经学习了bean如何定义配置
,DI如何定义配置
以及容器对象如何获取
的内容,接下来主要是把这三块内容展开进行详细的讲解,深入的学习下这三部分的内容,首先是 bean 基础配置。
对于 bean 的配置中,主要会讲解bean基础配置
,bean的别名配置
,bean的作用范围配置
==(重点)==,这三部分内容:
4.1.1 bean 基础配置(id 与 class)
对于 bean 的基础配置,在前面的案例中已经使用过:
其中,bean 标签的功能、使用方式以及 id 和 class 属性的作用,我们通过一张图来描述下
这其中需要大家重点掌握的是:==bean 标签的 id 和 class 属性的使用==。
思考:
class 属性能不能写接口如BookDao
的类全名呢?
答案肯定是不行,因为接口是没办法创建对象的。
前面提过为 bean 设置 id 时,id 必须唯一,但是如果由于命名习惯而产生了分歧后,该如何解决?
在解决这个问题之前,我们需要准备下开发环境,对于开发环境我们可以有两种解决方案:
使用前面 IOC 和 DI 的案例
重新搭建一个新的案例环境,目的是方便大家查阅代码
搭建的内容和前面的案例是一样的,内容如下:
4.1.2 bean 的 name 属性
环境准备好后,接下来就可以在这个环境的基础上来学习下 bean 的别名配置,
首先来看下别名的配置说明:
步骤 1:配置别名
打开 spring 的配置文件 applicationContext.xml
说明:Ebi 全称 Enterprise Business Interface,翻译为企业业务接口
步骤 2:根据名称容器中获取 bean 对象
步骤 3:运行程序
测试结果为:
==注意事项:==
bean 依赖注入的 ref 属性指定 bean,必须在容器中存在
如果不存在,则会报错,如下:
这个错误大家需要特别关注下:
获取 bean 无论是通过 id 还是 name 获取,如果无法获取到,将抛出异常==NoSuchBeanDefinitionException==
4.1.3 bean 作用范围 scope 配置
关于 bean 的作用范围是 bean 属性配置的一个==重点==内容。
看到这个作用范围,我们就得思考 bean 的作用范围是来控制 bean 哪块内容的?
我们先来看下bean作用范围的配置属性
:
4.1.3.1 验证 IOC 容器中对象是否为单例
验证思路
同一个 bean 获取两次,将对象打印到控制台,看打印出的地址值是否一致。
具体实现
创建一个 AppForScope 的类,在其 main 方法中来验证
打印,观察控制台的打印结果
结论:默认情况下,Spring 创建的 bean 对象都是单例的
获取到结论后,问题就来了,那如果我想创建出来非单例的 bean 对象,该如何实现呢?
4.1.3.2 配置 bean 为非单例
在 Spring 配置文件中,配置 scope 属性来实现 bean 的非单例创建
在 Spring 的配置文件中,修改<bean>
的 scope 属性
将 scope 设置为singleton
运行 AppForScope,打印看结果
将 scope 设置为prototype
运行 AppForScope,打印看结果
结论,使用 bean 的scope
属性可以控制 bean 的创建是否为单例:
singleton
默认为单例
prototype
为非单例
4.1.3.3 scope 使用后续思考
介绍完scope
属性以后,我们来思考几个问题:
为什么 bean 默认为单例?
bean 为单例的意思是在 Spring 的 IOC 容器中只会有该类的一个对象
bean 对象只有一个就避免了对象的频繁创建与销毁,达到了 bean 对象的复用,性能高
bean 在容器中是单例的,会不会产生线程安全问题?
如果对象是有状态对象,即该对象有成员变量可以用来存储数据的,
因为所有请求线程共用一个 bean 对象,所以会存在线程安全问题。
如果对象是无状态对象,即该对象没有成员变量没有进行数据存储的,
因方法中的局部变量在方法调用完成后会被销毁,所以不会存在线程安全问题。
哪些 bean 对象适合交给容器进行管理?
表现层对象
业务层对象
数据层对象
工具对象
哪些 bean 对象不适合交给容器进行管理?
封装实例的域对象,因为会引发线程安全问题,所以不适合。
关于 bean 的基础配置中,需要大家掌握以下属性:
对象已经能交给 Spring 的 IOC 容器来创建了,但是容器是如何来创建对象的呢?
就需要研究下bean的实例化过程
,在这块内容中主要解决两部分内容,分别是
bean 是如何创建的
实例化 bean 的三种方式,构造方法
,静态工厂
和实例工厂
在讲解这三种创建方式之前,我们需要先确认一件事:
bean 本质上就是对象,对象在 new 的时候会使用构造方法完成,那创建 bean 也是使用构造方法完成的。
基于这个知识点出发,我们来验证 spring 中 bean 的三种创建方式,
4.2.1 环境准备
为了方便大家阅读代码,重新准备个开发环境,
创建一个 Maven 项目
pom.xml 添加依赖
resources 下添加 spring 的配置文件 applicationContext.xml
这些步骤和前面的都一致,大家可以快速的拷贝即可,最终项目的结构如下:
4.2.2 构造方法实例化
在上述的环境下,我们来研究下 Spring 中的第一种 bean 的创建方式构造方法实例化
:
步骤 1:准备需要被创建的类
准备一个 BookDao 和 BookDaoImpl 类
步骤 2:将类配置到 Spring 容器
步骤 3:编写运行程序
步骤 4:类中提供构造函数测试
在 BookDaoImpl 类中添加一个无参构造函数,并打印一句话,方便观察结果。
运行程序,如果控制台有打印构造函数中的输出,说明 Spring 容器在创建对象的时候也走的是构造函数
步骤 5:将构造函数改成 private 测试
运行程序,能执行成功,说明内部走的依然是构造函数,能访问到类中的私有构造方法,显而易见 Spring 底层用的是反射
步骤 6:构造函数中添加一个参数测试
运行程序,
程序会报错,说明 Spring 底层使用的是类的无参构造方法。
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 类
(2)创建一个工厂类 OrderDaoFactory 并提供一个==静态方法==
(3)编写 AppForInstanceOrder 运行类,在类中通过工厂获取对象
(4)运行后,可以查看到结果
如果代码中对象是通过上面的这种方式来创建的,如何将其交给 Spring 来管理呢?
4.2.4.2 静态工厂实例化
这就要用到 Spring 中的静态工厂实例化的知识了,具体实现步骤为:
(1)在 spring 的配置文件 application.properties 中添加以下内容:
class:工厂类的类全名
factory-mehod:具体工厂类中创建对象的方法名
对应关系如下图:
(2)在 AppForInstanceOrder 运行类,使用从 IOC 容器中获取 bean 的方法进行运行测试
(3)运行后,可以查看到结果
看到这,可能有人会问了,你这种方式在工厂类中不也是直接 new 对象的,和我自己直接 new 没什么太大的区别,而且静态工厂的方式反而更复杂,这种方式的意义是什么?
主要的原因是:
在工厂的静态方法中,我们除了 new 对象还可以做其他的一些业务操作,这些操作必不可少,如:
之前 new 对象的方式就无法添加其他的业务内容,重新运行,查看结果:
介绍完静态工厂实例化后,这种方式一般是用来兼容早期的一些老系统,所以==了解为主==。
4.2.5 实例工厂与 FactoryBean
接下来继续来研究 Spring 的第三种 bean 的创建方式实例工厂实例化
:
4.2.5.1 环境准备
(1)准备一个 UserDao 和 UserDaoImpl 类
(2)创建一个工厂类 OrderDaoFactory 并提供一个普通方法,注意此处和静态工厂的工厂类不一样的地方是方法不是静态方法
(3)编写 AppForInstanceUser 运行类,在类中通过工厂获取对象
(4)运行后,可以查看到结果
对于上面这种实例工厂的方式如何交给 Spring 管理呢?
4.2.5.2 实例工厂实例化
具体实现步骤为:
(1)在 spring 的配置文件中添加以下内容:
实例化工厂运行的顺序是:
创建实例化工厂对象,对应的是第一行配置
调用对象中的方法来创建 bean,对应的是第二行配置
factory-bean:工厂的实例对象
factory-method:工厂对象中的具体创建对象的方法名,对应关系如下:
factory-mehod:具体工厂类中创建对象的方法名
(2)在 AppForInstanceUser 运行类,使用从 IOC 容器中获取 bean 的方法进行运行测试
(3)运行后,可以查看到结果
实例工厂实例化的方式就已经介绍完了,配置的过程还是比较复杂,所以 Spring 为了简化这种配置方式就提供了一种叫FactoryBean
的方式来简化开发。
4.2.5.3 FactoryBean 的使用
具体的使用步骤为:
(1)创建一个 UserDaoFactoryBean 的类,实现 FactoryBean 接口,重写接口的方法
(2)在 Spring 的配置文件中进行配置
(3)AppForInstanceUser 运行类不用做任何修改,直接运行
这种方式在 Spring 去整合其他框架的时候会被用到,所以这种方式需要大家理解掌握。
查看源码会发现,FactoryBean 接口其实会有三个方法,分别是:
方法一:getObject(),被重写后,在方法中进行对象的创建并返回
方法二:getObjectType(),被重写后,主要返回的是被创建类的 Class 对象
方法三:没有被重写,因为它已经给了默认值,从方法名中可以看出其作用是设置对象是否为单例,默认 true,从意思上来看,我们猜想默认应该是单例,如何来验证呢?
思路很简单,就是从容器中获取该对象的多个值,打印到控制台,查看是否为同一个对象。
打印结果,如下:
通过验证,会发现默认是单例,那如果想改成单例具体如何实现?
只需要将 isSingleton()方法进行重写,修改返回为 false,即可
重新运行 AppForInstanceUser,查看结果
从结果中可以看出现在已经是非单例了,但是一般情况下我们都会采用单例,也就是采用默认即可。所以 isSingleton()方法一般不需要进行重写。
4.2.6 bean 实例化小结
通过这一节的学习,需要掌握:
(1)bean 是如何创建的呢?
(2)Spring 的 IOC 实例化对象的三种方式分别是:
构造方法(常用)
静态工厂(了解)
实例工厂(了解)
FactoryBean(实用)
这些方式中,重点掌握构造方法
和FactoryBean
即可。
需要注意的一点是,构造方法在类中默认会提供,但是如果重写了构造方法,默认的就会消失,在使用的过程中需要注意,如果需要重写构造方法,最好把默认的构造方法也重写下。
关于 bean 的相关知识还有最后一个是bean的生命周期
,对于生命周期,我们主要围绕着bean生命周期控制
来讲解:
首先理解下什么是生命周期?
从创建到消亡的完整过程,例如人从出生到死亡的整个过程就是一个生命周期。
bean 生命周期是什么?
bean 对象从创建到销毁的整体过程。
bean 生命周期控制是什么?
在 bean 创建后到销毁前做一些事情。
现在我们面临的问题是如何在 bean 的创建之后和销毁之前把我们需要添加的内容添加进去。
4.3.1 环境准备
还是老规矩,为了方便大家后期代码的阅读,我们重新搭建下环境:
创建一个 Maven 项目
pom.xml 添加依赖
resources 下添加 spring 的配置文件 applicationContext.xml
这些步骤和前面的都一致,大家可以快速的拷贝即可,最终项目的结构如下:
(1)项目中添加 BookDao、BookDaoImpl、BookService 和 BookServiceImpl 类
(2)resources 下提供 spring 的配置文件
(3)编写 AppForLifeCycle 运行类,加载 Spring 的 IOC 容器,并从中获取对应的 bean 对象
4.3.2 生命周期设置
接下来,在上面这个环境中来为 BookDao 添加生命周期的控制方法,具体的控制有两个阶段:
bean 创建之后,想要添加内容,比如用来初始化需要用到资源
bean 销毁之前,想要添加内容,比如用来释放用到的资源
步骤 1:添加初始化和销毁方法
针对这两个阶段,我们在 BooDaoImpl 类中分别添加两个方法,==方法名任意==
步骤 2:配置生命周期
在配置文件添加配置,如下:
步骤 3:运行程序
运行 AppForLifeCycle 打印结果为:
从结果中可以看出,init 方法执行了,但是 destroy 方法却未执行,这是为什么呢?
Spring 的 IOC 容器是运行在 JVM 中
运行 main 方法后,JVM 启动,Spring 加载配置文件生成 IOC 容器,从容器获取 bean 对象,然后调方法执行
main 方法执行完后,JVM 退出,这个时候 IOC 容器中的 bean 还没有来得及销毁就已经结束了
所以没有调用对应的 destroy 方法
知道了出现问题的原因,具体该如何解决呢?
4.3.3 close 关闭容器
ApplicationContext 中没有 close 方法
需要将 ApplicationContext 更换成 ClassPathXmlApplicationContext
调用 ctx 的 close()方法
运行程序,就能执行 destroy 方法的内容
4.3.4 注册钩子关闭容器
在容器未关闭之前,提前设置好回调函数,让 JVM 在退出之前回调此函数来关闭容器
调用 ctx 的 registerShutdownHook()方法
注意: registerShutdownHook 在 ApplicationContext 中也没有
运行后,查询打印结果
两种方式介绍完后,close 和 registerShutdownHook 选哪个?
相同点:这两种都能用来关闭容器
不同点:close()是在调用的时候关闭,registerShutdownHook()是在 JVM 退出前调用关闭。
分析上面的实现过程,会发现添加初始化和销毁方法,即需要编码也需要配置,实现起来步骤比较多也比较乱。
Spring 提供了两个接口来完成生命周期的控制,好处是可以不用再进行配置init-method
和destroy-method
接下来在 BookServiceImpl 完成这两个接口的使用:
修改 BookServiceImpl 类,添加两个接口InitializingBean
, DisposableBean
并实现接口中的两个方法afterPropertiesSet
和destroy
重新运行 AppForLifeCycle 类,
那第二种方式的实现,我们也介绍完了。
小细节
对于 InitializingBean 接口中的 afterPropertiesSet 方法,翻译过来为属性设置之后
。
对于 BookServiceImpl 来说,bookDao 是它的一个属性
setBookDao 方法是 Spring 的 IOC 容器为其注入属性的方法
思考:afterPropertiesSet 和 setBookDao 谁先执行?
从方法名分析,猜想应该是 setBookDao 方法先执行
验证思路,在 setBookDao 方法中添加一句话
重新运行 AppForLifeCycle,打印结果如下:
验证的结果和我们猜想的结果是一致的,所以初始化方法会在类中属性设置之后执行。
4.3.5 bean 生命周期小结
(1)关于 Spring 中对 bean 生命周期控制提供了两种方式:
在配置文件中的 bean 标签中添加init-method
和destroy-method
属性
类实现InitializingBean
与DisposableBean
接口,这种方式了解下即可。
(2)对于 bean 的生命周期控制在 bean 的整个生命周期中所处的位置如下:
初始化容器
1.创建对象(内存分配)
2.执行构造方法
3.执行属性注入(set 操作)
==4.执行 bean 初始化方法==
使用 bean
1.执行业务操作
关闭/销毁容器
==1.执行 bean 销毁方法==
(3)关闭容器的两种方式:
ConfigurableApplicationContext 是 ApplicationContext 的子类
close()方法
registerShutdownHook()方法
前面我们已经完成了 bean 相关操作的讲解,接下来就进入第二个大的模块DI依赖注入
,首先来介绍下 Spring 中有哪些注入方式?
我们先来思考
向一个类中传递数据的方式有几种?
普通方法(set 方法)
构造方法
依赖注入描述了在容器中建立 bean 与 bean 之间的依赖关系的过程,如果 bean 运行需要的是数字或字符串呢?
引用类型
简单类型(基本数据类型与 String)
Spring 就是基于上面这些知识点,为我们提供了两种注入方式,分别是:
setter 注入
简单类型
==引用类型==
构造器注入
简单类型
引用类型
依赖注入的方式已经介绍完,接下来挨个学习下:
对于 setter 方式注入引用类型的方式之前已经学习过,快速回顾下:
在 bean 中定义引用类型属性,并提供可访问的==set==方法
配置中使用==property==标签==ref==属性注入引用类型对象
5.1.1 环境准备
为了更好的学习下面内容,我们依旧准备一个新环境:
创建一个 Maven 项目
pom.xml 添加依赖
resources 下添加 spring 的配置文件
这些步骤和前面的都一致,大家可以快速的拷贝即可,最终项目的结构如下:
(1)项目中添加 BookDao、BookDaoImpl、UserDao、UserDaoImpl、BookService 和 BookServiceImpl 类
(2)resources 下提供 spring 的配置文件
(3)编写 AppForDISet 运行类,加载 Spring 的 IOC 容器,并从中获取对应的 bean 对象
接下来,在上面这个环境中来完成 setter 注入的学习:
5.1.2 注入引用数据类型
需求:在 bookServiceImpl 对象中注入 userDao
1.在 BookServiceImpl 中声明 userDao 属性
2.为 userDao 属性提供 setter 方法
3.在配置文件中使用 property 标签注入
步骤 1:声明属性并提供 setter 方法
在 BookServiceImpl 中声明 userDao 属性,并提供 setter 方法
步骤 2:配置文件中进行注入配置
在 applicationContext.xml 配置文件中使用 property 标签注入
步骤 3:运行程序
运行 AppForDISet 类,查看结果,说明 userDao 已经成功注入。
5.1.3 注入简单数据类型
需求:给 BookDaoImpl 注入一些简单数据类型的数据
参考引用数据类型的注入,我们可以推出具体的步骤为:
1.在 BookDaoImpl 类中声明对应的简单数据类型的属性
2.为这些属性提供对应的 setter 方法
3.在 applicationContext.xml 中配置
思考:
引用类型使用的是<property name="" ref=""/>
,简单数据类型还是使用 ref 么?
ref 是指向 Spring 的 IOC 容器中的另一个 bean 对象的,对于简单数据类型,没有对应的 bean 对象,该如何配置?
步骤 1:声明属性并提供 setter 方法
在 BookDaoImpl 类中声明对应的简单数据类型的属性,并提供对应的 setter 方法
步骤 2:配置文件中进行注入配置
在 applicationContext.xml 配置文件中使用 property 标签注入
说明:
value:后面跟的是简单数据类型,对于参数类型,Spring 在注入的时候会自动转换,但是不能写成
这样的话,spring 在将abc
转换成 int 类型的时候就会报错。
步骤 3:运行程序
运行 AppForDISet 类,查看结果,说明 userDao 已经成功注入。
注意: 两个 property 注入标签的顺序可以任意。
对于 setter 注入方式的基本使用就已经介绍完了,
对于引用数据类型使用的是<property name="" ref=""/>
对于简单数据类型使用的是<property name="" value=""/>
5.2.1 环境准备
构造器注入也就是构造方法注入,学习之前,还是先准备下环境:
创建一个 Maven 项目
pom.xml 添加依赖
resources 下添加 spring 的配置文件
这些步骤和前面的都一致,大家可以快速的拷贝即可,最终项目的结构如下:
(1)项目中添加 BookDao、BookDaoImpl、UserDao、UserDaoImpl、BookService 和 BookServiceImpl 类
(2)resources 下提供 spring 的配置文件
(3)编写 AppForDIConstructor 运行类,加载 Spring 的 IOC 容器,并从中获取对应的 bean 对象
5.2.2 构造器注入引用数据类型
接下来,在上面这个环境中来完成构造器注入的学习:
需求:将 BookServiceImpl 类中的 bookDao 修改成使用构造器的方式注入。
1.将 bookDao 的 setter 方法删除掉
2.添加带有 bookDao 参数的构造方法
3.在 applicationContext.xml 中配置
步骤 1:删除 setter 方法并提供构造方法
在 BookServiceImpl 类中将 bookDao 的 setter 方法删除掉,并添加带有 bookDao 参数的构造方法
步骤 2:配置文件中进行配置构造方式注入
在 applicationContext.xml 中配置
说明:
标签<constructor-arg>
中
name 属性对应的值为构造函数中方法形参的参数名,必须要保持一致。
ref 属性指向的是 spring 的 IOC 容器中其他 bean 对象。
步骤 3:运行程序
运行 AppForDIConstructor 类,查看结果,说明 bookDao 已经成功注入。
5.2.3 构造器注入多个引用数据类型
需求:在 BookServiceImpl 使用构造函数注入多个引用数据类型,比如 userDao
1.声明 userDao 属性
2.生成一个带有 bookDao 和 userDao 参数的构造函数
3.在 applicationContext.xml 中配置注入
步骤 1:提供多个属性的构造函数
在 BookServiceImpl 声明 userDao 并提供多个参数的构造函数
步骤 2:配置文件中配置多参数注入
在 applicationContext.xml 中配置注入
说明: 这两个<contructor-arg>
的配置顺序可以任意
步骤 3:运行程序
运行 AppForDIConstructor 类,查看结果,说明 userDao 已经成功注入。
5.2.4 构造器注入多个简单数据类型
需求:在 BookDaoImpl 中,使用构造函数注入 databaseName 和 connectionNum 两个参数。
参考引用数据类型的注入,我们可以推出具体的步骤为:
1.提供一个包含这两个参数的构造方法
2.在 applicationContext.xml 中进行注入配置
步骤 1:添加多个简单属性并提供构造方法
修改 BookDaoImpl 类,添加构造方法
步骤 2:配置完成多个属性构造器注入
在 applicationContext.xml 中进行注入配置
说明: 这两个<contructor-arg>
的配置顺序可以任意
步骤 3:运行程序
运行 AppForDIConstructor 类,查看结果
上面已经完成了构造函数注入的基本使用,但是会存在一些问题:
当构造函数中方法的参数名发生变化后,配置文件中的 name 属性也需要跟着变
这两块存在紧耦合,具体该如何解决?
在解决这个问题之前,需要提前说明的是,这个参数名发生变化的情况并不多,所以上面的还是比较主流的配置方式,下面介绍的,大家都以了解为主。
方式一:删除 name 属性,添加 type 属性,按照类型注入
这种方式可以解决构造函数形参名发生变化带来的耦合问题
但是如果构造方法参数中有类型相同的参数,这种方式就不太好实现了
方式二:删除 type 属性,添加 index 属性,按照索引下标注入,下标从 0 开始
这种方式可以解决参数类型重复问题
但是如果构造方法参数顺序发生变化后,这种方式又带来了耦合问题
介绍完两种参数的注入方式,具体我们该如何选择呢?
强制依赖使用构造器进行,使用 setter 注入有概率不进行注入导致 null 对象出现
强制依赖指对象在创建的过程中必须要注入指定的参数
可选依赖使用 setter 注入进行,灵活性强
可选依赖指对象在创建过程中注入的参数可有可无
Spring 框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨
如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用 setter 注入完成可选依赖的注入
实际开发过程中还要根据实际情况分析,如果受控对象没有提供 setter 方法就必须使用构造器注入
==自己开发的模块推荐使用 setter 注入==
这节中主要讲解的是 Spring 的依赖注入的实现方式:
setter 注入
简单数据类型
引用数据类型
构造器注入
简单数据类型
引用数据类型
依赖注入的方式选择上
建议使用 setter 注入
第三方技术根据情况选择
前面花了大量的时间把 Spring 的注入去学习了下,总结起来就一个字==麻烦==。
问:麻烦在哪?
答:配置文件的编写配置上。
问:有更简单方式么?
答:有,自动配置
什么是自动配置以及如何实现自动配置,就是接下来要学习的内容:
5.3.1 什么是依赖自动装配?
IoC 容器根据 bean 所依赖的资源在容器中自动查找并注入到 bean 中的过程称为自动装配
5.3.2 自动装配方式有哪些?
==按类型(常用)==
按名称
按构造方法
不启用自动装配
5.3.3 准备下案例环境
创建一个 Maven 项目
pom.xml 添加依赖
resources 下添加 spring 的配置文件
这些步骤和前面的都一致,大家可以快速的拷贝即可,最终项目的结构如下:
(1)项目中添加 BookDao、BookDaoImpl、BookService 和 BookServiceImpl 类
(2)resources 下提供 spring 的配置文件
(3)编写 AppForAutoware 运行类,加载 Spring 的 IOC 容器,并从中获取对应的 bean 对象
5.3.4 完成自动装配的配置
接下来,在上面这个环境中来完成自动装配
的学习:
自动装配只需要修改 applicationContext.xml 配置文件即可:
(1)将<property>
标签删除
(2)在<bean>
标签中添加 autowire 属性
首先来实现按照类型注入的配置
==注意事项:==
需要注入属性的类中对应属性的 setter 方法不能省略
被注入的对象必须要被 Spring 的 IOC 容器管理
按照类型在 Spring 的 IOC 容器中如果找到多个对象,会报NoUniqueBeanDefinitionException
一个类型在 IOC 中有多个对象,还想要注入成功,这个时候就需要按照名称注入,配置方式为:
==注意事项:==
按照名称注入中的名称指的是什么?
bookDao 是 private 修饰的,外部类无法直接方法
外部类只能通过属性的 set 方法进行访问
对外部类来说,setBookDao 方法名,去掉 set 后首字母小写是其属性名
为什么是去掉 set 首字母小写?
这个规则是 set 方法生成的默认规则,set 方法的生成是把属性名首字母大写前面加 set 形成的方法名
所以按照名称注入,其实是和对应的 set 方法有关,但是如果按照标准起名称,属性名和 set 对应的名是一致的
如果按照名称去找对应的 bean 对象,找不到则注入 Null
当某一个类型在 IOC 容器中有多个对象,按照名称注入只找其指定名称对应的 bean 对象,不会报错
两种方式介绍完后,以后用的更多的是==按照类型==注入。
最后对于依赖注入,需要注意一些其他的配置特征:
自动装配用于引用类型依赖注入,不能对简单类型进行操作
使用按类型装配时(byType)必须保障容器中相同类型的 bean 唯一,推荐使用
使用按名称装配时(byName)必须保障容器中具有指定名称的 bean,因变量名与配置耦合,不推荐使用
自动装配优先级低于 setter 注入与构造器注入,同时出现时自动装配配置失效
前面我们已经能完成引入数据类型和简单数据类型的注入,但是还有一种数据类型==集合==,集合中既可以装简单数据类型也可以装引用数据类型,对于集合,在 Spring 中该如何注入呢?
先来回顾下,常见的集合类型有哪些?
数组
List
Set
Map
Properties
针对不同的集合类型,该如何实现注入呢?
5.4.1 环境准备
创建一个 Maven 项目
pom.xml 添加依赖
resources 下添加 spring 的配置文件 applicationContext.xml
这些步骤和前面的都一致,大家可以快速的拷贝即可,最终项目的结构如下:
(1)项目中添加添加 BookDao、BookDaoImpl 类
(2)resources 下提供 spring 的配置文件,applicationContext.xml
(3)编写 AppForDICollection 运行类,加载 Spring 的 IOC 容器,并从中获取对应的 bean 对象
接下来,在上面这个环境中来完成集合注入
的学习:
下面的所以配置方式,都是在 bookDao 的 bean 标签中使用<property>
进行注入
5.4.2 注入数组类型数据
5.4.3 注入 List 类型数据
5.4.4 注入 Set 类型数据
5.4.5 注入 Map 类型数据
5.4.6 注入 Properties 类型数据
配置完成后,运行下看结果:
说明:
property 标签表示 setter 方式注入,构造方式注入 constructor-arg 标签内部也可以写<array>
、<list>
、<set>
、<map>
、<props>
标签
List 的底层也是通过数组实现的,所以<list>
和<array>
标签是可以混用
集合中要添加引用类型,只需要把<value>
标签改成<ref>
标签,这种方式用的比较少