Spring Bean 中的生命周期和获取方式详解

 更新时间:2024年12月07日 10:46:22   作者:程序猿进阶  
Bean的加载和获取过程涉及配置文件解析、资源加载、XML解析和BeanDefinition注册等步骤,本文给大家介绍Spring Bean 的生命周期和获取方式,感兴趣的朋友跟随小编一起看看吧

一、Spring Bean 的生命周期,如何被管理的

对于普通的 Java对象,当 new的时候创建对象,当它没有任何引用的时候被垃圾回收机制回收。而由 Spring IoC容器托管的对象,它们的生命周期完全由容器控制。Spring 中每个 Bean的生命周期如下:

主要对几个重要的步骤进行说明:
【1】实例化 Bean: 对于 BeanFactory容器,当客户向容器请求一个尚未初始化的 bean时,或初始化 bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用 createBean进行实例化。对于 ApplicationContext容器,当容器启动结束后,便实例化所有的单实例 bean。容器通过获取 BeanDefinition对象中的信息进行实例化。并且这一步仅仅是简单的实例化,并未进行依赖注入。实例化对象被包装在 BeanWrapper 对象中,BeanWrapper 提供了设置对象属性的接口,从而避免了使用反射机制设置属性。通过工厂方法或者执行构造器解析执行即可:创建的对象是个空对象。
【2】设置对象属性(依赖注入): 实例化后的对象被封装在 BeanWrapper对象中,并且此时对象仍然是一个原生的状态,并没有进行依赖注入。紧接着获取所有的属性信息通过 populateBean(beanName,mbd,bw,pvs),Spring 根据 BeanDefinition 中的信息进行依赖注入。并且通过 BeanWrapper提供的设置属性的接口完成依赖注入。赋值之前获取所有的 InstantiationAwareBeanPostProcessor 后置处理器的 postProcessAfterInstantiation() 第二次获取InstantiationAwareBeanPostProcessor 后置处理器;执行 postProcessPropertyValues()最后为应用 Bean属性赋值:为属性利用 setter 方法进行赋值 applyPropertyValues(beanName,mbd,bw,pvs)。
【3】bean 初始化: initializeBean(beanName,bean,mbd)。
 1)执行xxxAware 接口的方法,调用实现了BeanNameAware、BeanClassLoaderAware、BeanFactoryAware接口的方法。
 2)执行后置处理器之前的方法:applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName)所有后置处理器的 BeanPostProcessor.postProcessBeforeInitialization()。
 3)执行初始化方法: InitializingBean 与 init-methodinvoke 当 BeanPostProcessor的前置处理完成后就会进入本阶段。先判断是否实现了 InitializingBean接口的实现;执行接口规定的初始化。其次自定义初始化方法。

InitializingBean 接口只有一个函数:afterPropertiesSet()这一阶段也可以在 bean正式构造完成前增加我们自定义的逻辑,但它与前置处理不同,由于该函数并不会把当前 bean对象传进来,因此在这一步没办法处理对象本身,只能增加一些额外的逻辑。若要使用它,我们需要让 bean实现该接口,并把要增加的逻辑写在该函数中。然后 Spring会在前置处理完成后检测当前 bean是否实现了该接口,并执行 afterPropertiesSet函数。当然,Spring 为了降低对客户代码的侵入性,给 bean的配置提供了 init-method属性,该属性指定了在这一阶段需要执行的函数名。Spring 便会在初始化阶段执行我们设置的函数。init-method 本质上仍然使用了InitializingBean接口。
 4)applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);执行初始化之后的后置处理器的方法。BeanPostProcessor.postProcessAfterInitialization(result, beanName);
【4】Bean的销毁: DisposableBean 和 destroy-method:和 init-method 一样,通过给 destroy-method 指定函数,就可以在bean 销毁前执行指定的逻辑。

Bean 的管理就是通过 IOC容器中的 BeanDefinition信息进行管理的。

二、Spring Bean 的加载和获取过程

Bean 的加载过程,主要是对配置文件的解析,并注册 bean 的过程 。

【1】根据注解或者 XML中 定义 Bean 的基本信息。例如:spring-core.xml

<bean id="myBean" class="com.taobao.pojo"></bean>

【2】获取配置文件:这里使用最原始的方式获取。

Resource resource = new ClassPathResource("spring-core.xml")

