spring 源码之核心类介绍与bean 的加载

核心类介绍

DefaultListableBeanFactory

  • DefaultListableBeanFactory:XmlBeanFactory 继承自DefaultListableBeanFactory,而DefaultListableBeanFactory是整个 bean 加载的核心部分,是 spring 注册以及加载 bean 的默认实现,而对于XmlBeanFactory与DefaultListableBeanFactory不同的地方其实是在XmlBeanFactory中使用了自定义的 xml 读取器 XmlBeanDefinitionReader,实现了个性化的 BeanDefinitionReader 读取,DefaultListableBeanFactory继承了 AbstractAutowireCapableBeanFactory并实现了ConfigurableListableBeanFactory以及BeanDefinitionReader接口。

  • 相关类图

  • AliasRegistry: 定义了对alias的增删改基本操作

  • SimpeAiasRegitry:使用 map缓存 alias,并实现AliasRegistry接口

  • SingletonBeanRegistry:定义对单例的注册以及获取

  • DefaultSingletonBeanRegistry :SingletonBeanRegistry 的实现

  • HierarchicalBeanFactory:继承BeanFactory的,也就是在 BeanFactory 定义的功能的基础上增加了对 parentFactory 支持

  • BeanDefinitionRegistry: 定义对 BeanDefinition 的各种增删改操作,BeanDefinition 是对Ben的封装

  • FactoryBeanRegstrySupport :在 DefaultSingletonBeanRegistry 基础上增加了对 FactoryBean的特殊处理功能

  • ConfigurableBeanFactory:ConfigurableBeanFactory定义BeanFactory的配置,同时继承了HierarchicalBeanFactory 和 SingletonBeanRegistry 这两个接口,即同时继承了分层和单例类注册的功能

  • ListableBeanFactory :该接口是对BeanFactory的扩展,允许预加载bean定义的BeanFactory可以实现此接口,其目的在于使实现它的BeanFactory能够枚举所有的Bean(就是用来获取多个Bean)

  • AbstractBeanFactory:综合FactoryBeanRegstrySupport和ConfigurableBeanFactory的功能

  • AutowireCapableBeanFactory:提供创建 bean、自动注入、初始化以及应用 bean 的后处理器

  • AbstractAutowireCapableBeanFactory:综合AbstractBeanFactory并对接口 AutowireCapableBeanFactory进行实现

  • ConfigurableListableBeanFactory : 提供bean definition的解析,注册功能,再对单例来个预加载(解决循环依赖问题).继承至 ListableBeanFactory, AutowireCapableBeanFactory, ConfigurableBeanFactory 三个接口。

  • DefaultListableBeanFactory: 是整个 bean 加载的核心部分,是 Spring 注册及加载Bean 的默认实现,DefaultListableBeanFactory继承了 AbstractAutowireCapableBeanFactory 并实现了 ConfigurableListableBeanFactory以及BeanDefinitionRegistry 接口

XmlBeanFactory对DefaultListableBeanFactory类进行了扩展,主要用于从 xml 文档中读取 BeanDefinition,对于注册以及获取 bean 都是使用从父类DefaultListableBeanFactory继承的方法去实现,而唯独于父类不同的个性化的实现就是增加了 XmlBeanDefinitionReader 类型的 reader 属性。在 XmlBeanFactory中主要使用 reader 属性对资源文件进行读取和注册。

XmlBeanDefinitionReader

XML 配置文件的读取是 Spring 中重要的功能,因为Spring 的大部分功能都是以配置作为切人点的,那么我们可以从 XmlBeanDefinitionReader 中梳理一下资源文件读取、解析及注册的大致脉络,首先我们看看各个类的功能。

  • ResourceLoader:定义资源加载器,主要应用于根据给定的资源文件地址返回对应的Resource
  • BeanDefinitionReader:主要定义资源文件读取并转换为 BeanDefinition 的各个功能
  • EnvironmentCapable:定义获取 Environment方法
  • DocumentLoader:定义从资源文件加载到转换 Document 的功能
  • AbstractBeanDefinitionReader: 対 EnvironmentCapable、BeanDefinitionReader类定义功能进行实现
  • BeanDefinitionDocumentReader:定义读取Document并注册BeanDefinition功能
  • BeanDefinitionParserDelegate:定义解析 element 的各种方法

