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 容器。