【3】 利用 XmlBeanFactory 解析并注册 bean 定义:已经完成将配置文件包装成了 Spring 定义的资源,并触发解析和注册。XmlBeanFactory 实际上是对 DefaultListableBeanFactory(非常核心的类,它包含了基本 IOC 容器所具有的重要功能,是一个 IOC 容器的基本实现。然后是调用了this.reader.loadBeanDefinitions(resource),从这里开始加载配置文件) 和 XmlBeanDefinitionReader 组合使用方式的封装,所以这里我们仍然将继续分析基于 XmlBeanFactory 加载 bean 的过程。

XmlBeanFactory beanFactory = new XmlBeanFactory(resource);

Spring 使用了专门的资源加载器对资源进行加载,这里的 reader 就是 XmlBeanDefinitionReader 对象,专门用来加载基于 XML 文件配置的 bean。这里的加载过程为:

①、利用 EncodedResource 二次包装资源文件;
②、获取资源输入流,并构造 InputSource 对象:

// 获取资源的输入流
InputStream inputStream = encodedResource.getResource().getInputStream();
// 构造InputSource对象
InputSource inputSource = new InputSource(inputStream);
// 真正开始从 XML文件中加载 Bean定义
return this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());

这里的 this.doLoadBeanDefinitions(inputSource, encodedResource.getResource()) 就是真正开始加载 XMl 的入口,该方法源码如下:第一步获取 org.w3c.dom.Document 对象,第二步由该对象解析得到 BeanDefinition 对象,并注册到 IOC 容器中。

protected intdoLoadBeanDefinitions(InputSource inputSource, Resource resource){
  try {
    // 1. 加载xml文件,获取到对应的Document(包含获取xml文件的实体解析器和验证模式)
    Document doc = this.doLoadDocument(inputSource, resource);
    // 2. 解析Document对象,并注册bean
    return this.registerBeanDefinitions(doc, resource);
  } 
}

③、获取 XML 文件的实体解析器和验证模式:this.doLoadDocument(inputSource, resource) 包含了获取实体解析器、验证模式,以及 Document 对象的逻辑,XML 是半结构化数据,XML 的验证模式用于保证结构的正确性,常见的验证模式有 DTD 和 XSD 两种。
④、加载 XML 文件,获取对应的 Document 对象和验证模式与解析器,解析器就可以加载 Document 对象了,这里本质上调用的是 DefaultDocumentLoader 的 loadDocument() 方法,源码如下:整个过程类似于我们平常解析 XML 文件的流程。

public DocumentloadDocument(InputSource inputSource, EntityResolver entityResolver,
               ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
    DocumentBuilderFactory factory = this.createDocumentBuilderFactory(validationMode, namespaceAware);
    DocumentBuilder builder = this.createDocumentBuilder(factory, entityResolver, errorHandler);
    return builder.parse(inputSource);
}

⑤、由 Document 对象解析并注册 bean:完成了对 XML 文件的到 Document 对象的解析,我们终于可以解析 Document 对象,并注册 bean 了,这一过程发生在 this.registerBeanDefinitions(doc, resource) 中,源码如下:

public intregisterBeanDefinitions(Document doc, Resource resource)throwsBeanDefinitionStoreException{
    // 使用DefaultBeanDefinitionDocumentReader构造
    BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();
    // 记录之前已经注册的BeanDefinition个数
    int countBefore = this.getRegistry().getBeanDefinitionCount();
    // 加载并注册bean
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    // 返回本次加载的bean的数量
    return getRegistry().getBeanDefinitionCount() - countBefore;
}

这里方法的作用是创建对应的 BeanDefinitionDocumentReader,并计算返回了过程中新注册的 bean 的数量,而具体的注册过程,则是由 BeanDefinitionDocumentReader 来完成的,具体的实现位于子类 DefaultBeanDefinitionDocumentReader 中:

publicvoidregisterBeanDefinitions(Document doc, XmlReaderContext readerContext){
    this.readerContext = readerContext;
    // 获取文档的root结点
    Element root = doc.getDocumentElement();
    this.doRegisterBeanDefinitions(root);
}

还是按照 Spring 命名习惯,doRegisterBeanDefinitions 才是真正干活的地方,这也是真正开始解析配置的核心所在:

protectedvoiddoRegisterBeanDefinitions(Element root){
  BeanDefinitionParserDelegate parent = this.delegate;
  this.delegate = this.createDelegate(getReaderContext(), root, parent);
    // 处理profile标签(其作用类比pom.xml中的profile)
    String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
    // 解析预处理,留给子类实现
    this.preProcessXml(root);
    // 解析并注册BeanDefinition
    this.parseBeanDefinitions(root, this.delegate);
    // 解析后处理,留给子类实现
    this.postProcessXml(root);
}