经上分析,我们可以梳理出整个 xml 配置文件读取的大致流程。XmlBeanDifinitionReader中主要包含以下几个步骤

  • 通过继承自AbstractBeanDefinitionReader 中的方法,来使用 ResourLoader 将资源文件路径转换对应的 Resource 文件。
  • 通过 DocumentLoader 对Resource 文件进行转换,将 Resource 文件转换为Document文件。
  • 通过实现接口 BeanDefinitionDocumentReader 的 DefaultBeanDefinitionDocumentReader 类对 Document 进行解析,并使用BeanDefinitionParserDelegate 对 Element 进行解析。

容器的基础 XmlBeanFactory

ini 复制代码
 BeanFactory bf = new XmlBeanFactory (new ClassPathResource ("beanFactoryTest.xml"));

通过 XmlBeanFactory 初始化时序图(如下图所示)我们来看一看上面代码的执行逻辑。 时序图从 BeanFactoryTest 测试类开始,通过时序图我们可以一目了然地看到整个逻辑处理顺序。在测试的 BeanFactoryTest 中首先调用 ClassPathResource 的构造函数来构造 Resource资源文件的实例对象,这样后续的资源处理就可以用 Resource 提供的各种服务来操作了,当我们有了 Resource 后就可以进行 XmlBeanFactory 的初始化了。那么 Resource 资源是如何封装的呢?

配置文件封装

Spring 的配置文件读取是通过 ClassPathResource 进行封装的,如 new ClassPathResource ("beanFactoryTest.xml"), 那么ClassPathResource 完成了什么功能呢? 在Java 中,将不同来源的资源抽象成 URL,通过注册不同的 handler (URLStreamHandler)来处理不同来源的资源的读取逻辑,一般handler 的类型使用不同前缀(协议,Protocol)来识别,如"file:" "http:" "jar:" 等,然而URL 没有默认定义相对 Classpath 或 ServletContext 等资源的 handler,虽然可以注册自己的 URLStreamHandler 来解析特定的URL 前缀(协议),比如"classpath",然而这需要了解 URL 的实现机制,而且URL也没有提供基本的方法,如检查当前资源是否存在、检查当前资源是否可读等方法。因而 Spring对其内部使用到的资源实现了自己的抽象结构Resource 接口封装底层资源。

csharp 复制代码
 public interface InputStreamSource {
    InputStream getInputStream() throws IOException;
 }
 public interface Resource extends InputStreamSource {
   boolean exists () ; 
   boolean isReadable () ; 
   boolean isOpen () ;
   URL getURL () throws IOException;
   URI getURI () throws IOException;
   File getFile () throws IOException; long lastModified() throws IOException;
   Resource createRelative (String relativePath) throws IOException;
   String getFilename () ;
   String getDescription () ;
 }

对不同来源的资源文件都有相应的 Resource 实现:文件(FileSystemResource)、Classpath资源(ClassPathResource )、URL. 资源 (UrlResource)、InputStream 资源(InputStreamResource)、Byte 数组(ByteArrayResource)等。

XmlBeanFactory

当通过 Resource 相关类完成了对配置文件进行封装后配置文件的读取工作就全权交给XmlBeanDefinitionReader 来处理了。

  • XmlBeanFactory是Spring框架中的一个实现类,它是BeanFactory接口的一个具体实现。XmlBeanFactory的主要作用是通过解析XML配置文件来创建和管理Bean的实例。XmlBeanFactory加载并解析XML配置文件,根据配置文件中的定义创建对应的Bean实例,并将这些Bean实例存储在容器中。
  • XmlBeanFactory类将持有此XML配置元数据,并用它来构建一个完全可配置的系统或应用。ApplicationContext包含BeanFactory的所有功能,同时还进行了扩展。
  • 然而,需要注意的是,Spring 3.1以后已经废弃了XmlBeanFactory这个类了,现在推荐使用的是ApplicationContext。虽然XmlBeanFactory已经废弃,但是它的源码和设计仍然是学习Spring内部工作原理的好资源。例如,你可以通过学习XmlBeanFactory的源码来理解Spring是如何从XML配置文件中读取Bean定义,以及如何将这些定义转化为实际的Bean实例。这对于理解Spring的IoC(控制反转)和DI(依赖注入)的概念非常有帮助。
  • 现在介绍的是使用新版的ApplicationContext进行解析

