1. Bean管理
Spring
当中提供的注解
@Component
以及它的三个衍
生注解(
@Controller
、
@Service
、
@Repository
)来声明
IOC
容器中的
bean
对象,同时我们也学
习了如何为应用程序注入运行时所需要依赖的
bean
对象,也就是依赖注入
DI
。
我们今天主要学习
IOC
容器中
Bean
的其他使用细节,主要学习以下三方面:
1.
如何从
IOC
容器中手动的获取到
bean
对象
2. bean
的作用域配置
3.
管理第三方的
bean
对象
1.1 获取Bean
默认情况下,
SpringBoot
项目在启动的时候会自动的创建
IOC
容器
(
也称为
Spring
容器
)
,并且在启动
的过程当中会自动的将
bean
对象都创建好,存放在
IOC
容器当中。应用程序在运行时需要依赖什么
bean
对象,就直接进行依赖注入就可以了。
而在
Spring
容器中提供了一些方法,可以主动从
IOC
容器中获取到
bean
对象,下面介绍
3
种常用方式,通过applicationcontext对象:
@Autowired
private
ApplicationContext applicationContext
;
//IOC
容器对象
1.
根据
name
获取
bean
Object
getBean
(
String
name
)
2.
根据类型获取
bean
<
T
>
T
getBean
(
Class
<
T
>
requiredType
)
3.
根据
name
获取
bean
(带类型转换)
<
T
>
T
getBean
(
String
name
,
Class
<
T
>
requiredType
)
1.2 Bean作用域
在前面我们提到的
IOC
容器当中,默认
bean
对象是单例模式
(
只有一个实例对象
)
。那么如何设置
bean
对象为非单例呢?需要设置
bean
的作用域。
在
Spring
中支持五种作用域,后三种在
web
环境才生效:
1).
测试一
控制器
//
默认
bean
的作用域为:
singleton (
单例
)
@Lazy
//
延迟加载(第一次使用
bean
对象时,才会创建
bean
对象并交给
ioc
容器管
理)
//
@Scope
(
"singleton"
) //单列是默认的,所以无需填写,若prototype...等可以填写
@RestController
@RequestMapping
(
"/depts"
)
public class
DeptController
{
@Autowired
private
DeptService deptService
;
public
DeptController
(){
System
.
out
.
println
(
"DeptController constructor ...."
);
}
//
省略其他代码
...
}
测试类
@SpringBootTest
class
SpringbootWebConfig2ApplicationTests
{
@Autowired
private
ApplicationContext applicationContext
;
//IOC
容器对象
//bean的作用域
@Test
public
void
testScope
(){
for
(
int
i
=
0
;
i
<
10
;
i
++
) {
DeptController deptController
=
applicationContext
.
getBean
(
DeptController
.
class
);
System
.
out
.
println
(
deptController
);
}
}
}
注意事项:
IOC
容器中的
bean
默认使用的作用域:
singleton (
单例
)
默认
singleton
的
bean
,在容器启动时被创建,可以使用
@Lazy
注解来延迟初始化
(
延迟到
第一次使用时
)
1.3 第三方Bean
之前我们所配置的
bean
,像
controller
、
service
,
dao
三层体系下编写的类,这些类都是我们在项
目当中自己定义的类
(
自定义类
)
。当我们要声明这些
bean
,也非常简单,我们只需要在类上加上
@Component
以及它的这三个衍生注解(
@Controller
、
@Service
、
@Repository
),就可以来声
明这个
bean对象了
在
pom.xml
文件中,引入
dom4j
:
dom4j
就是第三方组织提供的。
dom4j
中的
SAXReader
类就是第三方编写的。
当我们需要使用到
SAXReader
对象时,直接进行依赖注入是不是就可以了呢?
按照我们之前的做法,需要在
SAXReader
类上添加一个注解
@Component
(将当前类交给
IOC
容器
管理)
结论:第三方提供的类是只读的。无法在第三方类上添加
@Component
注解或衍生注解。
那么我们应该怎样使用并定义第三方的
bean
呢?
如果要管理的
bean
对象来自于第三方(不是自定义的),是无法用
@Component
及衍生注解声明
bean
的,就需要用到
@Bean
注解。
解决方案
1
:在启动类上添加
@Bean
标识的方法
xml
文件:
测试类:
说明:以上在启动类中声明第三方
Bean
的作法,不建议使用(项目中要保证启动类的纯粹性)
解决方案
2
:在配置类中定义
@Bean
标识的方法
如果需要定义第三方
Bean
时,通常会单独定义一个配置类
注意事项:
通过
@Bean
注解的
name
或
value
属性可以声明
bean
的名称,如果不指定,默认
bean
的名称
就是方法名。
如果第三方
bean
需要依赖其它
bean
对象,直接在
bean
定义方法中设置形参即可,容器会根
据类型自动装配。
关于
Bean
大家只需要保持一个原则:
如果是在项目当中我们自己定义的类,想将这些类交给
IOC
容器管理,我们直接使用
@Component
以及它的衍生注解来声明就可以。
如果这个类它不是我们自己定义的,而是引入的第三方依赖当中提供的类,而且我们还想将这个类
交给
IOC
容器管理。此时我们就需要在配置类中定义一个方法,在方法上加上一个
@Bean
注解,通
过这种方式来声明第三方的
bean
对象。
2.SpringBoot原理
Spring
是目前世界上最流行的
Java
框架,它可以帮助我们更加快速、更加容易的来构建
Java
项目。而
在
Spring
家族当中提供了很多优秀的框架,而所有的框架都是基于一个基础框架的
SpringFramework(
也就是
Spring
框架
)
。而前面我们也提到,如果我们直接基于
Spring
框架进行项
目的开发,会比较繁琐。
这个繁琐主要体现在两个地方:
1.
在
pom.xml中依赖配置比较繁琐,在项目开发时,需要自己去找到对应的依赖,还需要找到依赖
它所配套的依赖以及对应版本,否则就会出现版本冲突问题。
2.
在使用
Spring
框架进行项目开发时,需要在
Spring的配置文件中做大量的配置,这就造成Spring
框架入门难度较大,学习成本较高。
基于
Spring
存在的问题,官方在
Spring
框架
4.0
版本之后,又推出了一个全新的框架:
SpringBoot
。
通过
SpringBoot
来简化
Spring
框架的开发
(
是简化不是替代
)
。我们直接基于
SpringBoot
来
构建
Java
项目,会让我们的项目开发更加简单,更加快捷。
SpringBoot
框架之所以使用起来更简单更快捷,是因为
SpringBoot
框架底层提供了两个非常重要的
功能:一个是起步依赖,一个是自动配置。
通过
SpringBoot
所提供的起步依赖,就可以大大的简化
pom
文件当中依赖的配置,从而解决了
Spring
框架当中依赖配置繁琐的问题。
通过自动配置的功能就可以大大的简化框架在使用时
bean
的声明以及
bean
的配置。我们只需要引
入程序开发时所需要的起步依赖,项目开发时所用到常见的配置都已经有了,我们直接使用就可以
了。
2.1 起步依赖
假如我们没有使用
SpringBoot
,用的是
Spring
框架进行
web
程序的开发,此时我们就需要引入
web
程
序开发所需要的一些依赖。
spring-webmvc
依赖:这是
Spring
框架进行
web
程序开发所需要的依赖
servlet-api
依赖:
Servlet
基础依赖
jackson-databind
依赖:
JSON
处理工具包
如果要使用
AOP
,还需要引入
aop
依赖、
aspect
依赖
项目中所引入的这些依赖,还需要保证版本匹配,否则就可能会出现版本冲突问题。
如果我们使用了
SpringBoot
,就不需要像上面这么繁琐的引入依赖了。我们只需要引入一个依赖就可
以了,那就是
web
开发的起步依赖:
springboot-starter-web
。
为什么我们只需要引入一个
web
开发的起步依赖,
web
开发所需要的所有的依赖都有了呢?
因为
Maven
的依赖传递。
在
SpringBoot
给我们提供的这些起步依赖当中,已提供了当前程序开发所需要的所有的常
见依赖
(
官网地址:
https://docs.spring.io/spring-boot/docs/2.7.7/referen
ce/htmlsingle/#using.build-systems.starters
)
。
比如:
springboot-starter-web
,这是
web
开发的起步依赖,在
web
开发的起步依赖当
中,就集成了
web
开发中常见的依赖:
json
、
web
、
webmvc
、
tomcat
等。我们只需要引入
这一个起步依赖,其他的依赖都会自动的通过
Maven
的依赖传递进来。
结论:起步依赖的原理就是
Maven
的依赖传递。
2.2 自动配置
我们讲解了
SpringBoot
当中起步依赖的原理,就是
Maven
的依赖传递。接下来我们解析下自动配置的
原理,我们要分析自动配置的原理,首先要知道什么是自动配置。
2.2.1
概述
SpringBoot
的自动配置就是当
Spring
容器启动后,一些配置类、
bean
对象就自动存入到了
IOC
容器
中,不需要我们手动去声明,从而简化了开发,省去了繁琐的配置操作。
比如:我们要进行事务管理、要进行
AOP
程序的开发,此时就不需要我们再去手动的声明这些
bean
对象了,我们直接使用就可以从而大大的简化程序的开发,省去了繁琐的配置操作
通过源码的方式去查看自动配置是怎样执行的
大家会看到有两个
CommonConfig
,在第一个
CommonConfig
类中定义了一个
bean
对象,
bean
对象的
名字叫
reader
。
在第二个
CommonConfig
中它的
bean
名字叫
commonConfig
,为什么还会有这样一个
bean
对象呢?原
因是在
CommonConfig
配置类上添加了一个注解
@Configuration
,而
@Configuration
底层就是
@Component
所以配置类最终也是
SpringIOC
容器当中的一个
bean
对象
在
IOC
容器中除了我们自己定义的
bean
以外,还有很多配置类,这些配置类都是
SpringBoot
在启动的
时候加载进来的配置类。这些配置类加载进来之后,它也会生成很多的
bean
对象。
2.2.1 概述
我们知道了什么是自动配置之后,接下来我们就要来剖析自动配置的原理。解析自动配置的原理就是分
析在
SpringBoot
项目当中,我们引入对应的依赖之后,是如何将依赖
jar
包当中所提供的
bean
以及
配置类直接加载到当前项目的
SpringIOC
容器当中的。
我们就直接通过代码来分析自动配置原理
在
SpringBoot
项目
spring-boot-web-config2
工程中,通过坐标引入
itheima-utils
依赖
2
、在测试类中,添加测试方法
3
、执行测试方法
异常信息描述:没有
com.example.TokenParse
类型的
bean
说明:在
Spring
容器中没有找到
com.example.TokenParse
类型的
bean
对象
思考:引入进来的第三方依赖当中的
bean
以及配置类为什么没有生效?
因在我们之前讲解IOC的时候有提到过,在类上添加@Component注解来声明bean对象时,还需
要保证@Component注解能被Spring的组件扫描到。
SpringBoot项目中的@SpringBootApplication注解,具有包扫描的作用,但是它只会扫描启
动类所在的当前包以及子包。
当前包:com.itheima,第三方依赖中提供的包:com.example(扫描不到)
那么如何解决以上问题的呢?
方案
1
:
@ComponentScan
组件扫描
方案
2
:
@Import
导入(使用
@Import
导入的类会被
Spring
加载到
IOC
容器中)
方案一
@ComponentScan
组件扫描
重新执行测试方法,控制台日志输出:
大家可以想象一下,如果采用以上这种方式来完成自动配置,那我们进行项目开发时,当需要引入
大量的第三方的依赖,就需要在启动类上配置
N
多要扫描的包,这种方式会很繁琐。而且这种大面
积的扫描性能也比较低。
缺点:
1.
使用繁琐
2.
性能低
结论:
SpringBoot
中并没有采用以上这种方案。
方案二
@Import
导入
导入形式主要有以下几种:
1.
导入普通类
2.
导入配置类
3.
导入
ImportSelector
接口实现类
1).
使用
@Import
导入普通类:
2).
使用
@Import
导入配置类:
配置类
启动类
测试类
3).
使用
@Import
导入
ImportSelector
接口实现类:
ImportSelector
接口实现类
启动类
4).
使用第三方依赖提供的
@EnableXxxxx
注解
第三方依赖中提供的注解
在使用时只需在启动类上加上
@EnableXxxxx
注解即可
原理分析
2.2.2.1 源码跟踪
前面我们讲解了在项目当中引入第三方依赖之后,如何加载第三方依赖中定义好的
bean
对象以及配置
类,从而完成自动配置操作。那下面我们通过源码跟踪的形式来剖析下
SpringBoot
底层到底是如何完
成自动配置的。
源码跟踪技巧:
在跟踪框架源码的时候,一定要抓住关键点,找到核心流程。一定不要从头到尾一行代码去看,一
个方法的去研究,一定要找到关键流程,抓住关键点,先在宏观上对整个流程或者整个原理有一个
认识,有精力再去研究其中的细节。
要搞清楚
SpringBoot
的自动配置原理,要从
SpringBoot
启动类上使用的核心注解
@SpringBootApplication
开始分析:
我们先来看第一个注解:
@SpringBootConfiguration
@SpringBootConfiguration
注解上使用了
@Configuration
,表明
SpringBoot
启动类就是
一个配置类。
@Indexed
注解,是用来加速应用启动的(不用关心)。
接下来再先看
@ComponentScan
注解:
@ComponentScan
注解是用来进行组件扫描的,扫描启动类所在的包及其子包下所有被
@Component
及其衍生注解声明的类。
SpringBoot
启动类,之所以具备扫描包功能,就是因为包含了
@ComponentScan
注解。
最后我们来看看
@EnableAutoConfiguration
注解(自动配置核心注解):
AutoConfigurationImportSelector
类中重写了
ImportSelector
接口的
selectImports()
方
法:
selectImports()
方法底层调用
getAutoConfigurationEntry()
方法,获取可自动配置的
配置类信息集合
getAutoConfigurationEntry()
方法通过调用
getCandidateConfigurations(annotationMetadata, attributes)
方法获取在配置文
件中配置的所有自动配置类的集合
getCandidateConfigurations
方法的功能:
获取所有基于
META-
INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imp
orts
文件、
META-INF/spring.factories
文件中配置类的集合
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件和
META-INF/spring.factories
文件这两个文件在哪里呢?
通常在引入的起步依赖中,都有包含以上两个文件
第三方依赖中提供的
GsonAutoConfiguration
类:
在
GsonAutoConfiguration
类上,添加了注解
@AutoConfiguration
,通过查看源码,可以
明确:
GsonAutoConfiguration
类是一个配置。
动配置源码小结
自动配置原理源码入口就是@SpringBootApplication注解,在这个注解中封装了3个注解,分别
是:
@SpringBootConfiguration
声明当前类是一个配置类
@ComponentScan
进行组件扫描(SpringBoot中默认扫描的是启动类所在的当前包及其子包)
@EnableAutoConfiguration
封装了@Import注解(Import注解中指定了一个ImportSelector接口的实现类)
在实现类重写的selectImports()方法,读取当前项目下所有依赖jar包中META-
INF/spring.factories、META-
INF/spring/org.springframework.boot.autoconfigure.AutoConfigurat
ion.imports两个文件里面定义的配置类(配置类中定义了@Bean注解标识的方法)。
当
SpringBoot
程序启动时,就会加载配置文件当中所定义的配置类,并将这些配置类信息
(
类的全限定
名
)
封装到
String
类型的数组中,最终通过
@Import
注解将这些配置类全部加载到
Spring
的
IOC
容器
中,交给
IOC
容器管理。
在声明
bean
对象时,上面有加一个以
@Conditional
开头的注解,这种注解的
作用就是按照条件进行装配,只有满足条件之后,才会将
bean
注册到
Spring
的
IOC
容器中(下面
会详细来讲解)
2.2.2.2 @Conditional
我们在跟踪
SpringBoot
自动配置的源码的时候,在自动配置类声明
bean
的时候,除了在方法上加了一
个
@Bean
注解以外,还会经常用到一个注解,就是以
Conditional
开头的这一类的注解。以
Conditional
开头的这些注解都是条件装配的注解。下面我们就来介绍下条件装配注解。
@Conditional
注解:
作用:按照一定的条件进行判断,在满足给定条件后才会注册对应的
bean
对象到
Spring
的
IOC
容
器中。
位置:方法、类
@Conditional
本身是一个父注解,派生出大量的子注解:
@ConditionalOnClass
:判断环境中有对应字节码文件,才注册
bean
到
IOC
容器。
@ConditionalOnMissingBean
:判断环境中没有对应的
bean(
类型或名称
)
,才注册
bean
到
IOC
容器。
@ConditionalOnProperty
:判断配置文件中有对应属性和值,才注册
bean
到
IOC
容器。