万字解析 XML 配置映射为 BeanDefinition 的源码
本文分享自华为云社区《Spring高手之路16——解析XML配置映射为BeanDefinition的源码》,作者:砖业洋__。
1.BeanDefinition 阶段的分析
Spring 框架中控制反转(IOC)容器的 BeanDefinition 阶段的具体步骤,主要涉及到 Bean 的定义、加载、解析,并在后面进行编程式注入和后置处理。这个阶段是 Spring 框架中 Bean 生命周期的早期阶段之一,对于理解整个 Spring 框架非常关键。
加载配置文件、配置类
在这一步,Spring 容器通过配置文件或配置类来了解需要管理哪些 Bean。对于基于 XML 的配置,通常使用 ClassPathXmlApplicationContext 或者 FileSystemXmlApplicationContext。
解析配置文件、配置类并封装为 BeanDefinition
Spring 框架通过使用 BeanDefinitionReader 实例(如 XmlBeanDefinitionReader)来解析配置文件。解析后,每个 Bean 配置会被封装成一个 BeanDefinition 对象,这个对象包含了类名、作用域、生命周期回调等信息。
编程式注入额外的 BeanDefinition
除了配置文件定义的 Bean,也可以通过编程的方式动态添加 BeanDefinition 到 IOC 容器中,这增加了灵活性。
BeanDefinition 的后置处理
BeanDefinition 的后置处理是指容器允许使用 BeanDefinitionRegistryPostProcessor 或 BeanFactoryPostProcessor 来对解析后的 BeanDefinition 做进一步处理,例如修改 Bean 的属性等。
2. 加载 xml 配置文件
2.1 XML 配置文件中加载 bean 的代码示例
先给出最简单的代码示例,然后逐步分析
全部代码如下:
主程序:
xml 文件
运行结果:
接着我们就从这段代码开始分析
2.2 setConfigLocations - 设置和保存配置文件路径
我们还是以 Spring 5.3.7 的源码为例分析
这段代码,我们利用 idea 点击去分析,最后在 ClassPathXmlApplicationContext 的重载方法里看到调用了 setConfigLocations 设置配置文件的路径。
接着看看 setConfigLocations 方法
setConfigLocations() 方法的主要作用是设定 Spring 容器加载 Bean 定义时所需要读取的配置文件路径。
这些路径可以是类路径下的资源、文件系统中的资源或者其他任何通过 URL 定位的资源。该方法确保所有提供的配置路径都被保存并在稍后的容器刷新操作中使用。
源码提出来分析:
在上下文被刷新的时候,这些配置文件位置会被读取,并且 Spring 容器将解析其中定义的 beans 并将它们注册到容器中。setConfigLocations() 方法只是设置了这些位置,而实际的加载和注册过程是在上下文刷新时完成的。
这个 setConfigLocations 方法通常不是由用户直接调用的,而是在 ApplicationContext 初始化的过程中被框架调用,例如在基于 XML 的配置中,我们会在初始化 ClassPathXmlApplicationContext 或 FileSystemXmlApplicationContext 时提供配置文件的路径。
在 debug 的时候,可以看到把测试代码中设置的 xml 配置文件的路径保存了。
2.3 refresh - 触发容器刷新,配置文件的加载与解析
我们上面看到 ClassPathXmlApplicationContext 方法里面,执行完 setConfigLocations 后,紧接着有个 refresh 方法,我们来看看。
在 Spring 框架中,refresh()方法是非常关键的,它是 ApplicationContext 接口的一部分。这个方法的主要功能是刷新应用上下文,加载或者重新加载配置文件中定义的 Bean,初始化所有的单例,配置消息资源,事件发布器等。
代码提出来分析:
这个方法精确执行一系列步骤来配置 ApplicationContext,包括 Bean 的加载、注册和初始化。刷新过程包括了 Bean 定义的载入、注册以及 Bean 的初始化等一系列复杂的步骤。
在现代 Spring 框架中,ApplicationContext 一般在容器启动时刷新一次。一旦容器启动并且上下文被刷新,所有的 Bean 就被加载并且创建了。尽管技术上可能存在调用 refresh()方法多次的可能性,但这在实际中并不常见,因为这意味着重置应用上下文的状态并重新开始。这样做将销毁所有的单例 Bean,并重新初始化它们,这在大多数应用中是不可取的,不仅代价昂贵而且可能导致状态丢失、数据不一致等问题。
对于基于 xml 的 ApplicationContext(如 ClassPathXmlApplicationContext),在调用 refresh()方法时会重新读取和解析配置文件,然后重新创建 BeanFactory 和 Bean 的定义。如果容器已经被刷新过,则需要先销毁所有的单例 Bean,关闭 BeanFactory,然后重新创建。通常,这个功能用于开发过程中或者测试中,不推荐在生产环境使用,因为它的开销和风险都很大。
我们来看一下重点,加载配置文件的操作在哪里?这里图上我标注出来了,obtainFreshBeanFactory 方法里面有个 refreshBeanFactory 方法。
refreshBeanFactory 方法是个抽象方法,我们来看看实现类是怎么实现的,根据继承关系找到实现类的 refreshBeanFactory 方法。
refreshBeanFactory()方法通常在 refresh()方法中被调用。这个方法确保当前 ApplicationContext 含有一个清洁状态的 BeanFactory。
代码提出来分析:
这个方法在 AbstractApplicationContext 的具体实现中被重写。它提供了刷新 bean 工厂的模板——如果已经存在一个,则将其销毁并关闭;然后创建一个新的 bean 工厂,进行定制,并填充 bean 定义。在加载 bean 定义(例如,从 XML 文件读取)时,如果遇到 I/O 异常,会抛出一个 ApplicationContextException,提供有关错误性质的更多上下文信息。
这段代码我们可以看到有 loadBeanDefinitions 方法,是从底层资源(例如 XML 文件)中加载 bean 定义到 beanFactory,逻辑很复杂,我们下面来进行单独分析。
2.4 loadBeanDefinitions - 具体的 BeanDefinition 加载逻辑
this.loadBeanDefinitions 方法是在 AbstractApplicationContext 的子类中实现的,这种模式是一个典型的模板方法设计模式的例子。在模板方法设计模式中,一个算法的框架(即一系列的步骤)被定义在父类的方法中,但是一些步骤的具体实现会延迟到子类中完成。
AbstractApplicationContext 提供了 refreshBeanFactory 方法的框架,这个方法定义了刷新 BeanFactory 的步骤,但是它将 loadBeanDefinitions 的具体实现留给了子类。子类需要根据具体的存储资源类型(比如 XML 文件、Java 注解、Groovy 脚本等)来实现这个方法。
子类 AbstractXmlApplicationContext 实现的 loadBeanDefinitions 方法如下:
loadBeanDefinitions()方法是 Spring 框架中用于加载、解析并注册 Bean 定义的核心方法。其基本职责是从一个或多个源读取配置信息,然后将这些信息转换成 Spring 容器可以管理的 Bean 定义。这个方法通常在 Spring 上下文初始化过程中被调用,是 Spring 容器装载 Bean 定义的关键步骤。
代码提出来分析:
在 loadBeanDefinitions(DefaultListableBeanFactory beanFactory)方法中,首先创建了一个 XmlBeanDefinitionReader 实例,这个读取器是专门用来解析 XML 配置文件并把 Bean 定义加载到 DefaultListableBeanFactory 中。beanDefinitionReader 的相关属性被设置了,包括环境变量、资源加载器和实体解析器。这些设置确保了 beanDefinitionReader 能正确地解析 XML 文件并能解析文件中的占位符和外部资源。
接着,通过调用 initBeanDefinitionReader 方法,可以对 XmlBeanDefinitionReader 实例进行一些额外的配置,例如设置 XML 验证。最后,调用 loadBeanDefinitions(XmlBeanDefinitionReader reader)方法实际进行加载操作。这个方法会调用读取器来实际地读取和解析 XML 文件,把 Bean 定义加载到 Spring 容器中。
在 loadBeanDefinitions(XmlBeanDefinitionReader reader)方法中,首先尝试从 getConfigResources 方法获取 XML 配置文件资源,如果存在这样的资源,则通过 reader 加载这些定义。其次,尝试获取配置文件位置信息,如果存在,则通过 reader 加载这些位置指定的配置文件。这种设计允许从不同的来源加载配置,如直接从资源文件或者从指定的文件路径。
debug 可以看到 reader 和 configLocations 的详细状态
这里看到还有一个 reader.loadBeanDefinitions(configLocations);这是在做什么呢?下面接着来看!
2.5 loadBeanDefinitions - 由 XmlBeanDefinitionReader 实现
debug 的时候可以看到这里的 reader 是 XmlBeanDefinitionReader,点击跟踪 reader.loadBeanDefinitions(configLocations);方法,调用的方法在 AbstractBeanDefinitionReader,而 XmlBeanDefinitionReader 继承自 AbstractBeanDefinitionReader。
这里配置文件循环加载,有一个 count += this.loadBeanDefinitions(location); 继续跟踪!
这段代码的逻辑动作大致为:
根据传入的资源位置字符串,通过资源加载器(ResourceLoader)获取对应的资源。
如果资源加载器是资源模式解析器(ResourcePatternResolver),它会处理路径中的模式(比如通配符),加载所有匹配的资源。
读取资源,解析并注册其中定义的所有 bean 定义。
如果提供了一个实际资源的集合(actualResources),解析出来的资源将被添加到这个集合中。
返回加载并注册的 bean 定义的数量。
我们还是看重点,继续跟踪里面的 loadBeanDefinitions
代码提出来分析:
在这段代码中,loadBeanDefinitions 首先将 Resource 转换为 EncodedResource,这允许它保留关于资源编码的信息。然后,它尝试将资源加载为 InputStream 并将其转换为 InputSource,这是 XML 解析所需要的。接着它调用 doLoadBeanDefinitions 方法,实际上负责解析 XML 并注册 Bean 定义。
在这个过程中,代码确保了不会循环加载相同的资源,并且在加载资源时,如果发生异常,会适当地清理资源并报告错误。加载的 Bean 定义数量在完成后被返回。
我们来重点看下这段代码的重点步骤:doLoadBeanDefinitions 方法!
2.6 doLoadBeanDefinitions - 读取并解析 XML 配置文件内容
doLoadBeanDefinitions 方法做了什么?
具体步骤如下:
使用 doLoadDocument 方法将给定的 InputSource 解析为 DOM Document 对象。这个 Document 对象代表了 XML 文件的结构。
通过调用 registerBeanDefinitions 方法,将解析得到的 Document 中的 Bean 定义注册到 Spring 的 Bean 工厂中。这个方法返回注册的 Bean 定义的数量。
如果日志级别设置为 DEBUG,则会记录加载的 Bean 定义数量。
这里重点是 registerBeanDefinitions 方法,继续跟踪代码
继续看重点,最终追到 doRegisterBeanDefinitions 方法
doRegisterBeanDefinitions(Element root) 方法是 Spring 框架中用于解析 XML 配置文件中的 Bean 定义并注册它们到 Spring 容器的方法。这个方法通常在 XML 文件读取并转换成 DOM(Document Object Model)树之后调用,此时 XML 文件的根元素通过参数 root 传递给这个方法。
代码提出来分析:
上述代码片段是 Spring 框架用于注册 Bean 定义的内部方法。该方法在解析 XML 配置文件并注册 Bean 定义到 Spring 容器时被调用。它包含处理 profile 属性以根据运行时环境决定是否加载特定 Bean 定义的逻辑,以及前后处理钩子,允许在解析前后进行自定义操作。最后,它确保解析代理(delegate)被重置为之前的状态,以维护正确的状态。
接着,我们要看看是如何解析 xml 的,重点关注下 parseBeanDefinitions 方法
2.7 parseBeanDefinitions - 解析 XML 中的 BeanDefinition 元素
parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) 方法的主要目的是遍历 XML 配置文件的根节点,解析并注册其中定义的所有 Bean。该方法负责区分不同类型的元素,即默认命名空间下的标准元素和自定义命名空间下的自定义元素,并对它们进行相应的处理。
代码提出来分析:
这段代码的作用是解析 XML 文件中定义的 bean。它检查每个 XML 元素(包括根元素和子元素),并根据这些元素是否属于 Spring 的默认命名空间(通常是"http://www.springframework.org/schema/beans"),调用不同的处理方法。如果元素属于默认命名空间,那么它将调用 parseDefaultElement 来解析标准的 Spring 配置元素,例如<bean>。如果元素不属于默认命名空间,那么将认为它是一个自定义元素,并调用 parseCustomElement 来解析。自定义元素通常是由开发人员定义或 Spring 扩展提供的,以增加框架的功能。
这里可以看到是一个循环处理 Element 节点,解析的动作主要是 parseDefaultElement 方法,继续来看看。
parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) 方法是 Spring 框架解析 XML 配置文件中默认命名空间(也就是没有前缀的 Spring 命名空间)元素的方法。这个方法专门处理 <import>, <alias>, <bean>, 和 <beans> 这几种标签。
“没有前缀的 Spring 命名空间” 是指那些元素?它们属于 Spring 的默认命名空间,但在使用时不需要指定命名空间前缀。如 <bean>, <property> 或 <constructor-arg> ,这些元素都是没有前缀的,它们属于 Spring 默认定义的 XML 模式命名空间,默认命名空间通常在 XML 文件的顶部通过 xmlns 属性声明。
代码提出来分析:
这段代码的功能是根据元素的名称来决定对 XML 配置文件中的不同标签进行不同的处理操作。它处理 Spring 框架默认命名空间下的四种主要标签:
<import>:导入其他 Spring XML 配置文件到当前的配置文件中。
<alias>:为一个已经定义的 bean 提供一个或多个别名。
<bean>:定义一个 Spring 管理的 bean,是最常用的元素,包含了 bean 的详细配置。
<beans>:定义一个 beans 的集合,通常是配置文件中的顶层元素,但也可以是嵌套定义,表示一个新的作用域或者上下文。
这样,Spring 可以根据这些元素来构建应用上下文中的 bean 工厂。
调试可以发现,xml 已经解析出初步的雏形了
在这里似乎没看到 bean 元素,这是怎么解析的呢?让我们一步一步来,在上面提到的 parseDefaultElement 方法中有调用 processBeanDefinition 方法,来看看这是干嘛的。
2.8 processBeanDefinition - 对<bean>标签进行具体解析和处理
processBeanDefinition 方法是 Spring 框架中用于处理 <bean> XML 配置元素的方法。其目的是将 <bean> 元素中描述的信息转换为 Spring 内部使用的 BeanDefinition 对象,并将其注册到 Spring IoC 容器中。这是 Spring bean 生命周期中的一个关键步骤,因为在这里定义的 bean 会在容器启动时被实例化和管理
代码提出来分析:
该方法通常在 Spring 框架的 bean 定义解析过程中使用,它处理基于提供的 XML 元素创建和注册 bean 定义的逻辑。BeanDefinitionParserDelegate 是一个帮助类,负责处理解析特定 Spring XML 结构的细节。
debug 这个类的时候,发现已经解析出这个 bean 的 class 和 id 了
有人会好奇了,这是如何将 xml 元素封装为 BeanDefinitionHolder 呢
parseBeanDefinitionElement 方法是用来解析 Spring 配置文件中 <bean> 元素的定义,并生成对应的 BeanDefinitionHolder 对象。BeanDefinitionHolder 是一个包装类,它封装了 BeanDefinition 实例和该定义的名称(即 bean 的 id)以及别名(如果有的话)。
代码提出来分析:
这段代码负责解析 XML 中的<bean>元素,提取 id 和 name 属性,并处理可能的别名。然后它创建一个 AbstractBeanDefinition,这是 Spring 中 bean 定义的抽象表现形式。如果没有指定 bean 的名称,它会尝试生成一个唯一的名称,并在必要时添加别名。最终,它返回一个包含所有这些信息的 BeanDefinitionHolder。如果在解析过程中遇到任何问题,会记录错误并返回 null。
在这段代码中,会调用另一个重载方法,this.parseBeanDefinitionElement(ele, beanName, containingBean);这段代码里有封装 <bean> 其它属性的 parseBeanDefinitionAttributes 方法,我们来看下
方法 parseBeanDefinitionAttributes 用于解析 Spring 配置文件中 <bean> 元素的属性,并将这些属性应用到传入的 AbstractBeanDefinition 对象上。这个过程是为了设置 bean 的作用域、是否延迟初始化、自动装配模式、依赖关系、是否作为自动装配的候选、是否是优先考虑的 bean(primary)、初始化方法、销毁方法、工厂方法和工厂 bean 名称等属性。方法处理了属性的默认值以及处理了一些属性的遗留格式(如 singleton)。
直接提出代码分析:
这段代码的核心功能是将 XML 配置文件中的属性转换为 BeanDefinition 对象的属性。对于每个属性,它首先检查该属性是否存在,如果存在,则读取其值并设置到 BeanDefinition 对象中。如果存在默认值,并且 XML 中没有提供特定值,则使用默认值。通过这种方式,Spring 容器能够根据配置文件创建和管理 bean。
2.9 总结
从读取 XML 配置文件到注册 BeanDefinition 的完整流程:
1.加载配置文件:
图中"创建上下文"步骤对应于实例化 ClassPathXmlApplicationContext,这时会传入 XML 文件路径。
ClassPathXmlApplicationContext 接受一个或多个 XML 文件路径作为构造参数。
2.初始化 BeanFactory 并进行刷新:
在图中"执行 refresh"步骤表示 refresh()方法被调用,这个方法会启动容器的初始化和刷新过程。
在 refresh()方法中初始化 BeanFactory,并准备对配置文件进行解析。
3.读取 XML 配置文件:
图中"加载 Bean 定义"步骤代表 XmlBeanDefinitionReader 的作用,它负责读取和加载 XML 配置文件。
XmlBeanDefinitionReader 负责读取传入的 XML 配置文件。
4.解析 XML 文件:
图中的"解析 XML"步骤表示 DefaultBeanDefinitionDocumentReader 处理 XML 文件,这包括解析顶层<beans>标签。
DefaultBeanDefinitionDocumentReader 开始处理 XML 文件,解析<beans>这样的顶层标签。
对于<bean>元素的解析,首先检查元素是否在默认命名空间。如果是,进行默认元素的解析;如果不是,默认命名空间之外的元素被认为是自定义元素,并交由 delegate.parseCustomElement(ele)处理。
5.Bean 定义的解析和注册:
图中的"注册 Bean 定义"、“处理别名”、“处理 Bean”和“处理导入”步骤对应于 BeanDefinitionParserDelegate 的各种解析活动,它涉及解析 bean 的 id、name、别名、属性、子元素等,以及将解析结果注册到 BeanDefinitionRegistry。
使用 BeanDefinitionParserDelegate 来解析<bean>元素的细节,包括 bean 的 id、name、别名等。
解析<bean>元素的属性,如 scope、lazy-init 等,并将这些值设置到 BeanDefinition 实例中。
如果<bean>元素包含子元素(如<property>或<constructor-arg>),它们也将被解析并以相应的元数据形式加入到 BeanDefinition 中。
生成的 BeanDefinition 将会注册到 BeanDefinitionRegistry 中,使用 BeanDefinitionReaderUtils.registerBeanDefinition 方法。
如果解析过程中发生任何错误,会通过 error 方法记录错误信息。
6.事件发布:
在注册 BeanDefinition 后,ApplicationContext 会发布一个组件注册事件,以通知相关的监听器。这个过程允许实现了 ApplicationListener 接口或使用 @EventListener 注解的组件接收到这个事件,并根据需要进行响应。例如,可以使用这个事件来触发某些自定义的逻辑,如额外的配置检查、启动某些后处理操作等。
这个详细流程显示了从加载配置文件到解析并注册 BeanDefinition 所涉及的复杂过程,它展示了 Spring 框架处理 Bean 声明和依赖关系的内部机制。这是 Spring 依赖注入核心功能的基础,确保了 Bean 能够按照定义被实例化和管理。
3. 源码阅读练习题
1. XML 配置文件解析:
解析 Spring 配置文件时,Spring 容器使用了哪些组件?
Spring 容器在解析配置文件时主要使用了 XmlBeanDefinitionReader 类。此外,还用到了 BeanDefinitionDocumentReader 来进行具体的文档读取。
BeanDefinitionReader 在配置文件解析中扮演什么角色?
BeanDefinitionReader 负责从 XML 文件读取 bean 定义并转换为 Spring 内部的 BeanDefinition 对象。
parseBeanDefinitionElement 方法是在什么时候被调用的?它的输出是什么?
parseBeanDefinitionElement 在 XML 元素被读取时调用,它的输出是 BeanDefinitionHolder 对象,其中包含了 bean 定义以及名称和别名。
2. Bean 定义解析:
描述一个 bean 定义从读取 XML 元素开始,到生成 BeanDefinition 对象的过程。
BeanDefinition 对象是通过读取 XML 中的 <bean> 元素并提取相关属性来创建的。这些属性包括 bean 的类名、作用域、生命周期回调等。
parseBeanDefinitionAttributes 方法在整个解析过程中的作用是什么?
parseBeanDefinitionAttributes 方法用于提取 bean 元素上的属性,并设置到 AbstractBeanDefinition 对象中。
哪些 XML 属性会被 parseBeanDefinitionAttributes 方法处理,并如何影响生成的 BeanDefinition 对象?
parseBeanDefinitionAttributes 方法处理的属性包括 scope、lazy-init、autowire 等,这些属性会决定 bean 的行为和它如何与其他 bean 交互。
3. Bean 名称与别名:
如果 XML 元素中没有提供 bean 的 id 或 name,Spring 是如何处理的?
如果没有提供 id 或 name,Spring 会自动生成一个唯一的 bean 名称。它可能基于类名加上一定的序列号。提示:分析 parseBeanDefinitionElement 方法时有说过。
别名(alias)在 Spring 中有何用途?在 parseBeanDefinitionElement 方法中,别名是如何被处理的?
别名可以为 bean 提供额外的名称,这在需要引用相同的 bean 但在不同上下文中使用不同名称时很有用。在 parseBeanDefinitionElement 方法中,别名是通过解析 name 属性并以逗号、分号或空格作为分隔符来处理的。
4. Bean 作用域与生命周期属性:
如何定义一个 bean 的作用域(scope)?singleton 和 prototype 有什么不同?
通过设置 <bean> 元素的 scope 属性定义 bean 的作用域。singleton 表示全局唯一实例,而 prototype 表示每次请求都创建一个新的实例。
lazy-init、init-method 和 destroy-method 这些属性对 bean 的生命周期有什么影响?
lazy-init 属性确定 bean 是否应该在启动时延迟初始化,init-method 和 destroy-method 定义了 bean 的初始化和销毁时调用的方法。
5. Bean 注册:
一旦 BeanDefinition 对象被创建,Spring 是如何将其注册到容器中的?
BeanDefinition 对象在解析后,通过 DefaultListableBeanFactory.registerBeanDefinition 方法注册到 Spring 容器中。
注册过程中,如果发现 bean 名称冲突,Spring 会如何处理?
如果发现名称冲突,会抛出 BeanDefinitionStoreException。如果是在不同的配置文件中定义相同名称的 bean,后者通常会覆盖前者。
6. 异常处理:
当 XML 配置不正确或使用了不合法的属性时,Spring 是如何反馈给用户的?
Spring 会通过抛出 BeanDefinitionStoreException 来告知用户配置错误。异常信息会详细说明错误的原因和位置。
分析 Spring 中的错误处理机制,它对于开发者调试配置有何帮助?
Spring 的错误处理机制包括异常的详细信息和精确的定位,这对于开发者快速识别配置错误非常有帮助。
4. 常见疑问
4.1 在 refresh 过程中,Bean 的生命周期是怎样的?每个 Bean 的状态是如何被管理的?
1.实例化 BeanFactory:
在 refresh 方法开始时,Spring 会实例化一个新的 BeanFactory,通常是 DefaultListableBeanFactory,作为容器用于创建 Bean 实例。
2.加载 Bean 定义:
然后,refresh 调用 loadBeanDefinitions 来加载和注册 Bean 的定义。这些定义可以来源于 XML 配置文件、Java 配置类或者扫描的注解。
3.BeanFactoryPostProcessor 的执行:
在所有 Bean 定义加载完成之后,但在 Bean 实例化之前,Spring 会调用 BeanFactoryPostProcessor。这些处理器可以对 Bean 定义(配置元数据)进行修改。
4.BeanPostProcessor 的注册:
接下来,Spring 注册 BeanPostProcessor 实例。这些处理器可以对 Bean 的实例(创建和初始化后的对象)进行修改。
5.单例 Bean 的预实例化:
随后,Spring 会预实例化单例 Bean。对于单例作用域的 Bean,Spring 会创建并配置这些 Bean,然后将它们放入缓存中。
6.依赖注入:
在 Bean 实例化后,Spring 会进行依赖注入。此时,Bean 的属性将被设置,相关的依赖将被注入。
7.Bean 初始化:
之后,Bean 将被初始化。如果 Bean 实现了 InitializingBean 接口,afterPropertiesSet 方法会被调用;或者如果定义了 init-method,指定的方法也会被调用。
8.Aware 接口的调用:
如果 Bean 实现了任何 Aware 接口,如 ApplicationContextAware 或 BeanNameAware,它们将在初始化之前被调用。
9.BeanPostProcessor 的后处理:
BeanPostProcessor 的前置处理(postProcessBeforeInitialization)和后置处理(postProcessAfterInitialization)方法在 Bean 初始化之前和之后被调用,它们可以进一步定制 Bean。
10.事件发布:
一旦所有单例 Bean 都被初始化,Spring 会发布 ContextRefreshedEvent,表明 ApplicationContext 已被刷新。
11.使用 Bean:
此时,所有的 Bean 都准备就绪,并可以用于应用程序的其他部分。
12.关闭容器:
当应用上下文被关闭时,如果 Bean 实现了 DisposableBean 接口,destroy 方法会被调用;或者定义了 destroy-method 方法,它也会被执行来清理资源。
在整个生命周期过程中,每个 Bean 的状态被 ApplicationContext 和 BeanFactory 跟踪和管理,从创建、依赖注入、初始化,到销毁,确保 Bean 在正确的时机被创建和清理。
4.2 refresh 方法是自动触发的吗?如果不是,那么是什么条件下需要手动触发?
在 Spring 中的 refresh 方法:
1. 何时触发:
自动触发: 在初始化 ApplicationContext 的时候,比如在应用程序中使用 new ClassPathXmlApplicationContext("config.xml"),Spring 容器启动过程中会自动调用 refresh 方法。
手动触发: 如果在应用程序运行时需要重新加载配置(可能是修改了配置文件),可以手动调用 refresh 方法来实现。但这通常在开发或测试阶段用于特殊场景,因为它会导致整个应用上下文重建,包括所有的 Bean 对象。
2. 为什么需要手动触发:
通常情况下,Spring 容器在启动时只需要加载一次配置,初始化一次每个 Bean。除非有特殊需求,例如动态调整日志级别,重新加载配置文件中的特定 Bean,否则不需要手动触发。
在 Spring Boot 中的 refresh 方法:
Spring Boot 大大简化了 Spring 应用的配置和启动过程。它自动配置了 Spring 的 ApplicationContext 并在合适的时候调用了 refresh 方法。
1. 自动触发:
当使用 Spring Boot 的 SpringApplication.run()方法启动应用时,Spring Boot 会自动创建 ApplicationContext,并在内部调用 refresh 方法。这个过程是自动的,开发者通常不需要关心。
2. 可能的手动触发场景:
Spring Boot 提供了 actuator 模块,其中/refresh 端点可以用来重新加载配置(通常是与 Spring Cloud Config 结合使用)。这不是传统意义上的调用 ApplicationContext 的 refresh 方法,而是一种触发重新加载部分配置的机制,特别是标注了 @RefreshScope 的 Bean,它们可以在不重新启动整个应用的情况下更新。
一般情况下的建议:
对于开发者来说,不应该在生产环境中随意手动调用 refresh 方法。因为这会导致整个应用的重新加载,影响性能并可能导致服务中断。
如果需要动态更新配置,应当使用 Spring Cloud Config 和 Spring Boot Actuator 的/refresh 端点,这是一种更加安全和控制的方式来更新配置。
4.3 在 Spring Boot 中,refresh 方法的行为是否有所不同?Spring Boot 是否提供了更优的方法来处理应用上下文的变化?
在 Spring Boot 中,refresh 方法的基本行为保持不变,因为 Spring Boot 建立在 Spring 之上,遵循相同的基本原则。不过,Spring Boot 确实为应用上下文的管理和刷新提供了更多的自动化和便利性:
1.自动配置:
Spring Boot 特有的自动配置特性减少了需要手动刷新的场景。在启动时,它会自动装配 Bean,通常不需要显式调用 refresh。
2.外部化配置:
Spring Boot 支持强大的外部化配置机制,允许通过配置文件、环境变量等方式来注入配置。这使得改变配置而不需要重新刷新上下文成为可能。
3.条件刷新:
Spring Boot 使用条件注解(如 @ConditionalOnClass、@ConditionalOnBean 等),这允许上下文根据环境或者特定条件动态调整其配置,减少了需要手动触发 refresh 的场景。
4.生命周期管理:
通过 SpringApplication 类,Spring Boot 为应用生命周期提供了额外的管理能力。它处理了许多在传统 Spring 应用中需要手动完成的任务,如初始化和刷新应用上下文。
5.Actuator endpoints:
对于运行中的应用,Spring Boot Actuator 提供了一系列管理和监控的端点,其中一些可以用来刷新配置(如/refresh 端点)或者重启上下文(如/restart 端点),这在某些情况下可以替代完整的应用重启。
6.配置更改监听:
使用 Spring Cloud Config 的应用可以在配置变化时自动刷新上下文。在配置服务器上的变化可以被监听,并且可以触发客户端上下文的自动刷新,而不需要手动干预。
7.错误处理:
Spring Boot 有一套默认的错误处理机制,特别是在 Web 应用程序中,它会提供默认的错误页面和/error 端点。此外,开发者可以定制错误处理,以适应具体需求。
综上所述,Spring Boot 提供了更为自动化的方式来处理应用上下文的变化,很多时候无需手动调用 refresh 方法。不过,如果需要在运行时动态改变 Bean 的配置,并希望这些改变立即生效,那么可能还需要使用 Spring 提供的 refresh 方法或通过 Spring Boot Actuator 的相关端点来达成这一目的。
版权声明: 本文为 InfoQ 作者【华为云开发者联盟】的原创文章。
原文链接:【http://xie.infoq.cn/article/e5f83ef7430ce3a7366b1757a】。文章转载请联系作者。
评论