ToB企服应用市场:ToB评测及商务社交产业平台

标题: 万字解析XML配置映射为BeanDefinition的源码 [打印本页]

作者: 刘俊凯    时间: 2023-12-19 17:01
标题: 万字解析XML配置映射为BeanDefinition的源码
本文分享自华为云社区《Spring高手之路16——解析XML配置映射为BeanDefinition的源码》,作者:砖业洋__。
1. BeanDefinition阶段的分析

Spring框架中控制反转(IOC)容器的BeanDefinition阶段的具体步骤,主要涉及到Bean的定义、加载、解析,并在后面进行编程式注入和后置处理。这个阶段是Spring框架中Bean生命周期的早期阶段之一,对于理解整个Spring框架非常关键。
在这一步,Spring容器通过配置文件或配置类来了解需要管理哪些Bean。对于基于XML的配置,通常使用ClassPathXmlApplicationContext或者FileSystemXmlApplicationContext。
Spring框架通过使用BeanDefinitionReader实例(如XmlBeanDefinitionReader)来解析配置文件。解析后,每个Bean配置会被封装成一个BeanDefinition对象,这个对象包含了类名、作用域、生命周期回调等信息。
除了配置文件定义的Bean,也可以通过编程的方式动态添加BeanDefinition到IOC容器中,这增加了灵活性。
BeanDefinition的后置处理是指容器允许使用BeanDefinitionRegistryPostProcessor或BeanFactoryPostProcessor来对解析后的BeanDefinition做进一步处理,例如修改Bean的属性等。
2. 加载xml配置文件

2.1 XML配置文件中加载bean的代码示例

先给出最简单的代码示例,然后逐步分析
全部代码如下:
  1. package com.example.demo.bean;
  2. // HelloWorld.java
  3. public class HelloWorld {
  4.     private String message;
  5.     public void setMessage(String message) {
  6.         this.message = message;
  7.     }
  8.     public void sayHello() {
  9.         System.out.println("Hello, " + message + "!");
  10.     }
  11. }
复制代码
主程序:
  1. package com.example.demo;
  2. import com.example.demo.bean.HelloWorld;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class DemoApplication {
  5.     public static void main(String[] args) {
  6.         // 创建Spring上下文(容器)
  7.         ClassPathXmlApplicationContext context =
  8.                 new ClassPathXmlApplicationContext("ApplicationContext.xml");
  9.         // 从容器中获取bean,假设我们有一个名为 'helloWorld' 的bean
  10.         HelloWorld helloWorld = context.getBean("helloWorld", HelloWorld.class);
  11.         // 使用bean
  12.         helloWorld.sayHello();
  13.         // 关闭上下文
  14.         context.close();
  15.     }
  16. }
复制代码
xml文件
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.        xsi:schemaLocation="http://www.springframework.org/schema/beans
  5.        http://www.springframework.org/schema/beans/spring-beans.xsd">
  6.    
  7.     <bean id="helloWorld" class="com.example.demo.bean.HelloWorld">
  8.         
  9.         <property name="message" value="World"/>
  10.     </bean>
  11. </beans>
复制代码
运行结果:

接着我们就从这段代码开始分析
2.2 setConfigLocations - 设置和保存配置文件路径

我们还是以Spring 5.3.7的源码为例分析
  1. // 创建Spring上下文(容器)
  2.         ClassPathXmlApplicationContext context =
  3.                 new ClassPathXmlApplicationContext("ApplicationContext.xml");
复制代码
这段代码,我们利用idea点击去分析,最后在ClassPathXmlApplicationContext的重载方法里看到调用了setConfigLocations设置配置文件的路径。

接着看看setConfigLocations方法

setConfigLocations() 方法的主要作用是设定 Spring 容器加载 Bean 定义时所需要读取的配置文件路径。这些路径可以是类路径下的资源、文件系统中的资源或者其他任何通过URL定位的资源。该方法确保所有提供的配置路径都被保存并在稍后的容器刷新操作中使用。
源码提出来分析:
  1. public void setConfigLocations(@Nullable String... locations) {
  2.     if (locations != null) {
  3.         // 使用Spring的Assert类来校验,确保传入的配置位置数组中没有null元素。
  4.         Assert.noNullElements(locations, "Config locations must not be null");
  5.         
  6.         // 根据传入的配置位置数量,初始化内部存储配置位置的数组。
  7.         this.configLocations = new String[locations.length];
  8.         // 遍历传入的配置位置数组。
  9.         for(int i = 0; i < locations.length; ++i) {
  10.             // 调用resolvePath方法处理每一个配置位置(可能进行必要的路径解析,如解析占位符)。
  11.             // trim()用于移除字符串首尾的空格,保证保存的路径是净化的。
  12.             this.configLocations[i] = this.resolvePath(locations[i]).trim();
  13.         }
  14.     } else {
  15.         // 如果传入的配置位置是null,清除掉所有已设定的配置位置。
  16.         this.configLocations = null;
  17.     }
  18. }
