揭开Spring Bean生命周期的神秘面纱

        

目录

一、Spring IOC        

        1.1 Spring IOC 的加载过程

二、Spring Bean 生命周期

         2.1 实例化前置

        2.2 实例化后置

        2.3 属性赋值

        2.4 初始化前置

        2.5 初始化

        2.6 初始化后置

        2.7 Bean 销毁


        Spring 是一个开源的企业级Java应用程序框架,它简化了企业级应用程序开发,同时促进了松耦合、面向切面编程(AOP)、声明式事务管理以及基于Java EE平台的最佳实践。

        Spring 已经成为现代 Java 企业级应用开发的事实标准框架之一。今天就来了解下 Spring Bean的生命周期。

一、Spring IOC        

        Spring IOC 也叫控制反转或依赖注入,那谁控制谁呢?在以前,对象的创建销毁都是用户自己控制的,用了 IOC 之后对象的创建和销毁都交给了 Spring 容器来控制,用户不用管这些了,值管制业务需求就好。

        为什么叫控制反转呢?既然有反转,应该有正转。正转就是对象去找实例,比如你 new 了一个对象,那这个对象就指向了对象实例。反转就是通过实例来找对象。具体来说就是先通过容器找到实例,然后通过实例找到对象。

        1.1 Spring IOC 的加载过程

        整个加载过程大致如图所示:

        具体步骤如下:

        1、首先,通过 BeandefinitionReader 读取执行的配置文件生成 bean 的信息,然后到完整的 Beandefinition 定义信息,注意,这里存储的事 Bean 的定义信息,包括:是否单例、是否懒加载、是否抽象等等信息。就像工厂生产产品一样,准备好了原材料,下一步就该生成了。

        2、在生成完整的 Beandefinition 前,还可以通过 BeanFactoryPostProcessor 对 Beandefinition 进行增强,这个增强器可以设置多个。 

/**
 * 扩展方法--后置增强器(可修改bean的定义信息)
 */
@Component
public class ExtBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        BeanDefinition service = beanFactory.getBeanDefinition("Service");
        System.out.println("扩展方法--可进行修改beanDefinition的定义信息");
    }
}

        3、得到完整的 Beandefinition 后就可以创建对象了, 创建对象的过程称为 bean 的生命周,也就是从实例化到销毁的过程。

二、Spring Bean 生命周期

        Spring Bean 的生命周期简单来说只有四个阶段:实例化(Instantiation)、属性赋值(Populate)、初始化(Initialiation)、销毁(Destruction),在这四个阶段中外加几个扩展点,就组成了完整的生命周期。

        主要逻辑在doCreateBean()方法中,逻辑很清晰,就是顺序调用一下三个方法。这三个方法与三个生命周期阶段一一对应,非常重要。

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
      throws BeanCreationException {
   BeanWrapper instanceWrapper = null;
   if (mbd.isSingleton()) {
      instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
   }
   if (instanceWrapper == null) {
       // 实例化,通过反射实现
      instanceWrapper = createBeanInstance(beanName, mbd, args);
   }
   final Object bean = instanceWrapper.getWrappedInstance();
   Class<?> beanType = instanceWrapper.getWrappedClass();
   if (beanType != NullBean.class) {
      mbd.resolvedTargetType = beanType;
   }

   // Allow post-processors to modify the merged bean definition.
   synchronized (mbd.postProcessingLock) {
      if (!mbd.postProcessed) {
         try {
            applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
         }
         catch (Throwable ex) {
            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                  "Post-processing of merged bean definition failed", ex);
         }
         mbd.postProcessed = true;
      }
   }

   // Eagerly cache singletons to be able to resolve circular references
   // even when triggered by lifecycle interfaces like BeanFactoryAware.
   boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
         isSingletonCurrentlyInCreation(beanName));
   if (earlySingletonExposure) {
      if (logger.isTraceEnabled()) {
         logger.trace("Eagerly caching bean '" + beanName +
               "' to allow for resolving potential circular references");
      }
      addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
   }

   // Initialize the bean instance.
   Object exposedObject = bean;
   try {
       // 属性注入,包含循环依赖
      populateBean(beanName, mbd, instanceWrapper);
      // 初始化
      exposedObject = initializeBean(beanName, exposedObject, mbd);
   }
   catch (Throwable ex) {
      if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
         throw (BeanCreationException) ex;
      }
      else {
         throw new BeanCreationException(
               mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
      }
   }

   if (earlySingletonExposure) {
      Object earlySingletonReference = getSingleton(beanName, false);
      if (earlySingletonReference != null) {
         if (exposedObject == bean) {
            exposedObject = earlySingletonReference;
         }
         else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
            String[] dependentBeans = getDependentBeans(beanName);
            Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
            for (String dependentBean : dependentBeans) {
               if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                  actualDependentBeans.add(dependentBean);
               }
            }
            if (!actualDependentBeans.isEmpty()) {
               throw new BeanCurrentlyInCreationException(beanName,
                     "Bean with name '" + beanName + "' has been injected into other beans [" +
                     StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                     "] in its raw version as part of a circular reference, but has eventually been " +
                     "wrapped. This means that said other beans do not use the final version of the " +
                     "bean. This is often the result of over-eager type matching - consider using " +
                     "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
            }
         }
      }
   }

   // Register bean as disposable.
   try {
      registerDisposableBeanIfNecessary(beanName, bean, mbd);
   }
   catch (BeanDefinitionValidationException ex) {
      throw new BeanCreationException(
            mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
   }

   return exposedObject;
}

        实例化前后的扩展通过 InstantiationAwareBeanPostprocessor 实现,初始化的前后扩展通过 BeanPostprocessor 实现,如下代码:

