Spring Bean作用域与生命周期全解析
Bean 作用域
概念
我们在之前的 Ioc & DI 说过 spring 是如何管理我们的对象的



发现输出的 bean 对象地址值是一样的,说明每次从 Spring 容器中取出来的对象都是同一个
默认情况下,Spring容器中的bean都是单例的,这种行为模式,我们就称之为Bean的作用域
Bean 的作用域是指 Bean 在 Spring 框架中的某种行为模式
比如单例作用域:表示 Bean 在整个Spring中只有一份,它是全局共享的,当其他人修改了这个值之后,接下来读到的就都是被修改的值


我们发现 dog1 和 dog2 为同一个对象,dog2 拿到了 dog1 设置的值
那么能不能让他 bean 对象设置为非单例的
请看接下来所说的
Bean 的作用域
在 Spring 中支持 6 种作用域,后 4 种在 Spring Mvc 下才能生效
singleton: 单例作用域
prototype: 原型作用域( 每次使用该 bean 时会创建新的实例)
request: 请求作用域
session: 会话作用域
Application: 全局作用域
websocket: HTTP WebSocket 作用域
我们来简单进行测试一下
DogBeanConfig

DogController


我们来观察一下
单例作用域

多例作用域


每次获取的对象都不一样,但对于 Autowired注入的 ,因为注入的对象在 Spring 容器是,就已经注入了,所以多次请求都不会变化用域
请求作用域


在一次请求中他俩是同一个对象,但是再次发起一个请求的话,就会重新创建对象
会话作用域
同一个Session 多次请求获得到的对象都是同一个
 当我们换了一个浏览器访问时,它会重新创建一个对象
当我们换了一个浏览器访问时,它会重新创建一个对象

Application 作用域
在一个应用中,多次访问都是同一个对象,不管是重新发起请求,还是换一个浏览器


Bean 的生命周期
生命周期指的是一个对象从诞生到销毁的整个生命过程,我们把这个过程就叫做一个对象的生命周期
Bean 生命周期分为 5 个部分
1.实例化(为 Bean 分配空间)
2.属性赋值(Bean 注入和装配)
3.初始化
3.1 执行各种同时
3.2 执行初始化方法
xml 定义 init - method
使用注解的方式 @PostConstruct
执行初始化后置方法(BeanPostProcessor)
4.使用 Bean
5.销毁 Bean
这就像是往杯子倒水一样
1.买个杯子
2.可以对杯子进行染色等的一些属性操作
3. 可以往杯子里放东西了
4.接下来就是使用杯子了
5.用完杯子我可以把它扔了或者卖了
我们来看看源码
创建Bean的代码入口在
AbstractAutowireCapableBeanFactory
我们找到 createBean


在这里创建 Bean,方法中包含了实例化,属性赋值,初始化过程
我们进去看看
doCreateBean

实例化 Bean

根据bean definition 完成 bean 属性赋值,然后执行 bean 初始化

这三个方法分别对应:
createBeanInstance 实例化
populateBean 属性赋值
initializeBean 初始化
Spring Boot 自动配置
Spring Boot 的自动配置就是当 Spring 容器启动后,一些配置类,bean 对象等就自动存入到 IoC 容器中,不需要我们手动去声明,从而简化了开发,省去了繁琐的配置操作
SpringBoot 自动配置,就是指 SpringBoot 是如何将依赖 jar 包中的配置类以及 Bean 加载到 Spring IoC 容器中的
Spring 加载 Bean
使用 Spring 管理第三方的 jar 包的配置
数据准备
创建项目 SpringAutoconfig,当前项目目录为 com.example.springautoconfig
模拟第三方代码文件在 com.example.autoconfig 目录下

第三方文件

获取 AutoConfig 这个 Bean

运行程序