方法在解析并注册 BeanDefinition 前后各设置一个模板方法,留给子类扩展实现,而在this.parseBeanDefinitions(root, this.delegate)中执行解析和注册逻辑:方法中判断当前标签是默认标签还是自定义标签,并按照不同的策略去解析。

protectedvoidparseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate){
    if (delegate.isDefaultNamespace(root)) {
        // 解析默认标签
        NodeList nl = root.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            if (node instanceof Element) {
                Element ele = (Element) node;
                if (delegate.isDefaultNamespace(ele)) {
                  // 解析默认标签
                  this.parseDefaultElement(ele, delegate);
                } else {
                  // 解析自定义标签
                  delegate.parseCustomElement(ele);
                }
            }
        }
      } else {
        // 解析自定义标签
        delegate.parseCustomElement(root);
   }
}

到这里我们已经完成了静态配置到动态 BeanDefinition 的解析,这个时候 bean 的定义已经处于内存中。

【4】 从 IOC容器加载获取 bean:我们可以调用 beanFactory.getBean("myBean") 方法来获取目标对象。

MyBean myBean = (MyBean) beanFactory.getBean("myBean");

到此这篇关于Spring Bean 的生命周期和获取方式详解的文章就介绍到这了,更多相关Spring Bean 生命周期内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Idea实现接口的方法上无法添加@Override注解的解决方案

    Idea实现接口的方法上无法添加@Override注解的解决方案

    文章介绍了在IDEA中实现接口方法时无法添加@Override注解的问题及其解决方法,主要步骤包括更改项目结构中的Language level到支持该注解的版本,以及在pom.xml文件中指定maven-compiler-plugin的版本以解决自动更新后的问题
    2025-02-02
  • Java 如何用二维数组创建空心菱形

    Java 如何用二维数组创建空心菱形

    这篇文章主要介绍了Java 如何用二维数组创建空心菱形,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • 详解java如何调用代理ip

    详解java如何调用代理ip

    在进行网络爬虫、数据采集或是访问网站时,使用代理IP显得尤为重要,本文将深入探讨如何在Java中调用代理IP,感兴趣的小伙伴可以了解一下
    2024-11-11
  • 使用Java获取文件树的代码实现

    使用Java获取文件树的代码实现

    Java语言提供了丰富的库和工具,使得我们可以方便地获取和操作Java文件的语法树(AST, Abstract Syntax Tree),在这篇博客中,我们将探讨如何使用Java来获取一个Java文件的语法树,并展示详细的代码示例和运行结果,需要的朋友可以参考下
    2024-08-08
  • Java实现JSON与XML相互转换的简明教程

    Java实现JSON与XML相互转换的简明教程

    Java实现复杂数据结构(如嵌套对象、数组)在 JSON 与 XML 之间的相互转换,可以使用 Jackson 和 Jackson XML 扩展库来完成,Jackson 是一个流行的 JSON 处理库,通过 Jackson 的 XML 扩展库,可以实现 JSON 和 XML 之间的转换,需要的朋友可以参考下
    2024-08-08
  • java操作mysql实现增删改查的方法

    java操作mysql实现增删改查的方法

    这篇文章主要介绍了java操作mysql实现增删改查的方法,结合实例形式分析了java操作mysql数据库进行增删改查的具体实现技巧与相关注意事项,需要的朋友可以参考下
    2017-05-05
  • Spring Security角色继承实现过程解析

    Spring Security角色继承实现过程解析

    这篇文章主要介绍了Spring Security角色继承实现过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-08-08
  • Java获取任意http网页源代码的方法

    Java获取任意http网页源代码的方法

    这篇文章主要介绍了Java获取任意http网页源代码的方法,可实现获取网页代码以及去除HTML标签的代码功能,涉及Java正则操作相关实现技巧,需要的朋友可以参考下
    2017-09-09
  • java金额数字转中文工具类详解

    java金额数字转中文工具类详解

    这篇文章主要为大家详细介绍了java金额数字转中文工具类的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-04-04
  • jdbc连接数据库实例详解

    jdbc连接数据库实例详解

    在本篇内容里小编给大家分享了关于jdbc如何连接数据库的相关知识点内容,需要的朋友们学习下。
    2019-02-02

最新评论