SpringBoot:详解Bean生命周期和作用域

在这里插入图片描述

🏡浩泽学编程:个人主页

 🔥 推荐专栏:《深入浅出SpringBoot》《java项目分享》
              《RabbitMQ》《Spring》《SpringMVC》

🛸学无止境,不骄不躁,知行合一

文章目录

  • 前言
  • 一、生命周期
  • 二、作用域
  • 总结


前言

前面我们讲诉了将Bean正确地装配到IoC容器,却未讲诉IoC如何装配和销毁Bean。本篇文章主要讲诉一下Bean的生命周期和作用域。


一、生命周期

  • Bean 的生命周期的过程, 它大致分为Bean定义、Bean 的初始化、 Bean 的生存期和 Bean 的销毁4个部分。 其中 Bean 定义过程大致如下:

    • Spring 通过我们的配置,如@ComponentScan 定义的扫描路径去找到带有@Component 的类,
      这个过程就是一个资源定位的过程。
    • 一旦找到了资源,那么它就开始解析,并且将定义的信息保存起来。注意,此时还没有初始
      化Bean,也就没有Bean 的实例,它有的仅仅是Bean 的定义
    • 然后就会把Bean 定义发布到 Spring IoC 容器中。 此时, IoC 容器也只有Bean 的定义,还是
      没有Bean 的实例生成
  • 完成了这3 步只是一个资源定位并将Bean 的定义发布到IoC容器的过程,还没有Bean实例的生成,更没有完成依赖注入。在默认的情况下, Spring会继续去完成Bean 的实例化和依赖注入,这样从IoC 容器中就可以得到一个依赖注入完成的Bean。 但是,有些Bean会受到变化因素的影响,这时我们倒希望是取出 Bean 的时候完成初始化和依赖注入换句话说就是让那些 Bean 只是将定义发布到IoC 容器而不做实例化和依赖注入, 当我们取出来的时候才做初始化和依赖注入等操作。

Spring Bean的初始化过程:
在这里插入图片描述
ComponentScan 中还有一个配置项 lazyI nit,只可以配置 Boolean 值,且默认值为 false,也就是默认不进行延迟初始化,因此在默认的情况下Spring会对Bean进行实例化和依赖注入对应的属性值

引入例子:人类(Person)有时候利用一些动物(Animal)去完成一些事情,比方说狗(Dog)是用来看门的,猫(Cat)是用来抓老鼠的.。

代码如下:

//定义人类接口
public interface Person {void service();void setAnimal(Animal animal);
}
//定义动物接口
public interface Animal {void user();
}
//定义狗
@Component
public class Dog implements Animal {@Overridepublic void user() {System.out.println("狗【" + Dog.class.getSimpleName() + "】是用来看门的");}
}
//定义年轻人
@Component
public class YoungPerson implements Person {@Autowiredprivate Animal animal = null;@Overridepublic void service() {this.animal.user();}@Overridepublic void setAnimal(Animal animal) {this.animal = animal;}
}
//定义猫
@Component
public class Cat implements Animal{@Overridepublic void user() {System.out.println("猫【" + Cat.class.getSimpleName() + "】是抓老鼠的");}
}//定义配置类
@Configuration
@ComponentScan("com.dragon.restart")//所有的包和类都在restart下
public class AppConfig {
}

此时没有配置lazyInit的情况进行断点测试如下:
在这里插入图片描述
可以看到在断点处,我们并没有获取Bean 的实例,而日志就已经打出了,可见它是在SpringIoC容器初
始化时就执行了实例化和依赖注入。为了改变这个情况,我们在配置类AppConfig的@ComponentScan
中加入lazylnit 配置,如下面的代码:

@Configuration
@ComponentScan(value = "com.dragon.restart",lazyInit = true)
public class AppConfig {
}

在这里插入图片描述
在这里插入图片描述

就可以发现在断点处“延迟依赖注入”这行并不会出现在日志中,只有运行过断点处才会出现这行日志,这是因为我们把它修改为了延迟初始化, Spring并不会在发布Bean定义后马上为我们完成实例化和依赖注入。

如果仅仅是实例化和依赖注入还是比较简单的,还不能完成进行自定义的要求。 为了完成依赖注入的功能, Spring 在完成依赖注入之后,还提供了一系列的接口和配置来完成Bean初始化的过程,让我们学习这个过程。 Spring在完成依赖注入后,还会进行如下图所示流程来完成它的生命周期:
在这里插入图片描述

