0


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)是用来抓老鼠的.。

代码如下:

//定义人类接口publicinterfacePerson{voidservice();voidsetAnimal(Animal animal);}//定义动物接口publicinterfaceAnimal{voiduser();}//定义狗@ComponentpublicclassDogimplementsAnimal{@Overridepublicvoiduser(){System.out.println("狗【"+Dog.class.getSimpleName()+"】是用来看门的");}}//定义年轻人@ComponentpublicclassYoungPersonimplementsPerson{@AutowiredprivateAnimal animal =null;@Overridepublicvoidservice(){this.animal.user();}@OverridepublicvoidsetAnimal(Animal animal){this.animal = animal;}}//定义猫@ComponentpublicclassCatimplementsAnimal{@Overridepublicvoiduser(){System.out.println("猫【"+Cat.class.getSimpleName()+"】是抓老鼠的");}}//定义配置类@Configuration@ComponentScan("com.dragon.restart")//所有的包和类都在restart下publicclassAppConfig{}

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

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

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

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

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

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

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

现在改造一下YoungPerson类:

@ComponentpublicclassYoungPersonimplementsPerson,BeanNameAware,BeanFactoryAware,ApplicationContextAware,InitializingBean,DisposableBean{privateAnimal animal =null;@Overridepublicvoidservice(){this.animal.user();}@Autowired@Qualifier("dog")@OverridepublicvoidsetAnimal(Animal animal){System.out.println("延迟依赖注入");this.animal = animal;}@OverridepublicvoidsetBeanName(String name){System.out.println ("【"+this.getClass().getSimpleName()+"】调用BeanNameAware的setBeanName");}@OverridepublicvoidsetBeanFactory(BeanFactory beanFactory)throwsBeansException{System.out.println ("【"+this.getClass().getSimpleName()+"】调用BeanFactoryAware的setBeanFactory");}@Overridepublicvoiddestroy()throwsException{System.out.println ("【"+this.getClass().getSimpleName()+"】调用DisposableBean方法");}@OverridepublicvoidafterPropertiesSet()throwsException{System.out.println ("【"+this.getClass().getSimpleName()+"】调用InitializingBean方法的afterPropertiesSet方法");}@OverridepublicvoidsetApplicationContext(ApplicationContext applicationContext)throwsBeansException{System.out.println ("【"+this.getClass().getSimpleName()+"】调用ApplicationContextAware方法的setApplicationContext方法");}@PostConstructpublicvoid init (){System.out.println("【"+this.getClass().getSimpleName()+"】注解@PostConstruct定义的自定义初始化方法");}@PreDestroypublicvoid 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
 */publicclassBeanPostProcessorExampleimplementsBeanPostProcessor{@OverridepublicObjectpostProcessBeforeInitialization(Object bean,String beanName)throwsBeansException{System.out.println("BeanPostProcessor调用"+"postProcessBeforeinitialization方法,参数【"+
                bean.getClass().getSimpleName()+"】【"+beanName+"】");return bean;}@OverridepublicObjectpostProcessAfterInitialization(Object bean,String beanName)throwsBeansException{System.out.println("BeanPostProcessor调用"+"postProcessAfterinitialization方法,参数【"+
                bean.getClass().getSimpleName()+"】【"+beanName+"】");return bean;}}

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

AnnotationConfigApplicationContext ctx =newAnnotationConfigApplicationContext(AppConfig.class);
        ctx.close();
2024-01-20T23:43:23.135+08:00INFO748---[           main]c.d.restart.RestartApplicationTests:StartingRestartApplicationTests using Java19withPID748(started by ThundeRobot in E:\IDEA_projects\restart)2024-01-20T23:43:23.136+08:00INFO748---[           main]c.d.restart.RestartApplicationTests:No active profile set, falling back to1default 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:00INFO748---[main]c.d.restart.RestartApplicationTests:StartedRestartApplicationTests in 1.142 seconds (process running for1.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,则创建一个新的BeansessionSpring Web 应用HTTP 会话applicationSpring Web 应用Web 工程生命周期requestSpring Web 应用Web 工程单次请求 (request)globalSessionSpring Web 应用在一个全局的HTTPSession 中, 一个 Bean 定义对应一个实例。 实践中基本不使用

  • 前四个最常用
  • 对于application作用域,完全可以使用单例来替代。

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

首先定义一个类

@Component//@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) publicclassScopeBean{}

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

AnnotationConfigApplicationContext ctx
                =newAnnotationConfigApplicationContext(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)publicclassScopeBean{}

总结

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

标签: spring boot 后端 java

本文转载自: https://blog.csdn.net/weixin_62951900/article/details/135706261
版权归原作者 浩泽学编程 所有, 如有侵权,请联系我们删除。

“SpringBoot:详解Bean生命周期和作用域”的评论:

还没有评论