复制代码
在上下文被刷新的时候,这些配置文件位置会被读取,并且Spring容器将解析其中定义的beans并将它们注册到容器中。setConfigLocations() 方法只是设置了这些位置,而实际的加载和注册过程是在上下文刷新时完成的。
这个setConfigLocations方法通常不是由用户直接调用的,而是在ApplicationContext初始化的过程中被框架调用,例如在基于XML的配置中,我们会在初始化ClassPathXmlApplicationContext或FileSystemXmlApplicationContext时提供配置文件的路径。
在debug的时候,可以看到把测试代码中设置的 xml 配置文件的路径保存了。

2.3 refresh - 触发容器刷新,配置文件的加载与解析

我们上面看到ClassPathXmlApplicationContext方法里面,执行完setConfigLocations后,紧接着有个refresh方法,我们来看看。


在Spring框架中,refresh()方法是非常关键的,它是ApplicationContext接口的一部分。这个方法的主要功能是刷新应用上下文,加载或者重新加载配置文件中定义的Bean,初始化所有的单例,配置消息资源,事件发布器等。
代码提出来分析:
  1. public void refresh() throws BeansException, IllegalStateException {
  2.     // 同步块,确保容器刷新过程的线程安全
  3.     synchronized(this.startupShutdownMonitor) {
  4.         // 开始上下文刷新的步骤记录,用于监控和诊断
  5.         StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
  6.         
  7.         // 准备刷新过程,设置开始时间,状态标志等
  8.         this.prepareRefresh();
  9.         
  10.         // 获取新的BeanFactory,如果是第一次刷新则创建一个BeanFactory
  11.         ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
  12.         
  13.         // 配置BeanFactory,注册忽略的依赖接口等
  14.         this.prepareBeanFactory(beanFactory);
  15.         try {
  16.             // 允许BeanFactory的后置处理器对其进行修改
  17.             this.postProcessBeanFactory(beanFactory);
  18.             
  19.             // 开始Bean工厂的后置处理步骤的监控
  20.             StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
  21.             
  22.             // 调用BeanFactoryPostProcessors
  23.             this.invokeBeanFactoryPostProcessors(beanFactory);
  24.             
  25.             // 注册BeanPostProcessors到BeanFactory
  26.             this.registerBeanPostProcessors(beanFactory);
  27.             
  28.             // Bean后置处理步骤结束
  29.             beanPostProcess.end();
  30.             
  31.             // 初始化MessageSource组件,用于国际化等功能
  32.             this.initMessageSource();
  33.             
  34.             // 初始化事件广播器
  35.             this.initApplicationEventMulticaster();
  36.             
  37.             // 留给子类覆盖的定制方法
  38.             this.onRefresh();
  39.             
  40.             // 注册监听器
  41.             this.registerListeners();
  42.             
  43.             // 初始化剩余的单例Bean
  44.             this.finishBeanFactoryInitialization(beanFactory);
  45.             
  46.             // 完成刷新过程,通知生命周期处理器lifecycleProcessor刷新过程,发布ContextRefreshedEvent事件
  47.             this.finishRefresh();
  48.         } catch (BeansException var10) {
  49.             // 捕获BeansException,记录警告信息,销毁已创建的Bean
  50.             if (this.logger.isWarnEnabled()) {
  51.                 this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var10);
  52.             }
  53.             // 销毁已经初始化的单例Bean
  54.             this.destroyBeans();
  55.             
  56.             // 取消刷新,重置同步监视器上的标志位
  57.             this.cancelRefresh(var10);
  58.             
  59.             // 抛出异常,结束刷新过程
  60.             throw var10;
  61.         } finally {
  62.             // 在刷新的最后,重置Spring内核中的共享缓存
  63.             this.resetCommonCaches();
  64.             
  65.             // 结束上下文刷新步骤的记录
  66.             contextRefresh.end();
  67.         }
  68.     }
  69. }
复制代码
这个方法精确执行一系列步骤来配置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。
代码提出来分析:
  1. protected final void refreshBeanFactory() throws BeansException {
  2.     // 检查当前应用上下文是否已经包含了一个BeanFactory
  3.     if (this.hasBeanFactory()) {
  4.         // 如果已经存在BeanFactory,销毁它管理的所有bean
  5.         this.destroyBeans();
  6.         // 关闭现有的BeanFactory,释放其可能持有的任何资源
  7.         this.closeBeanFactory();
  8.     }
  9.     try {
  10.         // 创建一个DefaultListableBeanFactory的新实例,这是Spring中ConfigurableListableBeanFactory接口的默认实现
  11.         DefaultListableBeanFactory beanFactory = this.createBeanFactory();
  12.         // 为beanFactory设置一个序列化ID,这个ID后面可以用于反序列化
  13.         beanFactory.setSerializationId(this.getId());
  14.         // 允许子类定制新创建的beanFactory
  15.         this.customizeBeanFactory(beanFactory);
  16.         // 从底层资源(例如XML文件)中加载bean定义到beanFactory
  17.         this.loadBeanDefinitions(beanFactory);
  18.         // 将新的beanFactory赋值给这个上下文的beanFactory属性
  19.         this.beanFactory = beanFactory;
  20.     } catch (IOException var2) {
  21.         // 如果在解析bean定义资源过程中发生I/O异常,将其包装并重新抛出为ApplicationContextException
  22.         throw new ApplicationContextException("I/O错误解析用于" + this.getDisplayName() + "的bean定义源", var2);
  23.     }
  24. }