它说没有 com.example.autoconfig.AutoConfig 这个类型的 Bean
原因分析
Spring 通过五大注解和@Bean 注解可以帮助我们把Bean加载到 SpringIoC 容器中,但是有一个前提就是 这些注解类需要和 SpringBoot 启动类在同一个目录下
当我们引入第三方的 Jar 包时,第三方的 Jar 代码目录肯定不在启动类的目录下,如何告诉 Spring 帮我们管理这些 Bean?
解决方案
我们需要指定 路径或者引入的文件,告诉Spring,让 Spring 进行扫描
常见的解决方案有两种:
1.@ComponentScan 组件扫描
2.@Import 导入
使用 @import 导入的类会被 Spring 加载到 IoC 容器中
@ComponentScan


Spring 和 SpringBoot 都没有采用这种方式,因为会比较繁琐
@Import
@Import 导入主要有两种方式:
1.导入类
2.导入 ImportSelector 接口实现类


很显然这跟上面那位都很繁琐,所以 SpringBoot 也没有采纳
导入 ImportSelector 接口实现类
ImportSelector 接口实现类

启动类:

运行程序:

问题:
他们都有一个明显的问题,就是使用者需要知道第三方依赖中有哪些 Bean 对象或 配置类,如果漏掉一些Bean的话,就完了
这对我们很不友好
依赖中有哪些 Bean,使用是需要配置哪些 Bean,第三方依赖最清楚,我们是否可以把这件事交给第三方依赖呢?
一般就是第三方依赖给我们提供一个注解,这个注解一般都以 @Enablexxx 开头的,注解中封装的就是 @Import 注解
第三方依赖提供注解

在启动类上使用第三方提供的注解


SpringBoot 源码分析
源码阅读
SpringBoot 如何帮我们做的,都要先从 SpringBoot 启动类开始

@SpringBootApplication 是 SpringBoot 实现自动配置的核心

它是一个组合注解,包含了
1.元注解
| 元注解名称 | 作用说明 | 
|---|---|
| @Target | 描述注解的使用范围 | 
| @Retention | 描述注解保留的时间范围 | 
| @Documented | 描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息 | 
| @Inherited | 使被它修饰的注解具有继承性 | 
这四个是 JDK 提供的标准元注解,用于对 “注解类型” 本身进行注解,从而定义自定义注解的行为规则(如作用范围、保留时机、文档生成、继承性等)。
2.@SpringBootConfiguration
| 内容要点 | 说明 | 
|---|---|
| @Configuration | 标注当前类为配置类,是对底层机制的封装重命名 | 
| @Indexed | 用于加速应用启动 | 
3.@EnableAutoConfiguration(开启自动配置)
Spring 自动配置的核心注解
4.@ComponetScan(包扫描)
可以用 basePackageClasses / basePackages 来定义要扫描的特定包
| basePackageClasses / basePackages | 用于定义要扫描的特定包;若未定义,将从声明该注解的类的包开始扫描,这也是 Spring Boot 项目注解类需在启动类目录下的原因 | 
| excludeFilters | 自定义过滤器,通常用于排除一些类、注解等 | 
@EnableAutoConfiguration

这个注解包含两个部分:
@Import(AutoConfigurationImportSelector.class)
使用 @Import 注解,导入实现 ImportSelector 接口的实现类


selectImport()方法底层调用 getAutoConfigurationEntry()方法,获取可自动配置的配置类信息集合

getAutoConfigurationEntry()方法通过调用 getCandidateConfigurations(annotationMetadata, attributes) 方法获取在配置文件中配置的所有自动配置类的集合

@AutoConfigurationPackage

这个注解主要是导入一个配置文件 AutoConfigurationPackages.Registrar.class

 Registrar 实现了 ImportBeanDefinitionRegistrar 类,可被 @Import 注解导入到 Spring 容器里。
(String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0]) 表示当前启动类所在的包名。
@AutoConfigurationPackage 的作用是将启动类所在包下的所有组件扫描并注册到 Spring 容器中。