源码解析

测试入口

如下测试代码与ClassPathXmlApplicationContext的构造方法

scss 复制代码
 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();
 }
 ......
     public ClassPathXmlApplicationContext(
       String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
       throws BeansException {
 ​
     super(parent);
     setConfigLocations(configLocations);
     if (refresh) {
       refresh();
     }
   }
 ​

重点解析一下setConfigLocations(configLocations)

  • 这个方法的主要作用是设定 Spring 容器加载 Bean 定义时所需要读取的配置文件路径。这些路径可以是类路径下的资源、文件系统中的资源或者其他任何通过URL定位的资源。该方法确保所有提供的配置路径都被保存并在稍后的容器刷新操作中使用。
typescript 复制代码
 public void setConfigLocations(@Nullable String... locations) {
    if (locations != null) {
       Assert.noNullElements(locations, "Config locations must not be null");
       this.configLocations = new String[locations.length];
       for (int i = 0; i < locations.length; i++) {
          // 解析给定的路径,如有必要,将占位符替换为相应的环境属性值。应用于配置位置
          this.configLocations[i] = resolvePath(locations[i]).trim();
       }
    }
    else {
       this.configLocations = null;
    }
 }

bean 加载

保存配置文件的路径之后,调用刷新上下文的方法refresh()。这个方法很重要很重要很重要,很难很难很难,以后慢慢细说,现在关注 bean 加载

看到这一行代码,这里就是我们现在需要研究的。一路点进去,最终找到AbstractRefreshableApplicationContext#refreshBeanFactory

scss 复制代码
 /**
  * This implementation performs an actual refresh of this context's underlying
  * bean factory, shutting down the previous bean factory (if any) and
  * initializing a fresh bean factory for the next phase of the context's lifecycle.
  * 此实现执行此上下文的底层bean工厂的实际刷新,关闭以前的bean工厂(如果有的话),并为上下文生命周期的下一阶段初始化新的bean工厂。
  */
 @Override
 protected final void refreshBeanFactory() throws BeansException {
    // 如果存在 则先销毁
    if (hasBeanFactory()) {
       destroyBeans();
       closeBeanFactory();
    }
    try {
       // 创建 beanFactory
       DefaultListableBeanFactory beanFactory = createBeanFactory();
       // 设置序列化id
       beanFactory.setSerializationId(getId());
       customizeBeanFactory(beanFactory);
       // 加载 beanDefinition
       loadBeanDefinitions(beanFactory);
       this.beanFactory = beanFactory;
    }
    catch (IOException ex) {
       throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
    }
 }

loadBeanDefinitions

最终找到loadBeanDefinitions(beanFactory);此方法是在 AbstractApplicationContext 的子类中实现的,这种模式是一个典型的模板方法设计模式的例子,在 spring 中,这种模式被大量运行于框架中,这也是 spring 灵活扩展的原因之一。在模板方法设计模式中,一个算法的框架(即一系列的步骤)被定义在父类的方法中,但是一些步骤的具体实现会延迟到子类中完成。AbstractApplicationContext 提供了 refreshBeanFactory 方法的框架,这个方法定义了刷新 BeanFactory 的步骤,但是它将 loadBeanDefinitions 的具体实现留给了子类。子类需要根据具体的存储资源类型(比如 XML 文件、Java 注解、Groovy 脚本等)来实现这个方法。 下面我们解析AbstractXmlApplicationContext实现的loadBeanDefinitions

java 复制代码
 // 使用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定义。
     initBeanDefinitionReader(beanDefinitionReader);
     // 调用重载的loadBeanDefinitions,根据配置的资源和位置加载Bean定义
     loadBeanDefinitions(beanDefinitionReader);
 }
 ​
 // 空方法 留给字类自行实现
 protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader) {
 ​
 }
 ​
 // 通过XmlBeanDefinitionReader加载Bean定义
 protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
     // 获取所有配置文件位置的数组 这个方法上面有说过 在ClassPathXmlApplicationContext构造器中就保存了路径
     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)方法中,尝试获取配置文件位置信息,如果存在,则通过reader加载这些位置指定的配置文件。这种设计允许从不同的来源加载配置,如直接从资源文件或者从指定的文件路径。