复制代码
这个方法在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定义的关键步骤。
代码提出来分析:
  1. // 使用DefaultListableBeanFactory作为Bean定义注册的目标工厂,加载Bean定义
  2. protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
  3.     // 创建一个读取XML Bean定义的读取器,并将工厂传入用于注册定义
  4.     XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
  5.     // 设置环境对象,可能包含属性解析相关的环境配置
  6.     beanDefinitionReader.setEnvironment(this.getEnvironment());
  7.     // 设置资源加载器,允许读取器加载XML资源
  8.     beanDefinitionReader.setResourceLoader(this);
  9.     // 设置实体解析器,用于解析XML中的实体如DTD
  10.     beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
  11.     // 初始化Bean定义读取器,可能设置一些参数,如是否验证XML
  12.     this.initBeanDefinitionReader(beanDefinitionReader);
  13.     // 调用重载的loadBeanDefinitions,根据配置的资源和位置加载Bean定义
  14.     this.loadBeanDefinitions(beanDefinitionReader);
  15. }
  16. // 初始化Bean定义读取器,主要设置是否进行XML验证
  17. protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader) {
  18.     // 设置XML验证模式,通常取决于应用上下文的配置
  19.     reader.setValidating(this.validating);
  20. }
  21. // 通过XmlBeanDefinitionReader加载Bean定义
  22. protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
  23.     // 获取所有配置资源的数组(如XML配置文件)
  24.     Resource[] configResources = this.getConfigResources();
  25.     // 如果配置资源非空,则加载这些资源
  26.     if (configResources != null) {
  27.         reader.loadBeanDefinitions(configResources);
  28.     }
  29.     // 获取所有配置文件位置的数组
  30.     String[] configLocations = this.getConfigLocations();
  31.     // 如果配置文件位置非空,则加载这些位置指定的配置文件
  32.     if (configLocations != null) {
  33.         reader.loadBeanDefinitions(configLocations);
  34.     }
  35. }
复制代码
在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); 继续跟踪!

这段代码的逻辑动作大致为:
我们还是看重点,继续跟踪里面的loadBeanDefinitions

代码提出来分析:
  1. public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
  2. // 将Resource包装为EncodedResource,允许指定编码,然后继续加载Bean定义
  3. return this.loadBeanDefinitions(new EncodedResource(resource));
  4. }
  5. public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
  6. // 断言传入的EncodedResource不为空
  7. Assert.notNull(encodedResource, "EncodedResource must not be null");
  8. // 如果日志级别为trace,则输出跟踪日志
  9. if (this.logger.isTraceEnabled()) {
  10. this.logger.trace("Loading XML bean definitions from " + encodedResource);
  11. }
  12. // 获取当前线程正在加载的资源集合
  13. Set<EncodedResource> currentResources = (Set)this.resourcesCurrentlyBeingLoaded.get();
  14. // 检查资源是否已经在加载中,如果是,则抛出BeanDefinitionStoreException异常,避免循环加载
  15. if (!currentResources.add(encodedResource)) {
  16. throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");
  17. } else {
  18. int var6; // 这将用来存储加载的Bean定义数量
  19. try {
  20. // 打开资源的InputStream进行读取
  21. InputStream inputStream = encodedResource.getResource().getInputStream();
  22. Throwable var4 = null;
  23. try {
  24. // 将InputStream封装为InputSource,XML解析器可以接受这个类型
  25. InputSource inputSource = new InputSource(inputStream);
  26. // 如果资源编码不为空,设置资源的编码
  27. if (encodedResource.getEncoding() != null) {
  28. inputSource.setEncoding(encodedResource.getEncoding());
  29. }
  30. // 实际加载Bean定义的方法,返回加载的Bean定义数量
  31. var6 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
  32. } catch (Throwable var24) {
  33. // 捕获Throwable以便在finally块中处理资源释放
  34. var4 = var24;
  35. throw var24;
  36. } finally {
  37. // 关闭InputStream资源
  38. if (inputStream != null) {
  39. if (var4 != null) {
  40. try {
  41. inputStream.close();
  42. } catch (Throwable var23) {
  43. // 添加被抑制的异常
  44. var4.addSuppressed(var23);
  45. }
  46. } else {
  47. inputStream.close();
  48. }
  49. }
  50. }
  51. } catch (IOException var26) {
  52. // 抛出IOException异常,如果解析XML文档失败
  53. throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), var26);
  54. } finally {
  55. // 从当前加载的资源集合中移除该资源
  56. currentResources.remove(encodedResource);
  57. // 如果当前加载的资源集合为空,则从ThreadLocal中移除
  58. if (currentResources.isEmpty()) {
  59. this.resourcesCurrentlyBeingLoaded.remove();
  60. }
  61. }
  62. // 返回加载的Bean定义数量
  63. return var6;
  64. }
  65. }
