spring-reading./spring-beans/spring-bean-xmlBeanDefiniti.../README.md

20 KiB
Raw Blame History

XmlBeanDefinitionReader

一、知识储备

  1. Resource
    • Resource 代表一个资源可以是文件、类路径上的文件、URL 等。它提供了对资源的抽象和访问方法。
    • 点击查看Resource接口
  2. ResourceLoader
    • ResourceLoader 可以用于获取资源这些资源可以是文件、类路径上的资源、URL、甚至是远程资源。它提供了一种统一的方式来加载资源无论这些资源位于何处。
    • 点击查看ResourceLoader接口
  3. DocumentLoader
    • XmlBeanDefinitionReader依赖于DocumentLoader来加载和解析XML配置文件以便可以将XML文件中的Bean定义信息转化为Spring容器内部的Bean定义对象。
    • 点击查看DocumentLoader接口

二、基本描述

XmlBeanDefinitionReader是Spring Framework中的一个类用于加载和解析XML格式的Bean定义配置文件将配置文件中定义的Bean元数据信息提取为Spring容器内部的Bean定义对象进而实现IOC容器的构建和管理。这类负责读取XML配置文件解析Bean的定义信息包括ID、类名、属性、依赖等并将这些定义注册到Spring应用程序上下文使我们能够方便地配置和管理应用程序中的各种Bean组件。

三、主要功能

  1. 加载XML配置文件
    • XmlBeanDefinitionReader能够加载XML格式的Spring配置文件通常使用Resource对象来指定配置文件的位置ClassPathResourceFileSystemResource等。
  2. 解析XML文件
    • 它将XML配置文件解析为Spring容器内部数据结构使用DocumentLoader来实现。这包括将XML元素和属性转化为Spring的Bean定义信息。
  3. 注册Bean定义
    • 一旦XmlBeanDefinitionReader成功解析XML文件中的Bean定义信息它会将这些信息注册到Spring容器的Bean工厂以便容器能够创建和管理这些Bean实例。

四、最佳实践

首先创建了一个Spring容器DefaultListableBeanFactory),然后使用XmlBeanDefinitionReader来加载和解析名为"beans.xml"的XML配置文件将其中定义的Bean元数据信息注册到容器中。随后通过容器获取名为"myBean"的Bean实例最后将该Bean实例打印出来。这样的操作实现了Spring容器的初始化、XML配置文件的解析以及Bean的获取和使用。

public class XmlBeanDefinitionReaderDemo {

    public static void main(String[] args) {
        // 创建一个DefaultListableBeanFactory作为Spring容器
        DefaultListableBeanFactory factory = new DefaultListableBeanFactory();

        // 创建XmlBeanDefinitionReader实例用于解析XML配置
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
        // 加载XML配置文件
        reader.loadBeanDefinitions(new ClassPathResource("beans.xml"));

        // 获取并使用Bean
        MyBean myBean = factory.getBean("myBean", MyBean.class);
        System.out.println("myBean = " + myBean);
    }
}

ClassPathResource("sample.xml") 将加载类路径下名为 "sample.xml" 的资源文件的内容。在我们的示例配置文件中,这个资源文件定义了一个名为 "myBean" 的 Spring Bean该 Bean 具有一个属性 "message",其值设置为 "Hello World"。

<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 id="myBean" class="com.xcs.spring.bean.MyBean">
        <property name="message" value="Hello World"/>
    </bean>

</beans>

MyBean 的Java类代表了一个简单的Java Bean。

public class MyBean {

    private String message;

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    @Override
    public String toString() {
        return "MyBean{" +
                "message='" + message + '\'' +
                '}';
    }
}

五、时序图

sequenceDiagram
autonumber
Title: XmlBeanDefinitionReader时序图

XmlBeanDefinitionReaderDemo->>XmlBeanDefinitionReader:loadBeanDefinitions(resource)
XmlBeanDefinitionReader->>XmlBeanDefinitionReader:loadBeanDefinitions(encodedResource)
note over XmlBeanDefinitionReader: 解码资源并加载Bean定义

XmlBeanDefinitionReader->>XmlBeanDefinitionReader:doLoadBeanDefinitions(inputSource,resource)
note over XmlBeanDefinitionReader: 使用InputSource加载Bean定义