Component
public class InstantiationAwareBeanPostProcessorDemo implements InstantiationAwareBeanPostProcessor {
 
    // 实例化前置
    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        
        System.out.println("postProcessBeforeInstantiation被调用了----在对象实例化之前调用-----beanName:" + beanName);
        // 默认什么都不做,返回null
        return null;
    }
 
    // 实例化后置
    @Override
    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessAfterInstantiation被调用了---------beanName:" + beanName);
        //默认返回true,什么也不做,继续下一步
        return true;
    }
    
    // 属性修改
    @Override
    public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
        System.out.println("postProcessPropertyValues被调用了---------beanName:"+beanName);
        // 此方法可对bean中的属性值进行、添加、修改、删除操作;
        // 对属性值进行修改,如果postProcessAfterInstantiation方法返回false,该方法可能不会被调用,
        return pvs;
    }
}

         2.1 实例化前置

        实例化前置使用的是 postProcessBeforeInstantiation 方法,有两个参数,分别是beanClass 和 beanName,顾名思义,就是在对象实例化之前对 bean 对象的 class 信息进行修改或者扩展,以达到我们想要的功能,它的底层是动态代理 AOP 技术实现的;且是 bean 生命周期中最先执行的方法。

        返回值是 Object 类型,这意味着我们可以返回任何类型的值,由于这个时候目标对象还未实例化,所以这个返回值可以用来代替原本该生成对象的目标对象的实例,也就是说,如果返回了非空的值,那么以后我们需要用到这个bean的时候,拿到的就现在返回的对象了,也就不会去走第二步去实例化对象了。

        然后就是执行对象实例化了,通过调用 doCreateBean 方法来实现。

        2.2 实例化后置

        实例化后置使用的是 postProcessAfterInstantiation 方法,这时实例已经创建,下一步就是属性赋值,相当于调用 set 方法,如果返回的是 false,意味着不会对该 bean 执行任何属性注入和其他初始化的逻辑。这种情况下,通常意味着开发者已经在该方法内部完成了所有必要的初始化工作,或者处于某种原因不让 Spring 容器继续初始化该 Bean。

        如果该方法返回的是 true,那会继续执行后续的流程。

        2.3 属性赋值

         属性赋值是通过 AbstractAutowireCapableBeanFactory.populateBean() 方法进行的。主要负责将 Bean 定义中的属性注入到已经实例化的 Bean 中。主要包含了 Setter 方法注入、构造器注入、字段注入等功能。

        2.4 初始化前置

        通过  BeanPostProcessor.postProcessBeforeInitialization() 来实现,在每一个 Bean 初始化之前执行,具体是指在调用初始化方法或 @PostConstruct 注解的方法执行前去执行前置操作。

        开发者可以根据业务需求,对特定的 Bean 在其初始化前做额外的增强功能,比如设置一些默认值、处理特殊的业务规则等。

        2.5 初始化

        Spring 初始化的方式有三种,分别是@PostConstruct、实现了 InitializingBean 接口、在注解 @Bean 中设置了 initMethod。

        通过 @PostConstruct 初始化实现如下:

import javax.annotation.PostConstruct;

public class MyBean {
    @PostConstruct
    public void init() {
        // 初始化逻辑
    }
}

        通过实现 InitializingBean 接口来初始化 Bean 时,需要重写 afterPropertiesSet 方法,Spring容器会在Bean实例化并设置好所有属性后调用该方法。

import org.springframework.beans.factory.InitializingBean;

public class MyBean implements InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        // 初始化逻辑
    }
}

        Java配置中的 @Bean 注解: 使用 @Bean 注解定义Bean时,可以指定 initMethod 属性来设置初始化方法。

@Configuration
public class AppConfig {
    @Bean(initMethod = "init")
    public MyBean myBean() {
        return new MyBean();
    }
}