复制代码
  1. 在这段代码中,loadBeanDefinitions 首先将Resource转换为EncodedResource,这允许它保留关于资源编码的信息。然后,它尝试将资源加载为InputStream并将其转换为InputSource,这是XML解析所需要的。接着它调用doLoadBeanDefinitions方法,实际上负责解析XML并注册Bean定义。
复制代码
在这个过程中,代码确保了不会循环加载相同的资源,并且在加载资源时,如果发生异常,会适当地清理资源并报告错误。加载的Bean定义数量在完成后被返回。
我们来重点看下这段代码的重点步骤:doLoadBeanDefinitions方法!
2.6 doLoadBeanDefinitions - 读取并解析XML配置文件内容


doLoadBeanDefinitions方法做了什么?
具体步骤如下:
这里重点是registerBeanDefinitions方法,继续跟踪代码

继续看重点,最终追到doRegisterBeanDefinitions方法

doRegisterBeanDefinitions(Element root) 方法是 Spring 框架中用于解析 XML 配置文件中的 Bean 定义并注册它们到 Spring 容器的方法。这个方法通常在 XML 文件读取并转换成 DOM(Document Object Model)树之后调用,此时 XML 文件的根元素通过参数 root 传递给这个方法。
代码提出来分析:
  1. protected void doRegisterBeanDefinitions(Element root) {
  2. // 保存旧的解析代理(delegate),以便之后可以恢复
  3. BeanDefinitionParserDelegate parent = this.delegate;
  4. // 创建新的解析代理(delegate),用于处理当前XML根节点的解析
  5. this.delegate = this.createDelegate(this.getReaderContext(), root, parent);
  6. // 如果当前节点使用的是Spring默认的XML命名空间
  7. if (this.delegate.isDefaultNamespace(root)) {
  8. // 获取根节点的"profile"属性
  9. String profileSpec = root.getAttribute("profile");
  10. // 检查"profile"属性是否有文本内容
  11. if (StringUtils.hasText(profileSpec)) {
  12. // 按逗号、分号和空格分隔"profile"属性值,得到指定的profiles数组
  13. String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
  14. // 如果当前环境不接受任何指定的profiles,则不加载该Bean定义文件
  15. if (!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
  16. // 如果日志级别是DEBUG,则记录跳过文件的信息
  17. if (this.logger.isDebugEnabled()) {
  18. this.logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + this.getReaderContext().getResource());
  19. }
  20. // 退出方法,不进行后续处理
  21. return;
  22. }
  23. }
  24. }
  25. // 在解析XML前进行预处理,可被重写的方法
  26. this.preProcessXml(root);
  27. // 解析XML根节点下的Bean定义
  28. this.parseBeanDefinitions(root, this.delegate);
  29. // 在解析XML后进行后处理,可被重写的方法
  30. this.postProcessXml(root);
  31. // 恢复旧的解析代理(delegate)
  32. this.delegate = parent;
  33. }
复制代码
上述代码片段是Spring框架用于注册Bean定义的内部方法。该方法在解析XML配置文件并注册Bean定义到Spring容器时被调用。它包含处理profile属性以根据运行时环境决定是否加载特定Bean定义的逻辑,以及前后处理钩子,允许在解析前后进行自定义操作。最后,它确保解析代理(delegate)被重置为之前的状态,以维护正确的状态。
接着,我们要看看是如何解析xml的,重点关注下parseBeanDefinitions方法
2.7 parseBeanDefinitions - 解析XML中的BeanDefinition元素


parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) 方法的主要目的是遍历 XML 配置文件的根节点,解析并注册其中定义的所有 Bean。该方法负责区分不同类型的元素,即默认命名空间下的标准元素和自定义命名空间下的自定义元素,并对它们进行相应的处理。
代码提出来分析:
  1. protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
  2. // 判断根节点是否使用的是Spring的默认命名空间
  3. if (delegate.isDefaultNamespace(root)) {
  4. // 获取所有子节点
  5. NodeList nl = root.getChildNodes();
  6. // 遍历所有子节点
  7. for (int i = 0; i < nl.getLength(); ++i) {
  8. Node node = nl.item(i);
  9. // 只处理Element类型的节点(过滤掉文本节点等其他类型)
  10. if (node instanceof Element) {
  11. Element ele = (Element)node;
  12. // 如果子元素节点也是默认命名空间,则调用parseDefaultElement方法解析
  13. if (delegate.isDefaultNamespace(ele)) {
  14. this.parseDefaultElement(ele, delegate);
  15. } else {
  16. // 如果子元素节点不是默认命名空间,则调用parseCustomElement方法解析
  17. // 这通常表示节点定义了自定义的行为,可能是用户自定义的标签或者是Spring扩展的标签
  18. delegate.parseCustomElement(ele);
  19. }
  20. }
  21. }
  22. } else {
  23. // 如果根节点不是默认命名空间,那么它可能是一个自定义标签的顶级元素
  24. // 在这种情况下,直接调用parseCustomElement进行解析
  25. delegate.parseCustomElement(root);
  26. }
  27. }