开始解析

这里再分析AbstractBeanDefinitionReader#loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) 做了这么多准备 终于真正开始解析了

正片开始之前,还是得稍微解释这一段代码

  • location:这是一个字符串,表示资源的位置。这个位置可以是一个具体的资源路径,也可以是一个资源模式,比如一个文件路径或者一个 URL。
  • actualResources:这是一个 Resource 对象的集合,它可以为 null。这个参数用于在加载 bean 定义时提供额外的资源。
  • 根据传入的资源位置字符串,通过资源加载器(ResourceLoader)获取对应的资源。如果资源加载器是资源模式解析器(ResourcePatternResolver),它会处理路径中的模式(比如通配符),加载所有匹配的资源。
  • 读取资源,解析并注册其中定义的所有bean定义。如果提供了一个实际资源的集合(actualResources),解析出来的资源将被添加到这个集合中。 返回加载并注册的bean定义的数量。

继续往下追踪XmlBeanDefinitionReader#loadBeanDefinitions(EncodedResource encodedResource)

csharp 复制代码
 public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
    Assert.notNull(encodedResource, "EncodedResource must not be null");
    if (logger.isTraceEnabled()) {
       logger.trace("Loading XML bean definitions from " + encodedResource);
    }
 ​
    // 获取当前线程正在加载的资源集合
    Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
    // 检查资源是否已经在加载中,如果是,则抛出BeanDefinitionStoreException异常,避免循环加载
    if (!currentResources.add(encodedResource)) {
       throw new BeanDefinitionStoreException(
             "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
    }
 ​
    // 打开资源的InputStream进行读取
    try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
       // 将InputStream封装为InputSource,XML解析器可以接受这个类型
       InputSource inputSource = new InputSource(inputStream);
       if (encodedResource.getEncoding() != null) {
          inputSource.setEncoding(encodedResource.getEncoding());
       }
       // 实际加载Bean定义的方法,返回加载的Bean定义数量
       return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
    }
    catch (IOException ex) {
       throw new BeanDefinitionStoreException(
             "IOException parsing XML document from " + encodedResource.getResource(), ex);
    }
    finally {
       // 从当前加载的资源集合中移除该资源
       currentResources.remove(encodedResource);
       if (currentResources.isEmpty()) {
          // 如果当前加载的资源集合为空,则从ThreadLocal中移除
          this.resourcesCurrentlyBeingLoaded.remove();
       }
    }
 }
  • 这个主要是判断当前的encodedResource这个资源是否已经被加载过了,避免重复加载
  • 然后通过encodedResource获取inputStream,再将inputStream封装成InputSource,XML解析器可以接受这个类型
  • 最后调用doLoadBeanDefinitions加载 bean 定义
  • 避免重复加载,在finally将当前的encodedResource从currentResources移除
  • 如果currentResources是空集合的话,则从ThreadLocal移除,因为这个很容易造成内存泄露

doLoadBeanDefinitions

继续看XmlBeanDefinitionReader#doLoadBeanDefinitions,看到 do 开头的方法,就知道 spring 要开始正在工作了

这里的代码很多是日志输出,就看一下重要的这一段代码

  • 将InputSource进一步封装成Document,这个Document对象代表了 XML 文件的结构
  • 通过调用registerBeanDefinitions方法,将解析得到的Document中的 Bean 定义注册到 SpringBean 工厂中。这个方法返回注册的 Bean 定义的数量。