// 对应的Java类
public class MyBean {
    public void init() {
        // 初始化逻辑
    }
}

        这些初始化方式的执行顺序大致为:

  1. @PostConstruct 注解的方法
  2. InitializingBean 接口的 afterPropertiesSet 方法
  3. init-method 指定的方法

        2.6 初始化后置

        通过调用 postProcessAfterInitialization 来实现初始化后置扩展,会在 Spring 容器完成对 Bean 的初始化之后,但在 Bean 交付给 Spring 容器进行使用之前执行。比如

  • 对 Bean 的属性值进行最后的修改或补充设置
  • 对 Bean 进行迭代增强,比如动态代理等
  • 甚至是完全替换掉这个 Bean 实例

        2.7 Bean 销毁

        当Spring容器不再需要一个Bean或容器本身正在关闭时,会按照一定的顺序对Bean进行销毁。销毁的方式如下:

  • 如果一个Bean实现了org.springframework.beans.factory.DisposableBean接口,那么在销毁阶段,Spring会调用其destroy()方法。
  • 若 Bean 中存在一个方法使用了@PreDestroy注解,那么在销毁 Bean 之前,Spring 会调用这个方法。
  • 如果在 @Bean 注解中使用了 destroy-method属性,销毁 Bean 时会调用该方法

        总之,Spring Bean 的生命周期在保持基本的功能基础上,引入了诸多扩展点,为开发者提供了诸多便捷的功能,以实现更加灵活的定制化需求。

        

往期经典推荐:

Redis分布式锁:保障微服务架构下的并发控制与数据一致性实战指南-CSDN博客

探析Drools规则引擎的工作原理_规则引擎执行性能-CSDN博客

实时数据传输的新里程——Server-Sent Events(SSE)消息推送技术-CSDN博客

Spring循环依赖的成因与破局-CSDN博客

SpringBoot项目并发处理大揭秘,你知道它到底能应对多少请求洪峰?_一个springboot能支持多少并发-CSDN博客

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

k8s资源监控_bitnami metrics-server v0(1),2024一位Linux运维中级程序员的跳槽面经

错误3 也有可能会遇到以下错误&#xff0c;按照下面提示解决 Error from server (ServiceUnavailable): the server is currently unable to handle the request (get nodes.metrics.k8s.io) 如果metrics-server正常启动&#xff0c;没有错误&#xff0c;应该就是网络问题。修改…

基于SpringBoot的“自习室预订系统”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“自习室预订系统”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 管理员登录界面 座位预订管理界面图 自习室管理…

13 Python进阶:pip及其他常用模块

pip 是 Python 包管理工具&#xff0c;它提供了对 Python 包的查找、下载、安装、卸载的功能。 包地址&#xff1a; https://pypi.org/ 最新的 Python 版本已经预装了 pip。 pip 各种命令 查看是否已经安装 pip 可以使用以下命令&#xff1a; pip --version下载安装包使用…

Leetcode 581. 最短无序连续子数组

心路历程&#xff1a; 本以为这道题要用动态规划求解&#xff0c;因为题目中这几个关键字与动态规划太匹配了&#xff0c;结果想了半天也没发现dp(i)和dp(i-1)的递推关系。 这道题本意考察双指针的做法&#xff0c;也可以用排序后做比较的方式来做。 注意的点&#xff1a; 1…

【Redis 知识储备】冷热分离架构 -- 分布系统的演进(5)

冷热分离架构 简介出现原因架构工作原理技术案例架构优缺点 简介 引入缓存, 实行冷热分离, 将热点数据放到缓存中快速响应 (如存储到 Redis中) 出现原因 海量的请求导致数据库负载过高, 站点响应再读变慢 架构工作原理 多了缓存服务器, 对于热点数据全部到缓存中, 不常用数…

Android10系统ROM定制之Frida逆向分析实战

CSDN在线课程地址: https://edu.csdn.net/course/detail/37881 推荐阅读 2024培训课程 2024技术交流群 Android14系统安全 Android10系统ROM定制之Frida逆向分析实战

ctfshow web入门 文件包含 web151--web161

web151 打算用bp改文件形式(可能没操作好)我重新试了一下抓不到 文件上传不成功 改网页前端 鼠标右键&#xff08;检查&#xff09;&#xff0c;把png改为php访问&#xff0c;执行命令 我上传的马是<?php eval($_POST[a]);?> 查看 web152 上传马 把Content-Type改为…

在linux环境下如何进行stm32的开发?

在Linux环境下进行STM32开发确实需要一些配置和工具。我这里有一套嵌入式入门教程&#xff0c;不仅包含了详细的视频讲解&#xff0c;项目实战。如果你渴望学习嵌入式&#xff0c;不妨点个关注&#xff0c;给个评论222&#xff0c;私信22&#xff0c;我在后台发给你。 选择开发…

一起学习python——基础篇(7)