复制代码
这段代码的作用是解析XML文件中定义的bean。它检查每个XML元素(包括根元素和子元素),并根据这些元素是否属于Spring的默认命名空间(通常是"http://www.springframework.org/schema/beans"),调用不同的处理方法。如果元素属于默认命名空间,那么它将调用parseDefaultElement来解析标准的Spring配置元素,例如。如果元素不属于默认命名空间,那么将认为它是一个自定义元素,并调用parseCustomElement来解析。自定义元素通常是由开发人员定义或Spring扩展提供的,以增加框架的功能。
这里可以看到是一个循环处理Element节点,解析的动作主要是parseDefaultElement方法,继续来看看。

parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) 方法是 Spring 框架解析 XML 配置文件中默认命名空间(也就是没有前缀的 Spring 命名空间)元素的方法。这个方法专门处理 , , , 和  这几种标签。
“没有前缀的 Spring 命名空间” 是指那些元素?它们属于 Spring 的默认命名空间,但在使用时不需要指定命名空间前缀。如 ,  或  ,这些元素都是没有前缀的,它们属于 Spring 默认定义的 XML 模式命名空间,默认命名空间通常在 XML 文件的顶部通过 xmlns 属性声明。
代码提出来分析:
  1. private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
  2. // 判断当前元素节点名称是否是"import"
  3. if (delegate.nodeNameEquals(ele, "import")) {
  4. // 如果是"import",则导入其他配置文件
  5. this.importBeanDefinitionResource(ele);
  6. } else if (delegate.nodeNameEquals(ele, "alias")) {
  7. // 如果节点是"alias",则处理别名定义,为一个bean定义一个或多个别名
  8. this.processAliasRegistration(ele);
  9. } else if (delegate.nodeNameEquals(ele, "bean")) {
  10. // 如果节点是"bean",则处理bean定义,这是定义Spring bean的核心元素
  11. this.processBeanDefinition(ele, delegate);
  12. } else if (delegate.nodeNameEquals(ele, "beans")) {
  13. // 如果节点是"beans",意味着有嵌套的beans定义,需要递归地注册其中的bean定义
  14. this.doRegisterBeanDefinitions(ele);
  15. }
  16. }
复制代码
这段代码的功能是根据元素的名称来决定对XML配置文件中的不同标签进行不同的处理操作。它处理Spring框架默认命名空间下的四种主要标签:
这样,Spring可以根据这些元素来构建应用上下文中的bean工厂。
调试可以发现,xml已经解析出初步的雏形了

在这里似乎没看到bean元素,这是怎么解析的呢?让我们一步一步来,在上面提到的parseDefaultElement方法中有调用processBeanDefinition方法,来看看这是干嘛的。
2.8 processBeanDefinition - 对标签进行具体解析和处理


processBeanDefinition方法是 Spring 框架中用于处理  XML 配置元素的方法。其目的是将  元素中描述的信息转换为 Spring 内部使用的BeanDefinition对象,并将其注册到 Spring IoC 容器中。这是 Spring bean 生命周期中的一个关键步骤,因为在这里定义的 bean 会在容器启动时被实例化和管理
代码提出来分析:
  1. protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
  2. // 使用代理解析bean定义元素,这涉及将XML定义的<bean>元素转换成Spring的BeanDefinitionHolder对象,
  3. // 该对象包含了bean定义和名称。
  4. BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
  5. // 检查解析是否返回了BeanDefinitionHolder对象。
  6. if (bdHolder != null) {
  7. // 如有需要,对bean定义进行装饰。这可能涉及应用任何额外的属性或嵌套元素,
  8. // 这些都是bean定义的一部分,但不是标准<bean> XML配置的一部分。
  9. bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
  10. try {
  11. // 在注册中心注册bean定义。注册中心通常是持有所有bean定义的Spring IoC容器。
  12. BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.getReaderContext().getRegistry());
  13. } catch (BeanDefinitionStoreException var5) {
  14. // 如果在bean注册过程中出现异常,报告错误上下文并抛出异常。
  15. // 错误上下文包括bean的名称和引起问题的XML元素。
  16. this.getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, var5);
  17. }
  18. // 在成功注册后,通知任何监听器一个新的bean定义已被注册。
  19. // 这是Spring事件机制的一部分,允许对容器内的特定动作作出响应。
  20. this.getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
  21. }
  22. // 注意:如果bdHolder为空,则意味着bean定义元素没有被正确解析
  23. // 或者它不是要被注册的(例如,在抽象定义的情况下)。
  24. // 因此,在这种情况下,该方法不执行任何操作。
  25. }
