本文同享自华为云社区《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的代码示例
先给出最简单的代码示例,然后逐渐剖析
全部代码如下:
package com.example.demo.bean;
// HelloWorld.java
public class HelloWorld {
private String message;
public void setMessage(String message) {
this.message = message;
}
public void sayHello() {
System.out.println("Hello, " message "!");
}
}
主程序:
package com.example.demo;
import com.example.demo.bean.HelloWorld;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class DemoApplication {
public static void main(String[] args) {
// 创立Spring上下文(容器)
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("ApplicationContext.xml");
// 从容器中获取bean,假设咱们有一个名为 'helloWorld' 的bean
HelloWorld helloWorld = context.getBean("helloWorld", HelloWorld.class);
// 运用bean
helloWorld.sayHello();
// 封闭上下文
context.close();
}
}
xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- Bean界说 -->
<bean id="helloWorld" class="com.example.demo.bean.HelloWorld">
<!-- 设置特点 -->
<property name="message" value="World"/>
</bean>
</beans>
运转成果:
接着咱们就从这段代码开端剖析
2.2 setConfigLocations – 设置和保存装备文件途径
咱们还是以Spring 5.3.7
的源码为例剖析
// 创立Spring上下文(容器)
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("ApplicationContext.xml");
这段代码,咱们利用idea
点击去剖析,终究在ClassPathXmlApplicationContext
的重载办法里看到调用了setConfigLocations
设置装备文件的途径。
接着看看setConfigLocations
办法
setConfigLocations()
办法的首要效果是设定 Spring
容器加载 Bean
界说时所需求读取的装备文件途径。这些途径能够是类途径下的资源、文件体系中的资源或许其他任何经过URL
定位的资源。该办法保证一切供给的装备途径都被保存并在稍后的容器改写操作中运用。
源码提出来剖析:
public void setConfigLocations(@Nullable String... locations) {
if (locations != null) {
// 运用Spring的Assert类来校验,保证传入的装备方位数组中没有null元素。
Assert.noNullElements(locations, "Config locations must not be null");
// 依据传入的装备方位数量,初始化内部存储装备方位的数组。
this.configLocations = new String[locations.length];
// 遍历传入的装备方位数组。
for(int i = 0; i < locations.length; i) {
// 调用resolvePath办法处理每一个装备方位(或许进行必要的途径解析,如解析占位符)。
// trim()用于移除字符串首尾的空格,保证保存的途径是净化的。
this.configLocations[i] = this.resolvePath(locations[i]).trim();
}
} else {
// 假如传入的装备方位是null,清除掉一切已设定的装备方位。
this.configLocations = null;
}
}
在上下文被改写的时分,这些装备文件方位会被读取,而且Spring
容器将解析其间界说的beans
并将它们注册到容器中。setConfigLocations()
办法只是设置了这些方位,而实践的加载和注册进程是在上下文改写时完结的。
这个setConfigLocations
办法一般不是由用户直接调用的,而是在ApplicationContext
初始化的进程中被结构调用,例如在根据XML
的装备中,咱们会在初始化ClassPathXmlApplicationContext
或FileSystemXmlApplicationContext
时供给装备文件的途径。
在debug
的时分,能够看到把测验代码中设置的 xml
装备文件的途径保存了。
2.3 refresh – 触发容器改写,装备文件的加载与解析
咱们上面看到ClassPathXmlApplicationContext
办法里边,履行完setConfigLocations
后,紧接着有个refresh
办法,咱们来看看。
在Spring
结构中,refresh()
办法是十分要害的,它是ApplicationContext
接口的一部分。这个办法的首要功用是改写运用上下文,加载或许从头加载装备文件中界说的Bean
,初始化一切的单例,装备消息资源,事情发布器等。
代码提出来剖析:
public void refresh() throws BeansException, IllegalStateException {
// 同步块,保证容器改写进程的线程安全
synchronized(this.startupShutdownMonitor) {
// 开端上下文改写的进程记载,用于监控和诊断
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
// 准备改写进程,设置开端时间,状况标志等
this.prepareRefresh();
// 获取新的BeanFactory,假如是第一次改写则创立一个BeanFactory
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
// 装备BeanFactory,注册忽略的依靠接口等
this.prepareBeanFactory(beanFactory);
try {
// 答应BeanFactory的后置处理器对其进行修正
this.postProcessBeanFactory(beanFactory);
// 开端Bean工厂的后置处理进程的监控
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// 调用BeanFactoryPostProcessors
this.invokeBeanFactoryPostProcessors(beanFactory);
// 注册BeanPostProcessors到BeanFactory
this.registerBeanPostProcessors(beanFactory);
// Bean后置处理进程完毕
beanPostProcess.end();
// 初始化MessageSource组件,用于国际化等功用
this.initMessageSource();
// 初始化事情播送器
this.initApplicationEventMulticaster();
// 留给子类掩盖的定制办法
this.onRefresh();
// 注册监听器
this.registerListeners();
// 初始化剩下的单例Bean
this.finishBeanFactoryInitialization(beanFactory);
// 完结改写进程,告诉生命周期处理器lifecycleProcessor改写进程,发布ContextRefreshedEvent事情
this.finishRefresh();
} catch (BeansException var10) {
// 捕获BeansException,记载警告信息,毁掉已创立的Bean
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " var10);
}
// 毁掉现已初始化的单例Bean
this.destroyBeans();
// 撤销改写,重置同步监视器上的标志位
this.cancelRefresh(var10);
// 抛出反常,完毕改写进程
throw var10;
} finally {
// 在改写的终究,重置Spring内核中的同享缓存
this.resetCommonCaches();
// 完毕上下文改写进程的记载
contextRefresh.end();
}
}
}
这个办法准确履行一系列进程来装备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
。
代码提出来剖析:
protected final void refreshBeanFactory() throws BeansException {
// 查看当时运用上下文是否现已包含了一个BeanFactory
if (this.hasBeanFactory()) {
// 假如现已存在BeanFactory,毁掉它办理的一切bean
this.destroyBeans();
// 封闭现有的BeanFactory,开释其或许持有的任何资源
this.closeBeanFactory();
}
try {
// 创立一个DefaultListableBeanFactory的新实例,这是Spring中ConfigurableListableBeanFactory接口的默许完结
DefaultListableBeanFactory beanFactory = this.createBeanFactory();
// 为beanFactory设置一个序列化ID,这个ID后面能够用于反序列化
beanFactory.setSerializationId(this.getId());
// 答应子类定制新创立的beanFactory
this.customizeBeanFactory(beanFactory);
// 从底层资源(例如XML文件)中加载bean界说到beanFactory
this.loadBeanDefinitions(beanFactory);
// 将新的beanFactory赋值给这个上下文的beanFactory特点
this.beanFactory = beanFactory;
} catch (IOException var2) {
// 假如在解析bean界说资源进程中产生I/O反常,将其包装并从头抛出为ApplicationContextException
throw new ApplicationContextException("I/O过错解析用于" this.getDisplayName() "的bean界说源", var2);
}
}
这个办法在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
界说的要害进程。
代码提出来剖析:
// 运用DefaultListableBeanFactory作为Bean界说注册的方针工厂,加载Bean界说
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// 创立一个读取XML Bean界说的读取器,并将工厂传入用于注册界说
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// 设置环境方针,或许包含特点解析相关的环境装备
beanDefinitionReader.setEnvironment(this.getEnvironment());
// 设置资源加载器,答应读取器加载XML资源
beanDefinitionReader.setResourceLoader(this);
// 设置实体解析器,用于解析XML中的实体如DTD
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// 初始化Bean界说读取器,或许设置一些参数,如是否验证XML
this.initBeanDefinitionReader(beanDefinitionReader);
// 调用重载的loadBeanDefinitions,依据装备的资源和方位加载Bean界说
this.loadBeanDefinitions(beanDefinitionReader);
}
// 初始化Bean界说读取器,首要设置是否进行XML验证
protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader) {
// 设置XML验证形式,一般取决于运用上下文的装备
reader.setValidating(this.validating);
}
// 经过XmlBeanDefinitionReader加载Bean界说
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
// 获取一切装备资源的数组(如XML装备文件)
Resource[] configResources = this.getConfigResources();
// 假如装备资源非空,则加载这些资源
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
// 获取一切装备文件方位的数组
String[] configLocations = this.getConfigLocations();
// 假如装备文件方位非空,则加载这些方位指定的装备文件
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}
在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
代码提出来剖析:
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
// 将Resource包装为EncodedResource,答应指定编码,然后持续加载Bean界说
return this.loadBeanDefinitions(new EncodedResource(resource));
}
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
// 断语传入的EncodedResource不为空
Assert.notNull(encodedResource, "EncodedResource must not be null");
// 假如日志等级为trace,则输出盯梢日志
if (this.logger.isTraceEnabled()) {
this.logger.trace("Loading XML bean definitions from " encodedResource);
}
// 获取当时线程正在加载的资源调集
Set<EncodedResource> currentResources = (Set)this.resourcesCurrentlyBeingLoaded.get();
// 查看资源是否现已在加载中,假如是,则抛出BeanDefinitionStoreException反常,防止循环加载
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException("Detected cyclic loading of " encodedResource " - check your import definitions!");
} else {
int var6; // 这将用来存储加载的Bean界说数量
try {
// 翻开资源的InputStream进行读取
InputStream inputStream = encodedResource.getResource().getInputStream();
Throwable var4 = null;
try {
// 将InputStream封装为InputSource,XML解析器能够接受这个类型
InputSource inputSource = new InputSource(inputStream);
// 假如资源编码不为空,设置资源的编码
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// 实践加载Bean界说的办法,回来加载的Bean界说数量
var6 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
} catch (Throwable var24) {
// 捕获Throwable以便在finally块中处理资源开释
var4 = var24;
throw var24;
} finally {
// 封闭InputStream资源
if (inputStream != null) {
if (var4 != null) {
try {
inputStream.close();
} catch (Throwable var23) {
// 添加被按捺的反常
var4.addSuppressed(var23);
}
} else {
inputStream.close();
}
}
}
} catch (IOException var26) {
// 抛出IOException反常,假如解析XML文档失败
throw new BeanDefinitionStoreException("IOException parsing XML document from " encodedResource.getResource(), var26);
} finally {
// 从当时加载的资源调集中移除该资源
currentResources.remove(encodedResource);
// 假如当时加载的资源调集为空,则从ThreadLocal中移除
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
// 回来加载的Bean界说数量
return var6;
}
}
在这段代码中,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
传递给这个办法。
代码提出来剖析:
protected void doRegisterBeanDefinitions(Element root) {
// 保存旧的解析署理(delegate),以便之后能够康复
BeanDefinitionParserDelegate parent = this.delegate;
// 创立新的解析署理(delegate),用于处理当时XML根节点的解析
this.delegate = this.createDelegate(this.getReaderContext(), root, parent);
// 假如当时节点运用的是Spring默许的XML命名空间
if (this.delegate.isDefaultNamespace(root)) {
// 获取根节点的"profile"特点
String profileSpec = root.getAttribute("profile");
// 查看"profile"特点是否有文本内容
if (StringUtils.hasText(profileSpec)) {
// 按逗号、分号和空格分隔"profile"特点值,得到指定的profiles数组
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
// 假如当时环境不接受任何指定的profiles,则不加载该Bean界说文件
if (!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
// 假如日志等级是DEBUG,则记载跳过文件的信息
if (this.logger.isDebugEnabled()) {
this.logger.debug("Skipped XML bean definition file due to specified profiles [" profileSpec "] not matching: " this.getReaderContext().getResource());
}
// 退出办法,不进行后续处理
return;
}
}
}
// 在解析XML前进行预处理,可被重写的办法
this.preProcessXml(root);
// 解析XML根节点下的Bean界说
this.parseBeanDefinitions(root, this.delegate);
// 在解析XML后进行后处理,可被重写的办法
this.postProcessXml(root);
// 康复旧的解析署理(delegate)
this.delegate = parent;
}
上述代码片段是Spring
结构用于注册Bean
界说的内部办法。该办法在解析XML
装备文件并注册Bean
界说到Spring
容器时被调用。它包含处理profile
特点以依据运转时环境决议是否加载特定Bean
界说的逻辑,以及前后处理钩子,答应在解析前后进行自界说操作。终究,它保证解析署理(delegate
)被重置为之前的状况,以维护正确的状况。
接着,咱们要看看是怎么解析xml
的,要点关注下parseBeanDefinitions
办法
2.7 parseBeanDefinitions – 解析XML中的BeanDefinition元素
parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)
办法的首要意图是遍历 XML
装备文件的根节点,解析并注册其间界说的一切 Bean
。该办法担任区分不同类型的元素,即默许命名空间下的规范元素和自界说命名空间下的自界说元素,并对它们进行相应的处理。
代码提出来剖析:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
// 判别根节点是否运用的是Spring的默许命名空间
if (delegate.isDefaultNamespace(root)) {
// 获取一切子节点
NodeList nl = root.getChildNodes();
// 遍历一切子节点
for (int i = 0; i < nl.getLength(); i) {
Node node = nl.item(i);
// 只处理Element类型的节点(过滤掉文本节点等其他类型)
if (node instanceof Element) {
Element ele = (Element)node;
// 假如子元素节点也是默许命名空间,则调用parseDefaultElement办法解析
if (delegate.isDefaultNamespace(ele)) {
this.parseDefaultElement(ele, delegate);
} else {
// 假如子元素节点不是默许命名空间,则调用parseCustomElement办法解析
// 这一般标明节点界说了自界说的行为,或许是用户自界说的标签或许是Spring扩展的标签
delegate.parseCustomElement(ele);
}
}
}
} else {
// 假如根节点不是默许命名空间,那么它或许是一个自界说标签的顶级元素
// 在这种情况下,直接调用parseCustomElement进行解析
delegate.parseCustomElement(root);
}
}
这段代码的效果是解析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
特点声明。
代码提出来剖析:
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
// 判别当时元素节点称号是否是"import"
if (delegate.nodeNameEquals(ele, "import")) {
// 假如是"import",则导入其他装备文件
this.importBeanDefinitionResource(ele);
} else if (delegate.nodeNameEquals(ele, "alias")) {
// 假如节点是"alias",则处理别号界说,为一个bean界说一个或多个别号
this.processAliasRegistration(ele);
} else if (delegate.nodeNameEquals(ele, "bean")) {
// 假如节点是"bean",则处理bean界说,这是界说Spring bean的中心元素
this.processBeanDefinition(ele, delegate);
} else if (delegate.nodeNameEquals(ele, "beans")) {
// 假如节点是"beans",意味着有嵌套的beans界说,需求递归地注册其间的bean界说
this.doRegisterBeanDefinitions(ele);
}
}
这段代码的功用是依据元素的称号来决议对XML
装备文件中的不同标签进行不同的处理操作。它处理Spring
结构默许命名空间下的四种首要标签:
-
<import>
:导入其他Spring XML
装备文件到当时的装备文件中。 -
<alias>
:为一个现已界说的bean
供给一个或多个别号。 -
<bean>
:界说一个Spring
办理的bean
,是最常用的元素,包含了bean
的详细装备。 -
<beans>
:界说一个beans
的调集,一般是装备文件中的顶层元素,但也能够是嵌套界说,标明一个新的效果域或许上下文。
这样,Spring
能够依据这些元从来构建运用上下文中的bean
工厂。
调试能够发现,xml
现已解分出开端的雏形了
在这儿好像没看到bean
元素,这是怎样解析的呢?让咱们一步一步来,在上面提到的parseDefaultElement
办法中有调用processBeanDefinition
办法,来看看这是干嘛的。
2.8 processBeanDefinition – 对
标签进行详细解析和处理
processBeanDefinition
办法是 Spring
结构中用于处理 <bean> XML
装备元素的办法。其意图是将 <bean>
元素中描述的信息转换为 Spring
内部运用的BeanDefinition
方针,并将其注册到 Spring IoC
容器中。这是 Spring bean
生命周期中的一个要害进程,由于在这儿界说的 bean
会在容器发动时被实例化和办理
代码提出来剖析:
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
// 运用代了解析bean界说元素,这触及将XML界说的<bean>元素转换成Spring的BeanDefinitionHolder方针,
// 该方针包含了bean界说和称号。
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
// 查看解析是否回来了BeanDefinitionHolder方针。
if (bdHolder != null) {
// 如有需求,对bean界说进行装饰。这或许触及运用任何额定的特点或嵌套元素,
// 这些都是bean界说的一部分,但不是规范<bean> XML装备的一部分。
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// 在注册中心注册bean界说。注册中心一般是持有一切bean界说的Spring IoC容器。
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.getReaderContext().getRegistry());
} catch (BeanDefinitionStoreException var5) {
// 假如在bean注册进程中出现反常,陈述过错上下文并抛出反常。
// 过错上下文包含bean的称号和引起问题的XML元素。
this.getReaderContext().error("Failed to register bean definition with name '" bdHolder.getBeanName() "'", ele, var5);
}
// 在成功注册后,告诉任何监听器一个新的bean界说已被注册。
// 这是Spring事情机制的一部分,答应对容器内的特定动作作出呼应。
this.getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
// 注意:假如bdHolder为空,则意味着bean界说元素没有被正确解析
// 或许它不是要被注册的(例如,在笼统界说的情况下)。
// 因此,在这种情况下,该办法不履行任何操作。
}
该办法一般在Spring
结构的bean
界说解析进程中运用,它处理根据供给的XML
元素创立和注册bean
界说的逻辑。BeanDefinitionParserDelegate
是一个协助类,担任处了解析特定Spring XML
结构的细节。
debug
这个类的时分,发现现已解分出这个bean
的class
和id
了
有人会好奇了,这是怎么将 xml
元素封装为 BeanDefinitionHolder
呢
parseBeanDefinitionElement
办法是用来解析 Spring
装备文件中 <bean>
元素的界说,并生成对应的 BeanDefinitionHolder
方针。BeanDefinitionHolder
是一个包装类,它封装了 BeanDefinition
实例和该界说的称号(即bean
的id
)以及别号(假如有的话)。
代码提出来剖析:
@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
// 调用重载办法parseBeanDefinitionElement,并将BeanDefinition设置为null
return this.parseBeanDefinitionElement(ele, (BeanDefinition)null);
}
@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
// 获取元素的id特点
String id = ele.getAttribute("id");
// 获取元素的name特点
String nameAttr = ele.getAttribute("name");
// 创立别号列表
List<String> aliases = new ArrayList();
if (StringUtils.hasLength(nameAttr)) {
// 假如name特点非空,则运用分隔符切割name字符串,并将成果添加到别号列表
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, ",; ");
aliases.addAll(Arrays.asList(nameArr));
}
// 默许情况下bean的称号运用id特点的值
String beanName = id;
if (!StringUtils.hasText(id) && !aliases.isEmpty()) {
// 假如id为空且别号列表非空,则运用别号列表中的第一个作为bean称号,并从列表中移除它
beanName = aliases.remove(0);
if (this.logger.isTraceEnabled()) {
this.logger.trace("No XML 'id' specified - using '" beanName "' as bean name and " aliases " as aliases");
}
}
if (containingBean == null) {
// 假如不是嵌套bean界说,则查看bean称号和别号的仅有性
this.checkNameUniqueness(beanName, aliases, ele);
}
// 解析bean界说元素,回来AbstractBeanDefinition方针
AbstractBeanDefinition beanDefinition = this.parseBeanDefinitionElement(ele, beanName, containingBean);
if (beanDefinition != null) {
if (!StringUtils.hasText(beanName)) {
// 假如bean称号为空,则测验生成bean称号
try {
if (containingBean != null) {
// 假如是内部bean,则运用特定的生成战略
beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true);
} else {
// 不然运用默许战略
beanName = this.readerContext.generateBeanName(beanDefinition);
String beanClassName = beanDefinition.getBeanClassName();
if (beanClassName != null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() && !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
// 假如bean类名不为空,且生成的bean称号以类名最初,且未被运用,则将类名添加到别号列表
aliases.add(beanClassName);
}
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Neither XML 'id' nor 'name' specified - using generated bean name [" beanName "]");
}
} catch (Exception var9) {
// 在称号生成进程中捕获反常,并记载过错
this.error(var9.getMessage(), ele);
return null;
}
}
// 将别号列表转换为数组
String[] aliasesArray = StringUtils.toStringArray(aliases);
// 创立并回来BeanDefinitionHolder方针
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
} else {
// 假如bean界说为空,则回来null
return null;
}
}
这段代码担任解析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
)。
直接提出代码剖析:
public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName, @Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) {
// 查看是否运用了已抛弃的singleton特点,假如存在,则报错提示应该升级到scope特点
if (ele.hasAttribute("singleton")) {
this.error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
// 假如存在scope特点,则设置bean的效果域
} else if (ele.hasAttribute("scope")) {
bd.setScope(ele.getAttribute("scope"));
// 假如没有设置scope特点可是有包含bean,则设置为包含bean的效果域
} else if (containingBean != null) {
bd.setScope(containingBean.getScope());
}
// 假如设置了abstract特点,依据该特点的值设置bean界说是否为笼统
if (ele.hasAttribute("abstract")) {
bd.setAbstract("true".equals(ele.getAttribute("abstract")));
}
// 解析lazy-init特点,默许运用装备的默许值,假如设置了则掩盖
String lazyInit = ele.getAttribute("lazy-init");
if (this.isDefaultValue(lazyInit)) {
lazyInit = this.defaults.getLazyInit();
}
bd.setLazyInit("true".equals(lazyInit));
// 解析autowire特点,将字符串值转换为相应的主动装配形式
String autowire = ele.getAttribute("autowire");
bd.setAutowireMode(this.getAutowireMode(autowire));
// 解析depends-on特点,将字符串值转换为数组,并设置为bean界说的依靠
if (ele.hasAttribute("depends-on")) {
String dependsOn = ele.getAttribute("depends-on");
bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, ",; "));
}
// 解析autowire-candidate特点,设置bean是否可作为主动装配的候选者
String autowireCandidate = ele.getAttribute("autowire-candidate");
if (this.isDefaultValue(autowireCandidate)) {
String defaultValue = this.defaults.getAutowireCandidates();
if (defaultValue != null) {
String[] patterns = StringUtils.commaDelimitedListToStringArray(defaultValue);
bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));
}
} else {
bd.setAutowireCandidate("true".equals(autowireCandidate));
}
// 解析primary特点,设置bean是否为primary
if (ele.hasAttribute("primary")) {
bd.setPrimary("true".equals(ele.getAttribute("primary")));
}
// 解析init-method特点,设置bean的初始化办法
String initMethodName = ele.getAttribute("init-method");
if (ele.hasAttribute("init-method")) {
bd.setInitMethodName(initMethodName);
// 假如没有设置可是有默许值,则运用默许值
} else if (this.defaults.getInitMethod() != null) {
bd.setInitMethodName(this.defaults.getInitMethod());
bd.setEnforceInitMethod(false);
}
// 解析destroy-method特点,设置bean的毁掉办法
String destroyMethodName = ele.getAttribute("destroy-method");
if (ele.hasAttribute("destroy-method")) {
bd.setDestroyMethodName(destroyMethodName);
// 假如没有设置可是有默许值,则运用默许值
} else if (this.defaults.getDestroyMethod() != null) {
bd.setDestroyMethodName(this.defaults.getDestroyMethod());
bd.setEnforceDestroyMethod(false);
}
// 解析factory-method特点,设置bean的工厂办法
if (ele.hasAttribute("factory-method")) {
bd.setFactoryMethodName(ele.getAttribute("factory-method"));
}
// 解析factory-bean特点,设置bean的工厂bean名
if (ele.hasAttribute("factory-bean")) {
bd.setFactoryBeanName(ele.getAttribute("factory-bean"));
}
// 回来装备好的bean界说
return bd;
}
这段代码的中心功用是将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
的相关端点来达到这一意图。