图中描述的是整个IoC容器初始化Bean 的流程,作为开发者,需要注意这些流程。除此之外,还需要注意以下两点:

  • 这些接口和方法是针对什么而言的。 对于上图, 在没有注释的情况下的流程节点都是针对单个Bean 而言的,但是BeanPostProcessor 是针对所有 Bean 而言的,这是我们需要注意的地方。
  • 即使你定义了 ApplicationContextAware 接口,但是有时候并不会调用,这要根据你的 IoC 容器来决定。 我们知道, Spring IoC 容器最低的要求是实现 BeanFactory 接口,而不是实现ApplicationContext 接口 。 对于那些没有实现 ApplicationContext 接口的容器,在生命周期对应的ApplicationContextAware 定义的方法也是不会被调用的,只有实现了 ApplicationContext 接口的容器,才会在生命周期调用 ApplicationContextAware 所定义的 setApplicationContext方法。

现在改造一下YoungPerson类:

@Component
public class YoungPerson implements Person, BeanNameAware , BeanFactoryAware, ApplicationContextAware, InitializingBean, DisposableBean {private Animal animal = null;@Overridepublic void service() {this.animal.user();}@Autowired@Qualifier("dog")@Overridepublic void setAnimal(Animal animal) {System.out.println("延迟依赖注入");this.animal = animal;}@Overridepublic void setBeanName(String name) {System.out.println ("【" + this.getClass().getSimpleName() + "】调用BeanNameAware的setBeanName");}@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {System.out.println ("【" + this.getClass().getSimpleName() + "】调用BeanFactoryAware的setBeanFactory");}@Overridepublic void destroy() throws Exception {System.out.println ("【" + this.getClass().getSimpleName() + "】调用DisposableBean方法");}@Overridepublic void afterPropertiesSet() throws Exception {System.out.println ("【" + this.getClass().getSimpleName() + "】调用InitializingBean方法的afterPropertiesSet方法");}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {System.out.println ("【" + this.getClass().getSimpleName() + "】调用ApplicationContextAware方法的setApplicationContext方法");}@PostConstructpublic void init () {System.out.println("【" + this.getClass().getSimpleName() + "】注解@PostConstruct定义的自定义初始化方法");}@PreDestroypublic void destroyl () {System.out.println("【" + this.getClass().getSimpleName() + "】注解@PreDestroy定义的自定义销毁方法");}
}

这样,这个 B巳an 就实现了生命周期中单个 Bean 可以实现的所有接口, 并且通过注解@PostConstruct 定义了初始化方法,通过注解@PreDestroy 定义了销毁方法。 为了测试 Bean 的后置处理器, 这里创建一个类BeanPostProcessorExampIe,如下:

/*** @Version: 1.0.0* @Author: Dragon_王* @ClassName: BeanPostProcessorExample* @Description: TODO描述* @Date: 2024/1/20 23:34*/
public class BeanPostProcessorExample implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {System.out.println("BeanPostProcessor调用"+"postProcessBeforeinitialization方法,参数【"+bean.getClass().getSimpleName()+"】【"+beanName+"】");return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {System.out.println("BeanPostProcessor调用"+"postProcessAfterinitialization方法,参数【"+bean.getClass().getSimpleName()+"】【"+beanName+"】");return bean;}
}

注意,这个Bean后置处理器将对所有的Bean有效,运行测试如下:
测试类:

AnnotationConfigApplicationContext ctx =new AnnotationConfigApplicationContext(AppConfig.class) ;ctx.close();
2024-01-20T23:43:23.135+08:00  INFO 748 --- [           main] c.d.restart.RestartApplicationTests      : Starting RestartApplicationTests using Java 19 with PID 748 (started by ThundeRobot in E:\IDEA_projects\restart)
2024-01-20T23:43:23.136+08:00  INFO 748 --- [           main] c.d.restart.RestartApplicationTests      : No active profile set, falling back to 1 default profile: "default"
BeanPostProcessor调用postProcessBeforeinitialization方法,参数【RestartApplication$$SpringCGLIB$$0】【restartApplication】
BeanPostProcessor调用postProcessAfterinitialization方法,参数【RestartApplication$$SpringCGLIB$$0】【restartApplication】
BeanPostProcessor调用postProcessBeforeinitialization方法,参数【AppConfig$$SpringCGLIB$$0】【appConfig】
BeanPostProcessor调用postProcessAfterinitialization方法,参数【AppConfig$$SpringCGLIB$$0】【appConfig】
BeanPostProcessor调用postProcessBeforeinitialization方法,参数【Cat】【cat】
BeanPostProcessor调用postProcessAfterinitialization方法,参数【Cat】【cat】
BeanPostProcessor调用postProcessBeforeinitialization方法,参数【Dog】【dog】
BeanPostProcessor调用postProcessAfterinitialization方法,参数【Dog】【dog】
延迟依赖注入
【YoungPerson】调用BeanNameAware的setBeanName
【YoungPerson】调用BeanFactoryAware的setBeanFactory
【YoungPerson】调用ApplicationContextAware方法的setApplicationContext方法
BeanPostProcessor调用postProcessBeforeinitialization方法,参数【YoungPerson】【youngPerson】
【YoungPerson】注解@PostConstruct定义的自定义初始化方法
【YoungPerson】调用InitializingBean方法的afterPropertiesSet方法
BeanPostProcessor调用postProcessAfterinitialization方法,参数【YoungPerson】【youngPerson】
BeanPostProcessor 调用 postProcessBeforeinitialization 方法,参数 【Cat】【cat】BeanPostProcessor 调用 postProcessAfterinitialization 方法, 参数 【Cat】【cat】2024-01-20T23:43:24.044+08:00  INFO 748 --- [main] c.d.restart.RestartApplicationTests      : Started RestartApplicationTests in 1.142 seconds (process running for 1.772)YoungPerson】注解@PreDestroy定义的自定义销毁方法
【YoungPerson】调用DisposableBean方法