XmlBeanDefinitionReader->>XmlBeanDefinitionReader:doLoadDocument(inputSource,resource)
note over XmlBeanDefinitionReader: 调用doLoadDocument方法解析XML

XmlBeanDefinitionReader->>DefaultDocumentLoader:loadDocument(...)
note over XmlBeanDefinitionReader,DefaultDocumentLoader: 加载XML

DefaultDocumentLoader->>XmlBeanDefinitionReader:返回Document

XmlBeanDefinitionReader->>XmlBeanDefinitionReader:registerBeanDefinitions(doc,resource)
note over XmlBeanDefinitionReader: 调用registerBeanDefinitions方法注册Bean定义


XmlBeanDefinitionReader->>DefaultBeanDefinitionDocumentReader:createBeanDefinitionDocumentReader()
note over XmlBeanDefinitionReader,DefaultBeanDefinitionDocumentReader: 创建BeanDefinitionDocumentReader
DefaultBeanDefinitionDocumentReader->>XmlBeanDefinitionReader:返回documentReader

XmlBeanDefinitionReader->>XmlReaderContext:new XmlReaderContext()
note over XmlBeanDefinitionReader,XmlReaderContext: 创建XmlReaderContext
XmlReaderContext->>XmlBeanDefinitionReader:返回readerContext

XmlBeanDefinitionReader->>DefaultBeanDefinitionDocumentReader:registerBeanDefinitions(doc,readerContext)
note over XmlBeanDefinitionReader,DefaultBeanDefinitionDocumentReader: 调用registerBeanDefinitions注册Bean定义

loop Every minute
    DefaultBeanDefinitionDocumentReader->>DefaultBeanDefinitionDocumentReader:doRegisterBeanDefinitions(root)
    note over DefaultBeanDefinitionDocumentReader,DefaultBeanDefinitionDocumentReader: 处理beans标签
    
    DefaultBeanDefinitionDocumentReader->>DefaultBeanDefinitionDocumentReader:parseBeanDefinitions(root,delegate)
    note over DefaultBeanDefinitionDocumentReader,DefaultBeanDefinitionDocumentReader: 处理“import”、“alias”、“bean”标签
    
    DefaultBeanDefinitionDocumentReader->>DefaultBeanDefinitionDocumentReader:parseDefaultElement(ele,delegate)
    note over DefaultBeanDefinitionDocumentReader,DefaultBeanDefinitionDocumentReader: 解析默认元素

    alt 如果是import标签
    DefaultBeanDefinitionDocumentReader->>DefaultBeanDefinitionDocumentReader:importBeanDefinitionResource(ele)
    note over DefaultBeanDefinitionDocumentReader,DefaultBeanDefinitionDocumentReader: 处理import标签

    else 如果是alias标签
    DefaultBeanDefinitionDocumentReader->>DefaultBeanDefinitionDocumentReader:processAliasRegistration(ele)
    note over DefaultBeanDefinitionDocumentReader,DefaultBeanDefinitionDocumentReader: 处理alias标签

    else 如果是bean标签
        rect rgb(207,207,207)
            DefaultBeanDefinitionDocumentReader->>DefaultBeanDefinitionDocumentReader:processBeanDefinition(ele, delegate)
            note over DefaultBeanDefinitionDocumentReader,DefaultBeanDefinitionDocumentReader: 处理bean标签
            DefaultBeanDefinitionDocumentReader->>XmlBeanDefinitionReader:getRegistry()
            note over DefaultBeanDefinitionDocumentReader,XmlBeanDefinitionReader: 获取BeanDefinitionRegistry
            XmlBeanDefinitionReader->>DefaultBeanDefinitionDocumentReader:返回BeanDefinitionRegistry
            DefaultBeanDefinitionDocumentReader->>BeanDefinitionReaderUtils:registerBeanDefinition(definitionHolder,registry)
            note over DefaultBeanDefinitionDocumentReader,BeanDefinitionReaderUtils: 注册Bean定义
            BeanDefinitionReaderUtils->>BeanDefinitionReaderUtils:registerBeanDefinition(beanName,beanDefinition)
            note over BeanDefinitionReaderUtils: 注册Bean定义到容器
		end
    else 如果是beans标签
    DefaultBeanDefinitionDocumentReader->>DefaultBeanDefinitionDocumentReader:doRegisterBeanDefinitions(ele)
    note over DefaultBeanDefinitionDocumentReader,DefaultBeanDefinitionDocumentReader: 处理beans标签(重新递归)
    end
