2 容器的基本实现

felix.shao2025-02-23

2 容器的基本实现

2.1 容器的基本用法

 容器的基本用法见代码 com.stu.spring.context.chapter02.BeanFactoryTest

2.2 功能分析

 对应上述代码,容器的加载步骤大致如下:

  1. 读取配置文件 chapter02\myContainerTest.xml。对应 ConfigReader 类,用于读取及验证配置文件。
  2. 根据 myContainerTest.xml 中的配置找到对应的类的配置,并实例化。对应 ReflectionUtil 类,用于根据配置文件中的配置进行反射实例化。
  3. 调用实例化后的实例。

2.3 工程搭建

 没有用源码环境,忽略。

2.4.1 Spring 的结构组成

 即项目源码目录结构。

2.4.2 核心类介绍

 了解 Spring 中核心的两个类 DefaultListableBeanFactoryXmlBeanDefinitionReader

DefaultListableBeanFactory

XmlBeanFactory 继承自 DefaultListableBeanFactory,而 DefaultListableBeanFactory 是整个 bean 加载的核心部分,是 Spring 注册及加载 bean 的默认实现。
 可以在 idea 中使用 Ctrl + H,然后将菜单切为 Supertypes Hierarchy 即可查看 DefaultListableBeanFactory 的层次结构图。以下是其类图: DefaultListableBeanFactory.png

  • AliasRegistry:定义了对 Alias 简单的增删改操作。
  • BeanFactory:获取 bean 以及 bean 的 isSingleton, isPrototype, isTypeMatch 等属性。
  • SingletonBeanRegistry:定义对单例的注册获取。
  • SimpleAliasRegistry:使用了 Map 作为 Alias 的缓存,并实现 AliasRegistry 接口。
  • BeanDefinitionRegistry:继承 AliasRegitry,定义了对 BeanDefinitionRegistry 的基本操作。
  • ListableBeanFactory:获取 Bean 的配置清单。
  • HierarchicalBeanFactory:继承 BeanFactory, 在此基础增加了对 ParentBeanFactroy 的支持,多出 BeanFactory getParentBeanFactory(); 和 boolean containsLocalBean(String name); 方法。
  • DefaultSingletonBeanRegistry:对 SingletonBeanRegistry 的实现。
  • ConfigurableBeanFactory:提供配置 Factory 的各种方法。
  • FactoryBeanRegistrySupport:继承 DefaultSingletonBeanRegistry,并在此基础上增加对 FactoryBean 的一些操作。
  • AutowireCapableBeanFactory:提供创建 bean,自动注入,初始化以及应用 bean 的后处理器。
  • AbstractBeanFactory:综合 FactoryBeanRegitrySupport 和 ConfigurableBeanFactory 的功能。
  • ConfigurableListableBeanFactory: BeanFactory 配置清单,指定忽略类型及接口等。
  • AbstractAutowireCapableBeanFactory:结合 AbstractBeanFactory 和 接口 AutowireCapableBeanFactory。
  • DefaultListableBeanFactory:综合上述所有功能,主要是对 bean 注册后的处理。
XmlBeanDefinitionReader

 XML 配置文件的读取是 Spring 中重要的功能,因为 Spring 的大部分功能都是以配置作为切入点。以下是相关类介绍:

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

 以下是其类图: XmlBeanDefinitionReader.png

 在 XmlBeanDifinitionReader 中主要包含以下几步的处理:

  1. 通过继承自 AbstractBeanDefinitionReader 中的方法,来使用 ResourLoader 将资源文件路径转换对应的 Resource 文件;
  2. 通过 DocumentLoaderResource 文件进行转换,将 Resource 文件转换为 Document 文件;
  3. 通过实现 BeanDefinitionDocumentReaderDefaultBeanDefinitionDocumentReader 类对 Docment 进行解析,并使用 BeanDefinitionPareserDelegateElement 进行解析。

2.5 容器的基础 XmlBeanFactory

 代码入口如下,我们从这个入口开始跟踪源码。

package com.stu.spring.context.chapter02;
//...
new XmlBeanFactory(new ClassPathResource("chapter02\\myContainerTest.xml"));
//...

 以下是 XmlBeanFactory 初始化时序图。 XmlBeanFactoryInit.jpg

2.5.1 配置文件封装