从日志可以看出,对于Bean后置处理器(BeanPostProcessor)而言, 它对所有的 Bean 都起作用,而其他的接口则是对于单个Bean起作用。我们还可以注意到BussinessPerson执行的流程是上图所画出的流程。有时候Bean 的定义可能使用的是第三方的类,此时可以使用注解@Bean来配置自定义初始化和销毁方法,如下所示:

@Bean(InitMethod =”Init”, destroyMethod = ”destroy” )

二、作用域

在介绍IoC 容器最顶级接口 BeanFactory 的时候, 可以看到 isSingleton 和 isPrototype 两个方法。其中,isSingleton 方法如果返回 true,则 Bean 在 loC 容器中以单例存在,这也是 Spring IoC 容器的默认值;如果 isPrototype 方法返回 true,则当我们每次获取 Bean 的时候, IoC 容器都会创建一个新的 Bean,这显然存在很大的不同,这便是Spring Bean 的作用域的问题。在一般的容器中, Bean都会存在单例(Singleton)和原型(Prototype)两种作用域, Java EE 广泛地使用在互联网中,而在 Web容器中, 则存在页面(page)、请求(request)、会话 (session)和应用(application) 4 种作用域对于页面(page),是针对 JSP 当前页面的作用域,所以 Spring是无法支持的。为了满足各类的作用域,在Spring 的作用域中就存在如表所示的几种类型。

作用域类型使用范围作用域描述
singleton所有Spring 应用默认值, loC 容器只存在单例
prototype所有Spring 应用每当从IoC 容器中取出一个 Bean,则创建一个新的Bean
sessionSpring Web 应用HTTP 会话
applicationSpring Web 应用Web 工程生命周期
requestSpring Web 应用Web 工程单次请求 (request)
globalSessionSpring Web 应用在一个全局的HTTPSession 中, 一个 Bean 定义对应一个实例。 实践中基本不使用
  • 前四个最常用
  • 对于application作用域,完全可以使用单例来替代。

下面我们探讨单例 (Singleton)和原型(prototype)的区别

首先定义一个类

@Component 
//@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) 
public class ScopeBean { }

这是一个简单的类, 可以看到这里声明作用域的代码已经被注释掉了, 这样就是启用默认的作用域,实际就是单例。为了证明作用域的存在,我们进行一下测试:

AnnotationConfigApplicationContext ctx=new AnnotationConfigApplicationContext (AppConfig.class);ScopeBean scopeBeanl = ctx.getBean (ScopeBean.class);ScopeBean scopeBean2 = ctx.getBean (ScopeBean .class);System.out.println (scopeBeanl == scopeBean2) ;

在这里插入图片描述
从测试的结果来看,显然scopeBeanl 和 scopeBean2 这两个变量都指向了同一的实例,所以在IoC容器中, 只有一个ScopeBean 的实例。 然后取消代码中作用域代码的注释,进行同样的测试, 则可以看到scopeBeanl == scopeBean2 返回的将是 false,而不再是 true, 那是因为我们将Bean 的作用域修改为了 prototype,这样就能让IoC 容器在每次获取Bean 时,都新建一个Bean的实例返回给调用者。
在这里插入图片描述
这里的 ConfigurableBeanFactory 只能提供单例 ( SCOPE_ SINGLETON )和原型 ( SCOPE_PROTOTYPE)两种作用域供选择, 如果是在 SpringMVC环境中,还可以使用 WebApplicationContext去定义其他作用域, 如请求(SCOPE REQUEST)、 会话 (SCOPE_SESSION) 和应用 (SCOPE_APPLICATION)。 例如,下面的代码就是定义请求作用域:

@Component 
@Scope(WebApplicationContext.SCOPE_REQUEST) 
public class ScopeBean { }

总结

以上就是Bean生命周期和作用域的讲解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/419041.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