end

六、源码分析

org.springframework.beans.factory.xml.XmlBeanDefinitionReader#loadBeanDefinitions(resource)方法中,又调用了 loadBeanDefinitions(encodedResource) 方法,同时将 resource 包装成一个 EncodedResource 对象。

@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    return loadBeanDefinitions(new EncodedResource(resource));
}

org.springframework.beans.factory.xml.XmlBeanDefinitionReader#loadBeanDefinitions(encodedResource)方法中,首先是创建一个 InputSource 对象用于解析XML。最后调用 doLoadBeanDefinitions 方法实际的XML解析和Bean定义加载。

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
    // ... [代码部分省略以简化]

    try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
        // 创建一个 InputSource 对象用于解析XML
        InputSource inputSource = new InputSource(inputStream);
        
        // 如果 encodedResource 包含字符编码信息,设置 InputSource 的编码
        if (encodedResource.getEncoding() != null) {
            inputSource.setEncoding(encodedResource.getEncoding());
        }
        
        // 调用 doLoadBeanDefinitions 方法实际的XML解析和Bean定义加载
        return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
    }
    catch (IOException ex) {
        // ... [代码部分省略以简化]
    }
    finally {
        // ... [代码部分省略以简化]
    }
}

org.springframework.beans.factory.xml.XmlBeanDefinitionReader#doLoadBeanDefinitions方法中主要用于加载XML配置文件解析其中的Bean定义并注册这些Bean定义到Spring容器中

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
    try {
        // 解析XML文档
        Document doc = doLoadDocument(inputSource, resource);
        // 注册Bean定义并获取已加载的Bean数量
        int count = registerBeanDefinitions(doc, resource);
        if (logger.isDebugEnabled()) {
            logger.debug("Loaded " + count + " bean definitions from " + resource);
        }
        return count;
    } 
    
    // ... [代码部分省略以简化]
}

org.springframework.beans.factory.xml.XmlBeanDefinitionReader#doLoadDocument方法中,主要由DocumentLoader执行实际的XML加载和解析操作并将解析后的Document对象返回。

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
    return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
                                            getValidationModeForResource(resource), isNamespaceAware());
}

org.springframework.beans.factory.xml.XmlBeanDefinitionReader#registerBeanDefinitions方法中,首先创建一个 BeanDefinitionDocumentReader 实例,然后调用它的 registerBeanDefinitions 方法将XML文档中的Bean定义注册到Spring容器中最后返回成功注册的Bean数量。

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;
}

org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#registerBeanDefinitions方法中,又调用 doRegisterBeanDefinitions 方法开始实际的Bean定义注册过程。

@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    this.readerContext = readerContext;
    doRegisterBeanDefinitions(doc.getDocumentElement());
}

org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions方法中核心内容是XML的预处理、Bean定义的解析和XML的后处理。

protected void doRegisterBeanDefinitions(Element root) {
    // ... [代码部分省略以简化]
    
    // 调用 preProcessXml 方法执行XML预处理操作
    preProcessXml(root);

    // 调用 parseBeanDefinitions 方法解析Bean定义
    parseBeanDefinitions(root, this.delegate);

    // 调用 postProcessXml 方法执行XML后处理操作
    postProcessXml(root);
    
    // ... [代码部分省略以简化]
}

org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseBeanDefinitions方法中主要解析XML文档中的Bean定义元素根据元素所属的命名空间默认命名空间或自定义命名空间分别调用合适的方法进行解析。

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    if (delegate.isDefaultNamespace(root)) {
        // 如果根元素属于默认命名空间,则遍历子元素
        NodeList nl = root.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            if (node instanceof Element) {
                Element ele = (Element) node;
                if (delegate.isDefaultNamespace(ele)) {
                    // 如果子元素也属于默认命名空间,解析为默认元素
                    parseDefaultElement(ele, delegate);
                }
                else {
                    // 否则解析为自定义元素
                    delegate.parseCustomElement(ele);
                }
            }
        }
    }
    else {
        // 如果根元素不属于默认命名空间,解析为自定义元素
        delegate.parseCustomElement(root);
    }
}