registerBeanDefinitions

继续跟追XmlBeanDefinitionReader#registerBeanDefinitions

scss 复制代码
 public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    int countBefore = getRegistry().getBeanDefinitionCount();
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    return getRegistry().getBeanDefinitionCount() - countBefore;
 }
 ​
 public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
   this.readerContext = readerContext;
   // doc.getDocumentElement()这里获取Element
   doRegisterBeanDefinitions(doc.getDocumentElement());
 }

最终跟踪到DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions(Element root)

scss 复制代码
 protected void doRegisterBeanDefinitions(Element root) {
    // 保存旧的解析代理(delegate),以便之后可以恢复
    BeanDefinitionParserDelegate parent = this.delegate;
    // 创建新的解析代理(delegate),用于处理当前XML根节点的解析
    this.delegate = createDelegate(getReaderContext(), root, parent);
 ​
    // 如果当前节点使用的是Spring默认的XML命名空间
    if (this.delegate.isDefaultNamespace(root)) {
       // 获取根节点的"profile"属性
       String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
       if (StringUtils.hasText(profileSpec)) {
          // 按逗号、分号和空格分隔"profile"属性值,得到指定的profiles数组
          String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
          // We cannot use Profiles.of(...) since profile expressions are not supported
          // in XML config. See SPR-12458 for details.
          // 如果当前环境不接受任何指定的profiles,则不加载该Bean定义文件
          if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
             if (logger.isDebugEnabled()) {
                logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                      "] not matching: " + getReaderContext().getResource());
             }
             return;
          }
       }
    }
 ​
    // 在解析XML前进行预处理,可被重写的方法
    preProcessXml(root);
    // 解析XML根节点下的Bean定义
    parseBeanDefinitions(root, this.delegate);
    // 在解析XML后进行后处理,可被重写的方法
    postProcessXml(root);
    // 恢复旧的解析代理(delegate)
    this.delegate = parent;
 }
  • 该方法在解析XML配置文件并注册Bean定义到Spring容器时被调用。它包含处理profile属性以根据运行时环境决定是否加载特定Bean定义的逻辑,以及前后处理钩子,允许在解析前后进行自定义操作。最后,它确保解析代理(delegate)被重置为之前的状态,以维护正确的状态。
  • 上面有说过BeanDefinitionParserDelegate:定义解析 element 的各种方法

接着,我们要看看是如何解析xml的,重点关注下parseBeanDefinitions方法,马上就结束了加油

csharp 复制代码
 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 ele) {
             // 如果子元素节点也是默认命名空间,则调用parseDefaultElement方法解析
             if (delegate.isDefaultNamespace(ele)) {
                parseDefaultElement(ele, delegate);
             }
             else {
                // 如果子元素节点不是默认命名空间,则调用parseCustomElement方法解析
                // 这通常表示节点定义了自定义的行为,可能是用户自定义的标签或者是Spring扩展的标签
                delegate.parseCustomElement(ele);
             }
          }
       }
    }
    else {
       // 如果根节点不是默认命名空间,那么它可能是一个自定义标签的顶级元素
       // 在这种情况下,直接调用parseCustomElement进行解析
       delegate.parseCustomElement(root);
    }
 }
  • 这段代码的作用是解析XML文件中定义的bean。它检查每个XML元素(包括根元素和子元素),并根据这些元素是否属于Spring的默认命名空间(通常是"www.springframework.org/schema/bean..."),调用不同的处理方法。
  • 如果元素属于默认命名空间,那么它将调用parseDefaultElement来解析标准的Spring配置元素,例如。
  • 如果元素不属于默认命名空间,那么将认为它是一个自定义元素,并调用parseCustomElement来解析。自定义元素通常是由开发人员定义或Spring扩展提供的,以增加框架的功能。

这里可以看到是一个循环处理Element节点,解析的动作主要是parseDefaultElement方法,继续来看看。

