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
的完整流程:
加载配置文件:
图中"创建上下文"步骤对应于实例化
ClassPathXmlApplicationContext
,这时会传入XML
文件路径。ClassPathXmlApplicationContext
接受一个或多个XML
文件路径作为构造参数。
初始化 BeanFactory 并进行刷新:
在图中"执行
refresh
"步骤表示refresh()
方法被调用,这个方法会启动容器的初始化和刷新过程。在
refresh()
方法中初始化BeanFactory
,并准备对配置文件进行解析。
读取 XML 配置文件:
图中"加载
Bean
定义"步骤代表XmlBeanDefinitionReader
的作用,它负责读取和加载XML
配置文件。XmlBeanDefinitionReader
负责读取传入的XML
配置文件。
解析 XML 文件:
图中的"解析
XML
"步骤表示DefaultBeanDefinitionDocumentReader
处理XML
文件,这包括解析顶层<beans>
标签。DefaultBeanDefinitionDocumentReader
开始处理XML
文件,解析<beans>
这样的顶层标签。对于
<bean>
元素的解析,首先检查元素是否在默认命名空间。如果是,进行默认元素的解析;如果不是,默认命名空间之外的元素被认为是自定义元素,并交由delegate.parseCustomElement(ele)
处理。
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
方法记录错误信息。
事件发布:
在注册
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 的状态是如何被管理的?
实例化 BeanFactory:
在
refresh
方法开始时,Spring
会实例化一个新的BeanFactory
,通常是DefaultListableBeanFactory
,作为容器用于创建Bean
实例。
加载 Bean 定义:
然后,
refresh
调用loadBeanDefinitions
来加载和注册Bean
的定义。这些定义可以来源于XML
配置文件、Java
配置类或者扫描的注解。
BeanFactoryPostProcessor 的执行:
在所有
Bean
定义加载完成之后,但在Bean
实例化之前,Spring
会调用BeanFactoryPostProcessor
。这些处理器可以对Bean
定义(配置元数据)进行修改。
BeanPostProcessor 的注册:
接下来,
Spring
注册BeanPostProcessor
实例。这些处理器可以对Bean
的实例(创建和初始化后的对象)进行修改。
单例 Bean 的预实例化:
随后,
Spring
会预实例化单例Bean
。对于单例作用域的Bean
,Spring
会创建并配置这些Bean
,然后将它们放入缓存中。
依赖注入:
在
Bean
实例化后,Spring
会进行依赖注入。此时,Bean
的属性将被设置,相关的依赖将被注入。
Bean 初始化:
之后,
Bean
将被初始化。如果Bean
实现了InitializingBean
接口,afterPropertiesSet
方法会被调用;或者如果定义了init-method
,指定的方法也会被调用。
Aware 接口的调用:
如果
Bean
实现了任何Aware
接口,如ApplicationContextAware
或BeanNameAware
,它们将在初始化之前被调用。
BeanPostProcessor 的后处理:
BeanPostProcessor
的前置处理(postProcessBeforeInitialization
)和后置处理(postProcessAfterInitialization
)方法在Bean
初始化之前和之后被调用,它们可以进一步定制Bean
。
事件发布:
一旦所有单例
Bean
都被初始化,Spring
会发布ContextRefreshedEvent
,表明ApplicationContext
已被刷新。
使用 Bean:
此时,所有的
Bean
都准备就绪,并可以用于应用程序的其他部分。
关闭容器:
当应用上下文被关闭时,如果
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
确实为应用上下文的管理和刷新提供了更多的自动化和便利性:
自动配置:
Spring Boot
特有的自动配置特性减少了需要手动刷新的场景。在启动时,它会自动装配Bean
,通常不需要显式调用refresh
。
外部化配置:
Spring Boot
支持强大的外部化配置机制,允许通过配置文件、环境变量等方式来注入配置。这使得改变配置而不需要重新刷新上下文成为可能。
条件刷新:
Spring Boot
使用条件注解(如@ConditionalOnClass
、@ConditionalOnBean
等),这允许上下文根据环境或者特定条件动态调整其配置,减少了需要手动触发refresh
的场景。
生命周期管理:
通过
SpringApplication
类,Spring Boot
为应用生命周期提供了额外的管理能力。它处理了许多在传统Spring
应用中需要手动完成的任务,如初始化和刷新应用上下文。
Actuator endpoints:
对于运行中的应用,
Spring Boot Actuator
提供了一系列管理和监控的端点,其中一些可以用来刷新配置(如/refresh
端点)或者重启上下文(如/restart
端点),这在某些情况下可以替代完整的应用重启。
配置更改监听:
使用
Spring Cloud Config
的应用可以在配置变化时自动刷新上下文。在配置服务器上的变化可以被监听,并且可以触发客户端上下文的自动刷新,而不需要手动干预。
错误处理:
Spring Boot
有一套默认的错误处理机制,特别是在Web
应用程序中,它会提供默认的错误页面和/error
端点。此外,开发者可以定制错误处理,以适应具体需求。
综上所述,Spring Boot
提供了更为自动化的方式来处理应用上下文的变化,很多时候无需手动调用refresh
方法。不过,如果需要在运行时动态改变Bean
的配置,并希望这些改变立即生效,那么可能还需要使用Spring
提供的refresh
方法或通过Spring Boot Actuator
的相关端点来达成这一目的。
欢迎一键三连~
有问题请留言,大家一起探讨学习
----------------------Talk is cheap, show me the code-----------------------
版权声明: 本文为 InfoQ 作者【砖业洋__】的原创文章。
原文链接:【http://xie.infoq.cn/article/c1023736b5c9e1b08dab4e176】。文章转载请联系作者。
评论