org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseDefaultElement方法中主要负责根据元素的类型选择合适的方法进行处理从而实现了XML配置文件中不同元素的解析和Bean定义的注册。

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    // 根据元素的名称进行不同的处理
    if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
        // 如果是 <import> 元素导入其他Bean定义资源
        importBeanDefinitionResource(ele);
    }
    else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
        // 如果是 <alias> 元素处理Bean的别名注册
        processAliasRegistration(ele);
    }
    else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
        // 如果是 <bean> 元素处理普通Bean定义
        processBeanDefinition(ele, delegate);
    }
    else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
        // 如果是 <beans> 元素递归注册内部Bean定义
        doRegisterBeanDefinitions(ele);
    }
}

org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#processBeanDefinition方法中,主要是解析 <bean> 元素注册Bean定义到Spring容器。

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    // 调用 BeanDefinitionParserDelegate 的 parseBeanDefinitionElement 方法,解析 <bean> 元素
    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    
    if (bdHolder != null) {
        // 如果解析成功,调用 delegate.decorateBeanDefinitionIfRequired 方法,如果需要的话装饰 Bean 定义
        bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);

        try {
            // 注册最终装饰后的 Bean 定义
            BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
        } catch (BeanDefinitionStoreException ex) {
            // 处理注册时的异常,如果注册失败,抛出 BeanDefinitionStoreException 异常
            getReaderContext().error("Failed to register bean definition with name '" +
                                     bdHolder.getBeanName() + "'", ele, ex);
        }

        // 发送 Bean 注册事件
        getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
    }
}

org.springframework.beans.factory.support.BeanDefinitionReaderUtils#registerBeanDefinition方法中将Bean定义注册到Spring容器的Bean定义注册表中并处理别名的注册。

public static void registerBeanDefinition(
			BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
			throws BeanDefinitionStoreException {

    // Register bean definition under primary name.
    String beanName = definitionHolder.getBeanName();
    registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

    // Register aliases for bean name, if any.
    String[] aliases = definitionHolder.getAliases();
    if (aliases != null) {
        for (String alias : aliases) {
            registry.registerAlias(beanName, alias);
        }
    }
}

七、与其他组件的关系

  1. ApplicationContext的实现类
    • Spring的ApplicationContext接口有多个实现,其中一些实现类在其内部使用XmlBeanDefinitionReader来处理XML配置文件。例如ClassPathXmlApplicationContextFileSystemXmlApplicationContextXmlWebApplicationContext都使用XmlBeanDefinitionReader
  2. Custom XML配置加载
    • 如果我们编写自己的Spring应用程序或自定义的Spring容器我们可以使用XmlBeanDefinitionReader来实现自定义的XML配置加载逻辑。
  3. 单独的XmlBeanDefinitionReader使用
    • 有时,我们可能需要在应用程序中手动创建XmlBeanDefinitionReader实例然后使用它来加载和注册Bean定义。

八、常见问题

  1. XML文件路径正确性
    • 确保XML配置文件的路径和名称正确相对路径或绝对路径都要检查否则将导致文件无法找到或读取。
  2. XML文件格式和编码
    • XML文件必须符合XML语法规则包括标签嵌套、属性格式和XML声明。同时确保文件使用正确的字符编码与指定的编码一致否则可能导致解析错误或乱码。
  3. Bean定义的正确性
    • XML文件中的Bean定义必须正确包括Bean的属性、名称、类路径等。不正确的Bean定义可能导致Spring容器初始化失败。
  4. 版本兼容性
    • 确保所使用的Spring框架版本与XML配置文件中的XML schemaXSD版本兼容。不同版本可能需要不同的XSD版本。
  5. 错误处理和日志记录
    • 在出现问题时查看Spring框架的错误日志和异常堆栈信息以便更好地识别和解决配置问题。及时的错误处理和日志记录有助于定位问题并加快排查过程。