今天讲一下python的函数。 函数是什么&#xff1f;函数是一段独立的代码块&#xff0c;这块代码是为了实现一些功能&#xff0c;而这个代码块只有在被调用时才能运行。 在 Python 中&#xff0c;使用 def 关键字定义函数&#xff1a; 函数的固定结构就是 def(关键字)函数名字…

Redis单线程 VS 多线程

一、Redis 为什么选择单线程&#xff1f; 这种说法其实并不严谨&#xff0c;为什么这么说呢&#xff1f; Redis的版本有很多 3.x、4.x、6.x&#xff0c;版本不同架构也不同的&#xff0c;不限定版本问是否单线程也是不太严谨。 版本3.x&#xff0c;最早版本&#xff0c;也就…

第十二届蓝桥杯大赛软件赛省赛C/C++大学B组

第十二届蓝桥杯大赛软件赛省赛C/C 大学 B 组 文章目录 第十二届蓝桥杯大赛软件赛省赛C/C 大学 B 组1、空间2、卡片3、直线4、货物摆放5、路径6、时间显示7、砝码称重8、杨辉三角形9、双向排序10、括号序列 1、空间 1MB 1024KB 1KB 1024byte 1byte8bit // cout<<"2…

python基于opencv实现数籽粒

千粒重是一个重要的农艺性状&#xff0c;通过对其的测量和研究&#xff0c;我们可以更好地理解作物的生长状况&#xff0c;优化农业生产&#xff0c;提高作物产量和品质。但数籽粒数目是一个很繁琐和痛苦的过程&#xff0c;我们现在用一个简单的python程序来数水稻籽粒。代码的…

有限的边界-DDD领域

从广义上讲&#xff0c;领域&#xff08;Domain&#xff09;即是一个组织所做的事情以及其中所包含的一切。商业机构通常会确定一个市场&#xff0c;然后在这个市场中销售产品和服务。每个组织都有它自己的业务范围和做事方式。这个业务范围以及在其中所进行的活动便是领域。当…

Linux云计算之Linux基础3——Linux系统基础2

1、终端 终端(terminal)&#xff1a;人和系统交互的必要设备&#xff0c;人机交互最后一个界面&#xff08;包含独立的输入输出设备&#xff09; 物理终端(console)&#xff1a;直接接入本机器的键盘设备和显示器虚拟终端(tty)&#xff1a;通过软件方式虚拟实现的终端。它可以…

mysql的索引类型与数据存储

mysql索引与类型 什么是索引&#xff1f; 索引&#xff08;Index&#xff09;是帮助MySQL高效获取数据的数据结构。我们可以简单理解为&#xff1a;快速查找排好序的一种数据结构。Mysql索引主要有两种结构&#xff1a;BTree索引和Hash索引。我们平常所说的索引&#xff0c;如…

web渗透-SSH私钥泄露

发现主机 netdiscover -r 192.168.164.0 扫描端口 看到开放80和31337端口都为http服务 浏览器访问测试 查看80端口和31337端口网页和源代码并无发现有用信息 目录扫描 扫描出80端口并无有用信息 扫描31337端口 发现敏感文件robots.txt和目录.ssh 访问敏感文件和目录 /.ss…

好物分享:FPGA实现SDI视频编解码的方案设计汇总

目录 1、前言2、专用芯片方案2.1、GS2971FPGA的图像采集 设计方案2.2、GS2971FPGA的图像采集图像缩放 设计方案2.3、GS2971FPGA的图像采集纯verilog图像缩放纯verilog视频拼接 设计方案2.4、GS2971FPGA的图像采集HLS图像缩放Video Mixer视频拼接 设计方案2.5、GS2971FPGA的图像…

Java单链表和LinkedList的实现

一、单链表的实现 无头单向非循环链表 定义异常用于判断所给位置是否合法 public class IndexNotLegal extends RuntimeException{public IndexNotLegal(){}public IndexNotLegal(String smg){super(smg);} } class ListNode中包含当前节点的值和下一个节点指向 实现链表的…

阿里云2024年优惠券领取及使用常见问题

阿里云是阿里巴巴旗下云计算品牌&#xff0c;服务涵盖云服务器、云数据库、云存储、域名注册等全方位云服务和各行业解决方案。为了吸引用户上云&#xff0c;阿里云经常推出各种优惠活动&#xff0c;其中就包括阿里云优惠券。本文将对阿里云优惠券领取及使用常见问题进行解答&a…

鸿蒙原生应用已超4000个!

鸿蒙原生应用已超4000个&#xff01; 来自 HarmonyOS 微博近期消息&#xff0c;#鸿蒙千帆起# 重大里程碑&#xff01;目前已有超4000个应用加入鸿蒙生态。从今年1月18日华为宣布首批200多家应用厂商正在加速开发鸿蒙原生应用&#xff0c;到3月底超4000个应用&#xff0c;短短…