复制代码
该方法通常在Spring框架的bean定义解析过程中使用,它处理基于提供的XML元素创建和注册bean定义的逻辑。BeanDefinitionParserDelegate 是一个帮助类,负责处理解析特定Spring XML结构的细节。
debug这个类的时候,发现已经解析出这个bean的class和id了

有人会好奇了,这是如何将 xml 元素封装为 BeanDefinitionHolder呢

parseBeanDefinitionElement方法是用来解析 Spring 配置文件中  元素的定义,并生成对应的 BeanDefinitionHolder 对象。BeanDefinitionHolder 是一个包装类,它封装了 BeanDefinition 实例和该定义的名称(即bean的id)以及别名(如果有的话)。
代码提出来分析:
  1. @Nullable
  2. public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
  3. // 调用重载方法parseBeanDefinitionElement,并将BeanDefinition设置为null
  4. return this.parseBeanDefinitionElement(ele, (BeanDefinition)null);
  5. }
  6. @Nullable
  7. public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
  8. // 获取元素的id属性
  9. String id = ele.getAttribute("id");
  10. // 获取元素的name属性
  11. String nameAttr = ele.getAttribute("name");
  12. // 创建别名列表
  13. List<String> aliases = new ArrayList();
  14. if (StringUtils.hasLength(nameAttr)) {
  15. // 如果name属性非空,则使用分隔符分割name字符串,并将结果添加到别名列表
  16. String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, ",; ");
  17. aliases.addAll(Arrays.asList(nameArr));
  18. }
  19. // 默认情况下bean的名称使用id属性的值
  20. String beanName = id;
  21. if (!StringUtils.hasText(id) && !aliases.isEmpty()) {
  22. // 如果id为空且别名列表非空,则使用别名列表中的第一个作为bean名称,并从列表中移除它
  23. beanName = aliases.remove(0);
  24. if (this.logger.isTraceEnabled()) {
  25. this.logger.trace("No XML 'id' specified - using '" + beanName + "' as bean name and " + aliases + " as aliases");
  26. }
  27. }
  28. if (containingBean == null) {
  29. // 如果不是嵌套bean定义,则检查bean名称和别名的唯一性
  30. this.checkNameUniqueness(beanName, aliases, ele);
  31. }
  32. // 解析bean定义元素,返回AbstractBeanDefinition对象
  33. AbstractBeanDefinition beanDefinition = this.parseBeanDefinitionElement(ele, beanName, containingBean);
  34. if (beanDefinition != null) {
  35. if (!StringUtils.hasText(beanName)) {
  36. // 如果bean名称为空,则尝试生成bean名称
  37. try {
  38. if (containingBean != null) {
  39. // 如果是内部bean,则使用特定的生成策略
  40. beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true);
  41. } else {
  42. // 否则使用默认策略
  43. beanName = this.readerContext.generateBeanName(beanDefinition);
  44. String beanClassName = beanDefinition.getBeanClassName();
  45. if (beanClassName != null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() && !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
  46. // 如果bean类名不为空,且生成的bean名称以类名开头,且未被使用,则将类名添加到别名列表
  47. aliases.add(beanClassName);
  48. }
  49. }
  50. if (this.logger.isTraceEnabled()) {
  51. this.logger.trace("Neither XML 'id' nor 'name' specified - using generated bean name [" + beanName + "]");
  52. }
  53. } catch (Exception var9) {
  54. // 在名称生成过程中捕获异常,并记录错误
  55. this.error(var9.getMessage(), ele);
  56. return null;
  57. }
  58. }
  59. // 将别名列表转换为数组
  60. String[] aliasesArray = StringUtils.toStringArray(aliases);
  61. // 创建并返回BeanDefinitionHolder对象
  62. return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
  63. } else {
  64. // 如果bean定义为空,则返回null
  65. return null;
  66. }
  67. }
复制代码
这段代码负责解析XML中的元素,提取id和name属性,并处理可能的别名。然后它创建一个AbstractBeanDefinition,这是Spring中bean定义的抽象表现形式。如果没有指定bean的名称,它会尝试生成一个唯一的名称,并在必要时添加别名。最终,它返回一个包含所有这些信息的BeanDefinitionHolder。如果在解析过程中遇到任何问题,会记录错误并返回null。
在这段代码中,会调用另一个重载方法,this.parseBeanDefinitionElement(ele, beanName, containingBean);这段代码里有封装  其它属性的 parseBeanDefinitionAttributes 方法,我们来看下