ClassPathResource 作用

 Java 的 URL 的实现机制中,URL 没有提供一些基本的方法,如检查当前资源是否存在,检查当前资源是否可读等方法。因而 Spring 对其内部使用到的资源实现了自己的抽象结构:org.springframework.core.io.Resource 接口来封装底层资源。详细代码略。
 Spring 对不同来源的资源文件都有相应的 Resource 实现:文件(FileSystemResource),Classpath 资源(ClasspathResource),URL 资源(URLResource),InputStream 资源(InputStreamResource),Byte 数组(ByteArrayResource)等。
 日常的开发工作中,资源的加载,我们可以使用如 new ClassPathResource("chapter02\\myContainerTest.xml").getInputStream() 等进行实现。这部分只是 Spring 基于 java 流做了一些包装。

loadBeanDefinitions 入口

 我们从 new (XmlBeanFactory) 跟踪找到 loadBeanDefinitions 方法,其是我们分析的重点之一,该方法是资源加载的真正实现。

public class XmlBeanFactory extends DefaultListableBeanFactory {
    public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
        // 1 加载数据前,还有一个调用父类构造函数初始化的过程,主要是忽略给定接口的自动装配功能。详见如下代码及备注
        super(parentBeanFactory);
        // 2 加载数据,见下个小节分析
        this.reader.loadBeanDefinitions(resource);
    }
}

public abstract class AbstractAutowireCapableBeanFactory{

    // 忽略给定接口的自动装配功能说明 
    public AbstractAutowireCapableBeanFactory() {
		super();
		/**
		 * Spring采用的是懒加载方式,当类 A 中有属性 B 时,在从容器中获取 A 对象时会查看属性是否初始化,没有的话会自动初始化 B。
		 * 然而有些情况下是不希望初始化属性 B 的。例如 B 实现了 BeanNameAware、BeanFactoryAware、
		 * BeanClassLoaderAware 接口(该接口作用是获取其在容器中的名称、容器、类加载器)。
		 */
		ignoreDependencyInterface(BeanNameAware.class);
		ignoreDependencyInterface(BeanFactoryAware.class);
		ignoreDependencyInterface(BeanClassLoaderAware.class);
	}
}

2.5.2 加载 Bean

 详见示例代码,主要是以下几个步骤。

    1. 封装资源文件,以及处理可能的编码问题 new EncodedResource(resource)
    1. 获取输入流;
    1. 根据前面参数,继续调用 doLoadBeanDefinitions,这块是核心逻辑
    • 3.1 获取对 XML 文件的验证模式。
    • 3.2 加载 XML 文件,并得到对应的 Document。
    • 3.3 根据返回的 Document 注册 Bean 信息。
封装资源文件代码
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
    @Override
	public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
		// 1. 封装资源文件,以及处理可能的编码问题 new EncodedResource(resource):
		return loadBeanDefinitions(new EncodedResource(resource));
	}
}

public class EncodedResource implements InputStreamSource {
    // 设置编码属性,后续 Spring 会使用相应的编码作为输入流的编码
    public Reader getReader() throws IOException {
		if (this.charset != null) {
			return new InputStreamReader(this.resource.getInputStream(), this.charset);
		}
		else if (this.encoding != null) {
			return new InputStreamReader(this.resource.getInputStream(), this.encoding);
		}
	}
}
获取输入流、doLoadBeanDefinitions
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
    public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        // 2 获取输入流
		try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
			// org.xml.sax.InputSource
			InputSource inputSource = new InputSource(inputStream);
			if (encodedResource.getEncoding() != null) {
				inputSource.setEncoding(encodedResource.getEncoding());
			}
			// 3 根据前面参数,继续调用 doLoadBeanDefinitions,这块是核心逻辑
			return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
		}
    }

    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {

        // 3.1 获取对 XML 文件的验证模式。
        // 3.2 加载 XML 文件,并得到对应的 Document。
        Document doc = doLoadDocument(inputSource, resource);

        // 3.3 根据返回的 Document 注册 Bean 信息。
        int count = registerBeanDefinitions(doc, resource);
        if (logger.isDebugEnabled()) {
            logger.debug("Loaded " + count + " bean definitions from " + resource);
        }
        return count;
		
	}
}

 其中核心逻辑的 3 个步骤详细描述如下。

2.6 获取 XML 的验证模式