scss 复制代码
   private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
     if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
       // 如果是"import",则导入其他配置文件
       importBeanDefinitionResource(ele);
     }
     else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
       // 如果节点是"alias",则处理别名定义,为一个bean定义一个或多个别名
       processAliasRegistration(ele);
     }
     else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
       // 如果节点是"bean",则处理bean定义,这是定义Spring bean的核心元素
       processBeanDefinition(ele, delegate);
     }
     else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
       // 如果节点是"beans",意味着有嵌套的beans定义,需要递归地注册其中的bean定义
       doRegisterBeanDefinitions(ele);
     }
   }
  • 这段代码的功能是根据元素的名称来决定对XML配置文件中的不同标签进行不同的处理操作。它处理Spring框架默认命名空间下的四种主要标签:
  • :导入其他Spring XML配置文件到当前的配置文件中。
  • :为一个已经定义的bean提供一个或多个别名。
  • :定义一个Spring管理的bean,是最常用的元素,包含了bean的详细配置。
  • :定义一个beans的集合,通常是配置文件中的顶层元素,但也可以是嵌套定义,表示一个新的作用域或者上下文。
  • 这样,Spring可以根据这些元素来构建应用上下文中的bean工厂。

调试可以发现,xml已经解析出初步的雏形了,胜利就在眼前了

继续看DefaultBeanDefinitionDocument#ReaderprocessBeanDefinition

scss 复制代码
 protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    // 使用代理解析bean定义元素,这涉及将XML定义的<bean>元素转换成Spring的BeanDefinitionHolder对象,
    // 该对象包含了bean定义和名称。
    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    if (bdHolder != null) {
       // 如有需要,对bean定义进行装饰。这可能涉及应用任何额外的属性或嵌套元素,
       // 这些都是bean定义的一部分,但不是标准<bean> XML配置的一部分。
       bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
       try {
          // Register the final decorated instance.
          // 在注册中心注册bean定义。注册中心通常是持有所有bean定义的Spring IoC容器。
          BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
       }
       catch (BeanDefinitionStoreException ex) {
          getReaderContext().error("Failed to register bean definition with name '" +
                bdHolder.getBeanName() + "'", ele, ex);
       }
       // Send registration event.
       // 在成功注册后,通知任何监听器一个新的bean定义已被注册。
       // 这是Spring事件机制的一部分,允许对容器内的特定动作作出响应。
       getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
    }
 }
  • 该方法过后,就已经解析完 xml 并且已经解析出这个bean的class和id了,它处理基于提供的XML元素创建和注册bean定义的逻辑。BeanDefinitionParserDelegate 是一个帮助类,负责处理解析特定Spring XML结构的细节。

具体怎么解析的呢,还得往下跟踪到BeanDefinitionParserDelegate#parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean)