方法 parseBeanDefinitionAttributes 用于解析 Spring 配置文件中  元素的属性,并将这些属性应用到传入的 AbstractBeanDefinition 对象上。这个过程是为了设置bean的作用域、是否延迟初始化、自动装配模式、依赖关系、是否作为自动装配的候选、是否是优先考虑的bean(primary)、初始化方法、销毁方法、工厂方法和工厂bean名称等属性。方法处理了属性的默认值以及处理了一些属性的遗留格式(如 singleton)。
直接提出代码分析:
  1. public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName, @Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) {
  2. // 检查是否使用了已废弃的singleton属性,如果存在,则报错提示应该升级到scope属性
  3. if (ele.hasAttribute("singleton")) {
  4. this.error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
  5. // 如果存在scope属性,则设置bean的作用域
  6. } else if (ele.hasAttribute("scope")) {
  7. bd.setScope(ele.getAttribute("scope"));
  8. // 如果没有设置scope属性但是有包含bean,则设置为包含bean的作用域
  9. } else if (containingBean != null) {
  10. bd.setScope(containingBean.getScope());
  11. }
  12. // 如果设置了abstract属性,根据该属性的值设置bean定义是否为抽象
  13. if (ele.hasAttribute("abstract")) {
  14. bd.setAbstract("true".equals(ele.getAttribute("abstract")));
  15. }
  16. // 解析lazy-init属性,默认使用配置的默认值,如果设置了则覆盖
  17. String lazyInit = ele.getAttribute("lazy-init");
  18. if (this.isDefaultValue(lazyInit)) {
  19. lazyInit = this.defaults.getLazyInit();
  20. }
  21. bd.setLazyInit("true".equals(lazyInit));
  22. // 解析autowire属性,将字符串值转换为相应的自动装配模式
  23. String autowire = ele.getAttribute("autowire");
  24. bd.setAutowireMode(this.getAutowireMode(autowire));
  25. // 解析depends-on属性,将字符串值转换为数组,并设置为bean定义的依赖
  26. if (ele.hasAttribute("depends-on")) {
  27. String dependsOn = ele.getAttribute("depends-on");
  28. bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, ",; "));
  29. }
  30. // 解析autowire-candidate属性,设置bean是否可作为自动装配的候选者
  31. String autowireCandidate = ele.getAttribute("autowire-candidate");
  32. if (this.isDefaultValue(autowireCandidate)) {
  33. String defaultValue = this.defaults.getAutowireCandidates();
  34. if (defaultValue != null) {
  35. String[] patterns = StringUtils.commaDelimitedListToStringArray(defaultValue);
  36. bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));
  37. }
  38. } else {
  39. bd.setAutowireCandidate("true".equals(autowireCandidate));
  40. }
  41. // 解析primary属性,设置bean是否为primary
  42. if (ele.hasAttribute("primary")) {
  43. bd.setPrimary("true".equals(ele.getAttribute("primary")));
  44. }
  45. // 解析init-method属性,设置bean的初始化方法
  46. String initMethodName = ele.getAttribute("init-method");
  47. if (ele.hasAttribute("init-method")) {
  48. bd.setInitMethodName(initMethodName);
  49. // 如果没有设置但是有默认值,则使用默认值
  50. } else if (this.defaults.getInitMethod() != null) {
  51. bd.setInitMethodName(this.defaults.getInitMethod());
  52. bd.setEnforceInitMethod(false);
  53. }
  54. // 解析destroy-method属性,设置bean的销毁方法
  55. String destroyMethodName = ele.getAttribute("destroy-method");
  56. if (ele.hasAttribute("destroy-method")) {
  57. bd.setDestroyMethodName(destroyMethodName);
  58. // 如果没有设置但是有默认值,则使用默认值
  59. } else if (this.defaults.getDestroyMethod() != null) {
  60. bd.setDestroyMethodName(this.defaults.getDestroyMethod());
  61. bd.setEnforceDestroyMethod(false);
  62. }
  63. // 解析factory-method属性,设置bean的工厂方法
  64. if (ele.hasAttribute("factory-method")) {
  65. bd.setFactoryMethodName(ele.getAttribute("factory-method"));
  66. }
  67. // 解析factory-bean属性,设置bean的工厂bean名
  68. if (ele.hasAttribute("factory-bean")) {
  69. bd.setFactoryBeanName(ele.getAttribute("factory-bean"));
  70. }
  71. // 返回配置好的bean定义
  72. return bd;
  73. }
复制代码
这段代码的核心功能是将XML配置文件中的属性转换为BeanDefinition对象的属性。对于每个属性,它首先检查该属性是否存在,如果存在,则读取其值并设置到BeanDefinition对象中。如果存在默认值,并且XML中没有提供特定值,则使用默认值。通过这种方式,Spring容器能够根据配置文件创建和管理bean。
2.9 总结

从读取XML配置文件到注册BeanDefinition的完整流程:

