小工具知识网

当前位置: 首页 > 行业知识 > SpringBoot:详解Bean生命周期和作用域

行业知识

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

2026-11-05 20:08:26 来源:互联网转载
���泽学编程:个人主页

  推荐专栏:《深入浅出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();}//定义狗@Componentpublic class Dog implements Animal {    @Override    public void user() {        System.out.println("狗【" + Dog.class.getSimpleName() + "】是用来看门的");    }}//定义年轻人@Componentpublic class YoungPerson implements Person {    @Autowired    private Animal animal = null;    @Override    public void service() {        this.animal.user();    }    @Override    public void setAnimal(Animal animal) {        this.animal = animal;    }}//定义猫@Componentpublic class Cat implements Animal{    @Override    public 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类:

          @Componentpublic class YoungPerson implements Person, BeanNameAware , BeanFactoryAware, ApplicationContextAware, InitializingBean, DisposableBean {    private Animal animal = null;    @Override    public void service() {        this.animal.user();    }    @Autowired    @Qualifier("dog")    @Override    public void setAnimal(Animal animal) {        System.out.println("延迟依赖注入");        this.animal = animal;    }    @Override    public void setBeanName(String name) {        System.out.println ("【" + this.getClass().getSimpleName() + "】调用BeanNameAware的setBeanName");    }    @Override    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {        System.out.println ("【" + this.getClass().getSimpleName() + "】调用BeanFactoryAware的setBeanFactory");    }    @Override    public void destroy() throws Exception {        System.out.println ("【" + this.getClass().getSimpleName() + "】调用DisposableBean方法");    }    @Override    public void afterPropertiesSet() throws Exception {        System.out.println ("【" + this.getClass().getSimpleName() + "】调用InitializingBean方法的afterPropertiesSet方法");    }    @Override    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {        System.out.println ("【" + this.getClass().getSimpleName() + "】调用ApplicationContextAware方法的setApplicationContext方法");    }    @PostConstruct    public void init () {        System.out.println("【" + this.getClass().getSimpleName() + "】注解@PostConstruct定义的自定义初始化方法");    }    @PreDestroy    public 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 {    @Override    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {        System.out.println("BeanPostProcessor调用"+                "postProcessBeforeinitialization方法,参数【"+                bean.getClass().getSimpleName()+"】【"+beanName+"】");        return bean;    }    @Override    public 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$
          @Bean(InitMethod =”Init”, destroyMethod = ”destroy” )
          】【restartApplication】BeanPostProcessor调用postProcessAfterinitialization方法,参数【RestartApplication$$SpringCGLIB$

          二、作用域

          】【restartApplication】BeanPostProcessor调用postProcessBeforeinitialization方法,参数【AppConfig$$SpringCGLIB$作用域类型】【appConfig】BeanPostProcessor调用postProcessAfterinitialization方法,参数【AppConfig$$SpringCGLIB$使用范围】【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来配置自定义初始化和销毁方法,如下所示:

          作用域描述 singleton

          在介绍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 的作用域中就存在如表所示的几种类型。

        • 前四个最常用
        • @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) ;
          @Component @Scope(WebApplicationContext.SCOPE_REQUEST) public class ScopeBean { }
          所有Spring 应用默认值, loC 容器只存在单例prototype
          所有Spring 应用每当从IoC 容器中取出一个 Bean,则创建一个新的Beansession
          Spring Web 应用HTTP 会话application
          Spring Web 应用Web 工程生命周期request
          Spring Web 应用Web 工程单次请求 (request)globalSession
          Spring Web 应用在一个全局的HTTPSession 中, 一个 Bean 定义对应一个实例。 实践中基本不使用

            总结

          • 对于application作用域,完全可以使用单例来替代。

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

            首先定义一个类

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

            从测试的结果来看,显然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)。 例如,下面的代码就是定义请求作用域:


上一篇:奇游怎么登录澳洲服务器?

下一篇:王者怎么看音标?