scss 复制代码
 @Nullable
 public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
    // 获取bean的id
    String id = ele.getAttribute(ID_ATTRIBUTE);
    // 获取bean的name
    String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
 ​
    List<String> aliases = new ArrayList<>();
    // 分割 name
    if (StringUtils.hasLength(nameAttr)) {
       String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
       aliases.addAll(Arrays.asList(nameArr));
    }
 ​
    // 如果 Id 为空,则使用第一个 name 作为 Id
    String beanName = id;
    if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
       beanName = aliases.remove(0);
       if (logger.isTraceEnabled()) {
          logger.trace("No XML 'id' specified - using '" + beanName +
                "' as bean name and " + aliases + " as aliases");
       }
    }
 ​
    if (containingBean == null) {
       // 校验beanName和aliases的唯一性
       // 内部核心为使用usedNames集合保存所有已经使用了的beanName和alisa
       checkNameUniqueness(beanName, aliases, ele);
    }
 ​
    // 解析bean定义元素,返回AbstractBeanDefinition对象
    AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
    if (beanDefinition != null) {
       // 如果bean名称为空,则尝试生成bean名称
       if (!StringUtils.hasText(beanName)) {
          try {
             if (containingBean != null) {
                // 如果是内部bean,则使用特定的生成策略
                beanName = BeanDefinitionReaderUtils.generateBeanName(
                      beanDefinition, this.readerContext.getRegistry(), true);
             }
             else {
                // 否则使用默认策略
                beanName = this.readerContext.generateBeanName(beanDefinition);
                // Register an alias for the plain bean class name, if still possible,
                // if the generator returned the class name plus a suffix.
                // This is expected for Spring 1.2/2.0 backwards compatibility.
                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 (logger.isTraceEnabled()) {
                logger.trace("Neither XML 'id' nor 'name' specified - " +
                      "using generated bean name [" + beanName + "]");
             }
          }
          catch (Exception ex) {
             error(ex.getMessage(), ele);
             return null;
          }
       }
       // 将别名列表转换为数组
       String[] aliasesArray = StringUtils.toStringArray(aliases);
       // 创建并返回BeanDefinitionHolder对象
       // 此时BeanDefinition创建完毕
       return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
    }
 ​
    return null;
 }
  • 这段代码负责解析XML中的元素,提取id和name属性,并处理可能的别名。
  • 然后它创建一个AbstractBeanDefinition,这是Spring中bean定义的抽象表现形式。如果没有指定bean的名称,它会尝试生成一个唯一的名称,并在必要时添加别名。
  • 最终,它返回一个包含所有这些信息的BeanDefinitionHolder。如果在解析过程中遇到任何问题,会记录错误并返回null。

胜利真的在眼前了,继续继续

parseBeanDefinitionElement(Element ele, String beanName, @Nullable BeanDefinition containingBean)

重点看一下parseBeanDefinitionAttributes这个方法

相信我,胜利就在下面了,坚持,真的最后一次了BeanDefinitionParserDelegate#parseBeanDefinitionAttributes

scss 复制代码
 public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
       @Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) {
 ​
    // 检查是否使用了已废弃的singleton属性,如果存在,则报错提示应该升级到scope属性
    if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
       error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
    }
    else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {
       // 如果存在scope属性,则设置bean的作用域
       bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
    }
    else if (containingBean != null) {
       // 如果没有设置scope属性但是有包含bean,则设置为包含bean的作用域
       // Take default from containing bean in case of an inner bean definition.
       bd.setScope(containingBean.getScope());
    }
 ​
    // 如果设置了abstract属性,根据该属性的值设置bean定义是否为抽象
    if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {
       bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
    }
 ​
    // 解析lazy-init属性,默认使用配置的默认值,如果设置了则覆盖
    String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
    if (isDefaultValue(lazyInit)) {
       lazyInit = this.defaults.getLazyInit();
    }
    bd.setLazyInit(TRUE_VALUE.equals(lazyInit));
 ​
    // 解析autowire属性,将字符串值转换为相应的自动装配模式
    String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);
    bd.setAutowireMode(getAutowireMode(autowire));
 ​
    // 解析depends-on属性,将字符串值转换为数组,并设置为bean定义的依赖
    if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {
       String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
       bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS));
    }
 ​
    // 解析autowire-candidate属性,设置bean是否可作为自动装配的候选者
    String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);
    if (isDefaultValue(autowireCandidate)) {
       String candidatePattern = this.defaults.getAutowireCandidates();
       if (candidatePattern != null) {
          String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern);
          bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));
       }
    }
    else {
       bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate));
    }
 ​
    // 解析primary属性,设置bean是否为primary
    if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
       bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
    }
 ​
    // 解析init-method属性,设置bean的初始化方法
    if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) {
       String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE);
       bd.setInitMethodName(initMethodName);
    }
    // 如果没有设置但是有默认值,则使用默认值
    else if (this.defaults.getInitMethod() != null) {
       bd.setInitMethodName(this.defaults.getInitMethod());
       bd.setEnforceInitMethod(false);
    }
 ​
    // 解析destroy-method属性,设置bean的销毁方法
    if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) {
       String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE);
       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_ATTRIBUTE)) {
       bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));
    }
    // 解析factory-bean属性,设置bean的工厂bean名
    if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {
       bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));
    }
 ​
    // 返回配置好的bean定义
    return bd;
 }