2.6.1 DTD 与 XSD 区别

 略。

2.6.2 验证模式的读取

验证模式的读取代码
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
    protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
		// 3.1 获取对 XML 文件的验证模式。
		// 3.2 加载 XML 文件,并得到对应的 Document。
		// loadDocument 委托给了 DocumentLoader 去执行,真正调用的是其子类,DefaultDocumentLoader。注意 getEntityResolver() 方法
		return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
				getValidationModeForResource(resource), isNamespaceAware());
	}

    // 3.1 获取对 XML 文件的验证模式。
    protected int getValidationModeForResource(Resource resource) {
		int validationModeToUse = getValidationMode();
		// 1 如果手动指定了验证模式则使用指定的验证模式
		if (validationModeToUse != VALIDATION_AUTO) {
			return validationModeToUse;
		}
		// 2 如果未指定则使用自动检测,将自动检测验证模式的工作委托给了专门处理类 XmlValidationModeDetector
		int detectedMode = detectValidationMode(resource);
		if (detectedMode != VALIDATION_AUTO) {
			return detectedMode;
		}
		return VALIDATION_XSD;
	}
}

2.7 获取 Document

loadDocument 代码
public class DefaultDocumentLoader implements DocumentLoader {

    // 3.2 加载 XML 文件,并得到对应的 Document。
    @Override
	public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
			ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

		DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
		if (logger.isTraceEnabled()) {
			logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
		}
		DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
		return builder.parse(inputSource);
	}
}

 整个 SAX 解析 XML 文档的套路都是差不多的,这里也不展开讨论,主要是注意下 EntityResolver 参数。

2.7.1 EntityResolver 用法

EntityResolver 用法

 简单来说与自定义标签和默认标签的解析验证有关,比如联网下载 xsd 会很慢,因此内置了一套处理逻辑,当然也可以自定义处理逻辑。
 具体不展开了,可以参考 EntityResolver 作用open in new window,以下是 XSD 格式的 Spring 配置文件。

<?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 https://www.springframework.org/schema/beans/spring-beans.xsd">

 Spring 中使用 DelegatingEntityResolver 类为 EntityResolver 的实现类,resolveEntity 示例代码见 DelegatingEntityResolver.resolveEntity

2.8 解析并注册 BeanDefinitions

解析并注册 BeanDefinitions 代码
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {

    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
		// 使用 DefaultBeanDefinitionDocumentReader 实例化 BeanDefinitionDocumentReader
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
		// 在实例化 BeanDefinitionReader 时候会将 BeanDefinitionRegistry 传入,默认使用继承自 DefaultListableBeanFactory 的子类
		// 记录统计前 BeanDefinition 的加载个数
		int countBefore = getRegistry().getBeanDefinitionCount();
		// 重点:加载及注册 bean 的核心处理逻辑
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
		// 记录本次加载的 BeanDefinition 个数
		return getRegistry().getBeanDefinitionCount() - countBefore;
	}
}

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

    protected void doRegisterBeanDefinitions(Element root) {
		// 专门处理解析
		BeanDefinitionParserDelegate parent = this.delegate;
		this.delegate = createDelegate(getReaderContext(), root, parent);

		if (this.delegate.isDefaultNamespace(root)) {
			// 处理 profile 属性,注意:没有该 profile 时,是直接 return 掉的
			String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
			if (StringUtils.hasText(profileSpec)) {
				String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
						profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
				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;
				}
			}
		}

		// 解析前处理,留给子类实现
		preProcessXml(root);
		// 重点:解析并注册 BeanDefinition 的核心处理逻辑
		parseBeanDefinitions(root, this.delegate);
		// 解析后处理,留给子类实现
		postProcessXml(root);

		this.delegate = parent;
	}
}

2.8.1 profile 属性的使用

 Spring 多环境支持,略。

2.8.2 解析并注册 BeanDefinition

解析并注册 BeanDefinition 代码
public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {
    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        // 对 beans 的处理
        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)) {
                        // SPRING 对 默认标签 beans 的处理
                        parseDefaultElement(ele, delegate);
                    }
                    else {
                        // SPRING 对 自定义标签 beans 的处理
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        }
        else {
            // SPRING 对 自定义标签 beans 的处理
            delegate.parseCustomElement(root);
        }
    }
}
Last Updated 2/23/2025, 10:08:46 PM