1.加载配置文件:
2.初始化BeanFactory并进行刷新:
3.读取XML配置文件:
4.解析XML文件:
5.Bean定义的解析和注册:
6.事件发布:
这个详细流程显示了从加载配置文件到解析并注册BeanDefinition所涉及的复杂过程,它展示了Spring框架处理Bean声明和依赖关系的内部机制。这是Spring依赖注入核心功能的基础,确保了Bean能够按照定义被实例化和管理。
3. 源码阅读练习题

1. XML配置文件解析:
  Spring容器在解析配置文件时主要使用了 XmlBeanDefinitionReader 类。此外,还用到了 BeanDefinitionDocumentReader 来进行具体的文档读取。
  BeanDefinitionReader 负责从XML文件读取bean定义并转换为Spring内部的 BeanDefinition 对象。
  parseBeanDefinitionElement 在XML元素被读取时调用,它的输出是 BeanDefinitionHolder 对象,其中包含了bean定义以及名称和别名。
2. Bean定义解析:
  BeanDefinition 对象是通过读取XML中的  元素并提取相关属性来创建的。这些属性包括bean的类名、作用域、生命周期回调等。
  parseBeanDefinitionAttributes 方法用于提取bean元素上的属性,并设置到 AbstractBeanDefinition 对象中。
  parseBeanDefinitionAttributes 方法处理的属性包括 scope、lazy-init、autowire 等,这些属性会决定bean的行为和它如何与其他bean交互。
3. Bean名称与别名:
  如果没有提供id或name,Spring会自动生成一个唯一的bean名称。它可能基于类名加上一定的序列号。提示:分析parseBeanDefinitionElement方法时有说过。
  别名可以为bean提供额外的名称,这在需要引用相同的bean但在不同上下文中使用不同名称时很有用。在 parseBeanDefinitionElement 方法中,别名是通过解析 name 属性并以逗号、分号或空格作为分隔符来处理的。
4. Bean作用域与生命周期属性:
  通过设置  元素的 scope 属性定义bean的作用域。singleton 表示全局唯一实例,而 prototype 表示每次请求都创建一个新的实例。
  lazy-init 属性确定bean是否应该在启动时延迟初始化,init-method 和 destroy-method 定义了bean的初始化和销毁时调用的方法。
5. Bean注册:
  BeanDefinition 对象在解析后,通过 DefaultListableBeanFactory.registerBeanDefinition 方法注册到Spring容器中。
  如果发现名称冲突,会抛出 BeanDefinitionStoreException。如果是在不同的配置文件中定义相同名称的bean,后者通常会覆盖前者。
6. 异常处理:
  Spring会通过抛出 BeanDefinitionStoreException 来告知用户配置错误。异常信息会详细说明错误的原因和位置。
  Spring的错误处理机制包括异常的详细信息和精确的定位,这对于开发者快速识别配置错误非常有帮助。
4. 常见疑问

4.1 在refresh过程中,Bean的生命周期是怎样的?每个Bean的状态是如何被管理的?

1.实例化BeanFactory:
2.加载Bean定义:
3.BeanFactoryPostProcessor的执行:
4.BeanPostProcessor的注册:
5.单例Bean的预实例化:
6.依赖注入:
7.Bean初始化:
8.Aware接口的调用:
9.BeanPostProcessor的后处理:
10.事件发布:
11.使用Bean:
12.关闭容器:
在整个生命周期过程中,每个Bean的状态被ApplicationContext和BeanFactory跟踪和管理,从创建、依赖注入、初始化,到销毁,确保Bean在正确的时机被创建和清理。
4.2 refresh方法是自动触发的吗?如果不是,那么是什么条件下需要手动触发?

在Spring中的refresh方法:
1. 何时触发:
2. 为什么需要手动触发:
在Spring Boot中的refresh方法:
Spring Boot大大简化了Spring应用的配置和启动过程。它自动配置了Spring的ApplicationContext并在合适的时候调用了refresh方法。
1. 自动触发:
2. 可能的手动触发场景:
一般情况下的建议:
4.3 在Spring Boot中,refresh方法的行为是否有所不同?Spring Boot是否提供了更优的方法来处理应用上下文的变化?

在Spring Boot中,refresh方法的基本行为保持不变,因为Spring Boot建立在Spring之上,遵循相同的基本原则。不过,Spring Boot确实为应用上下文的管理和刷新提供了更多的自动化和便利性:
1.自动配置:
2.外部化配置:
3.条件刷新:
4.生命周期管理:
5.Actuator endpoints:
6.配置更改监听:
7.错误处理:
综上所述,Spring Boot提供了更为自动化的方式来处理应用上下文的变化,很多时候无需手动调用refresh方法。不过,如果需要在运行时动态改变Bean的配置,并希望这些改变立即生效,那么可能还需要使用Spring提供的refresh方法或通过Spring Boot Actuator的相关端点来达成这一目的。
 
点击关注,第一时间了解华为云新鲜技术~

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4