这段代码不用多说什么了,属于胜利的代码,这里就是最终将 xml 中 定义的 bean 信息添加到 BeanDefinition 中。完结撒花

总结

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

  1. 加载配置文件:
  • 图中实例化ClassPathXmlApplicationContext,这时会传入XML文件路径。
  • ClassPathXmlApplicationContext接受一个或多个XML文件路径作为构造参数。
  1. 初始化BeanFactory并进行刷新:
  • 在图中"执行refresh"步骤表示refresh()方法被调用,这个方法会启动容器的初始化和刷新过程。
  • 在refresh()方法中初始化BeanFactory,并准备对配置文件进行解析。
  1. 读取XML配置文件:
  • 图中"加载Bean定义"步骤代表XmlBeanDefinitionReader的作用,它负责读取和加载XML配置文件
  • XmlBeanDefinitionReader 负责读取传入的XML配置文件。
  1. 解析XML文件:
  • 图中的"解析XML"步骤表示DefaultBeanDefinitionDocumentReader处理XML文件,这包括解析顶层标签
  • DefaultBeanDefinitionDocumentReader 开始处理XML文件,解析这样的顶层标签。
  • 对于元素的解析,首先检查元素是否在默认命名空间。如果是,进行默认元素的解析;如果不是,默认命名空间之外的元素被认为是自定义元素,并交由delegate.parseCustomElement(ele)处理。

Bean定义的解析和注册:

  • 图中的"注册Bean定义"、"处理别名"、"处理Bean"和"处理导入"步骤对应于BeanDefinitionParserDelegate的各种解析活动,它涉及解析bean的id、name、别名、属性、子元素等,以及将解析结果注册到BeanDefinitionRegistry。
  • 使用BeanDefinitionParserDelegate来解析元素的细节,包括bean的id、name、别名等。
  • 解析元素的属性,如scope、lazy-init等,并将这些值设置到BeanDefinition实例中。
  • 如果元素包含子元素(如或),它们也将被解析并以相应的元数据形式加入到BeanDefinition中。
  • 生成的BeanDefinition将会注册到BeanDefinitionRegistry中,使用BeanDefinitionReaderUtils.registerBeanDefinition方法。
  • 如果解析过程中发生任何错误,会通过error方法记录错误信息。

事件发布:

  • 在注册BeanDefinition后,ApplicationContext会发布一个组件注册事件,以通知相关的监听器。这个过程允许实现了ApplicationListener接口或使用@EventListener注解的组件接收到这个事件,并根据需要进行响应。例如,可以使用这个事件来触发某些自定义的逻辑,如额外的配置检查、启动某些后处理操作等。

这个详细流程显示了从加载配置文件到解析并注册BeanDefinition所涉及的复杂过程,它展示了Spring框架处理Bean声明和依赖关系的内部机制。这是Spring依赖注入核心功能的基础,确保了Bean能够按照定义被实例化和管理。

相关推荐
xmh-sxh-13144 分钟前
jdk各个版本介绍
java
天天扭码24 分钟前
五天SpringCloud计划——DAY2之单体架构和微服务架构的选择和转换原则
java·spring cloud·微服务·架构
程序猿进阶24 分钟前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露
FIN技术铺29 分钟前
Spring Boot框架Starter组件整理
java·spring boot·后端
小曲程序36 分钟前
vue3 封装request请求
java·前端·typescript·vue
陈王卜1 小时前
django+boostrap实现发布博客权限控制
java·前端·django
小码的头发丝、1 小时前
Spring Boot 注解
java·spring boot
java亮小白19971 小时前
Spring循环依赖如何解决的?
java·后端·spring
飞滕人生TYF1 小时前
java Queue 详解
java·队列
武子康1 小时前
大数据-230 离线数仓 - ODS层的构建 Hive处理 UDF 与 SerDe 处理 与 当前总结
java·大数据·数据仓库·hive·hadoop·sql·hdfs