react中数据不可变

先看官网 一、不可变数据的概念 不可变数据意味着数据一旦创建,就不能被更改。在React中,每次对数据的修改都会返回一个新的数据副本,而不会改变原始数据。这种方式确保了数据的稳定性和一致性。 二、Props中的不可变数据 在React中&#xf…

机器视觉技术与应用实战(平均、高斯、水平prewitt、垂直prewitt、水平Sobel、垂直Sobel、拉普拉斯算子、锐化、中值滤波)

扯一点题外话,这一个月经历了太多,接连感染了甲流、乙流,人都快烧没了,乙流最为严重,烧了一个星期的38-39度,咳嗽咳到虚脱。还是需要保护好身体,感觉身体扛不住几次连续发烧!&#x…

gin中间件篇

1. 全局中间件 所有请求都经过此中间件 package mainimport ("fmt""time""github.com/gin-gonic/gin" )// 定义中间 func MiddleWare() gin.HandlerFunc {return func(c *gin.Context) {t : time.Now()fmt.Println("中间件开始执行了&quo…

设计模式-资源库模式

设计模式专栏 模式介绍模式特点应用场景资源库模式与关系型数据库的区别代码示例Java实现资源库模式Python实现资源库模式 资源库模式在spring中的应用 模式介绍 资源库模式是一种架构模式,介于领域层与数据映射层(数据访问层)之间。它的存在…

Linux中关于head命令详解

head的作用 head用于查看文件的开头部分的内容。 head的参数 -q隐藏文件名-v 显示文件名-c<数目>显示的字节数-n<数目>显示的行数 head的案例 # 查看yum.log前五行内容 head -5 yum.log

Datawhale 强化学习笔记(三)基于策略梯度(policy-based)的算法

文章目录 参考基于价值函数的缺点策略梯度算法REINFORCE 算法策略梯度推导进阶策略函数的设计离散动作的策略函数连续动作的策略函数 参考 第九章 策略梯度 之前介绍的 DQN 算法属于基于价值(value-based)的算法&#xff0c;基于策略梯度的算法直接对策略本身进行优化。 将策…

最优传输学习及问题总结

文章目录 参考内容lam0.1lam3lam10lam50lam100lam300画图线性规划matlabpython代码 参考内容 https://blog.csdn.net/qq_41129489/article/details/128830589 https://zhuanlan.zhihu.com/p/542379144 我主要想强调的是这个例子的解法存在的一些细节问题 lam0.1 lam 0.1P,…

EasyDarwin计划新增将各种流协议(RTSP、RTMP、HTTP、TCP、UDP)、文件转推RTMP到其他视频直播平台,支持转码H.264、文件直播推送

之前我们尝试做过EasyRTSPLive&#xff08;将RTSP流转推RTMP&#xff09;和EasyRTMPLive&#xff08;将各种RTSP/RTMP/HTTP/UDP流转推RTMP&#xff0c;这两个服务在市场上都得到了比较多的好评&#xff0c;其中&#xff1a; 1、EasyRTSPLive用的是EasyRTSPClient取流&#xff…

MySQL缓冲池(Buffer Pool)深入解析:原理、组成及其在数据操作中的核心作用

在关系型数据库管理系统&#xff08;RDBMS&#xff09;中&#xff0c;性能优化一直是数据库管理员和开发者关注的焦点。作为最流行的开源RDBMS之一&#xff0c;MySQL提供了多种优化手段&#xff0c;其中InnoDB存储引擎的缓冲池&#xff08;Buffer Pool&#xff09;是最为关键的…

ctfshow-反序列化(web271-web276)

目录 web271 web272-273 web274 web275 web276 为什么不用分析具体为什么能成功 ,后面会有几个专题 会对php框架进行更深入的了解 这里面会专门的研究 为什么能够实现RCE 前面作为初步的熟悉 首先知道一下他的框架 知道框架的风格 知道啥版本可以用什么来打 首先先不用太研…

Spring+SprinMVC+MyBatis注解方式简易模板

SpringSprinMVCMyBatis注解方式简易模板代码Demo GitHub访问 ssm-tpl-anno 一、数据准备 创建数据库test&#xff0c;执行下方SQL创建表ssm-tpl-cfg /*Navicat Premium Data TransferSource Server : 127.0.0.1Source Server Type : MySQLSource Server Version :…

2008年苏州大学837复试机试C语言

2008年苏州大学复试机试C 题目 编写程序充成以下功能: 一、从键盘上输入随机变量x的 10个取样点。X0&#xff0c;X1—X9 的值; 1、计算样本平均值 2、判定x是否为等差数列 3、用以下公式计算z的值(t0.63) 注。请对程序中必要地方进行注释 补充&#xff1a;个人觉得这个题目回…