IOC初始化的过程

[TOC]

1.IOC的理解

如何理解“控制反转”好呢?理解好它的关键在于我们需要回答如下四个问题:

  1. 谁控制谁:在传统的开发模式下,我们都是采用直接 new 一个对象的方式来创建对象,也就是说你依赖的对象直接由你自己控制,但是有了 IoC 容器后,则直接由 IoC 容器来控制。所以“谁控制谁”,当然是 IoC 容器控制对象
  2. 控制什么:控制对象。
  3. 为何是反转:没有 IoC 的时候我们都是在自己对象中主动去创建被依赖的对象,这是正转。但是有了 IoC 后,所依赖的对象直接由 IoC 容器创建后注入到被注入的对象中,依赖的对象由原来的主动获取变成被动接受,所以是反转。
  4. 哪些方面反转了:所依赖对象的获取被反转了。

在没有引入 IoC 的时候,被注入的对象直接依赖于被依赖的对象,有了 IoC 后,两者及其他们的关系都是通过 Ioc Service Provider 来统一管理维护的。被注入的对象需要什么,后者就会把相应的被依赖对象注入到被注入的对象中,从而达到 IoC Service Provider 为被注入对象服务的目的。

1
2
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
HelloWorld obj = (HelloWorld) context.getBean("helloWorld");

org.springframework.context.ApplicationContext ,这个就是大名鼎鼎的 Spring 容器,它叫做应用上下文,与我们应用息息相关。它继承 BeanFactory ,所以它是 BeanFactory 的扩展升级版,如果BeanFactory 是屌丝的话,那么 ApplicationContext 则是名副其实的高富帅。由于 ApplicationContext 的结构就决定了它与 BeanFactory 的不同,其主要区别有:

IoC 咋一看还是挺简单的,无非就是将配置文件(暂且认为是 xml 文件)进行解析(分析 xml 谁不会啊),然后放到一个 Map 里面就差不多了,初看有道理,其实要面临的问题还是有很多的,下面就劳烦各位看客跟着 LZ 博客来一步一步揭开 Spring IoC 的神秘面纱。

2.统一资源加载策略

img

  • FileSystemResource :对 java.io.File 类型资源的封装,只要是跟 File 打交道的,基本上与 FileSystemResource 也可以打交道。支持文件和 URL 的形式,实现 WritableResource 接口,且从 Spring Framework 5.0 开始,FileSystemResource 使用 NIO2 API进行读/写交互。
  • ByteArrayResource :对字节数组提供的数据的封装。如果通过 InputStream 形式访问该类型的资源,该实现会根据字节数组的数据构造一个相应的 ByteArrayInputStream。
  • UrlResource :对 java.net.URL类型资源的封装。内部委派 URL 进行具体的资源操作。
  • ClassPathResource :class path 类型资源的实现。使用给定的 ClassLoader 或者给定的 Class 来加载资源。
  • InputStreamResource :将给定的 InputStream 作为一种资源的 Resource 的实现类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public abstract class AbstractResource implements Resource {

/**
* 判断文件是否存在,若判断过程产生异常(因为会调用SecurityManager来判断),就关闭对应的流
*/
@Override
public boolean exists() {
try {
// 基于 File 进行判断
return getFile().exists();
}
catch (IOException ex) {
// Fall back to stream existence: can we open the stream?
// 基于 InputStream 进行判断
try {
InputStream is = getInputStream();
is.close();
return true;
} catch (Throwable isEx) {
return false;
}
}
}
/**
* 直接返回true,表示可读
*/
@Override
public boolean isReadable() {
return true;
}
...

}

img

1
2
3
4
5
6
7
8
9
public interface ResourceLoader {

String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX; // CLASSPATH URL 前缀。默认为:"classpath:"

Resource getResource(String location);

ClassLoader getClassLoader();

}

getResource(String location) 支持以下模式的资源加载:

    • URL位置资源,如 “file:C:/test.dat” 。
    • ClassPath位置资源,如 “classpath:test.dat 。
    • 相对路径资源,如 “WEB-INF/test.dat” ,此时返回的Resource 实例,根据实现不同而不同。
  • getClassLoader()方法的主要实现是在其子类 DefaultResourceLoader 中实现,具体过程我们在分析 DefaultResourceLoader 时做详细说明。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// DefaultResourceLoader.java

@Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");

// 首先,通过 ProtocolResolver 来加载资源
for (ProtocolResolver protocolResolver : this.protocolResolvers) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
// 其次,以 / 开头,返回 ClassPathContextResource 类型的资源
if (location.startsWith("/")) {
return getResourceByPath(location);
// 再次,以 classpath: 开头,返回 ClassPathResource 类型的资源
} else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
// 然后,根据是否为文件 URL ,是则返回 FileUrlResource 类型的资源,否则返回 UrlResource 类型的资源
} else {
try {
// Try to parse the location as a URL...
URL url = new URL(location);
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
} catch (MalformedURLException ex) {
// 最后,返回 ClassPathContextResource 类型的资源
// No URL -> resolve as resource path.
return getResourceByPath(location);
}
}
}

3.加载 BeanDefinition

1
2
3
4
ClassPathResource resource = new ClassPathResource("bean.xml"); // <1>
DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); // <2>
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); // <3>
reader.loadBeanDefinitions(resource); // <4>

这段代码是 Spring 中编程式使用 IoC 容器,通过这四段简单的代码,我们可以初步判断 IoC 容器的使用过程。

  1. 获取资源
  2. 获取 BeanFactory
  3. 根据新建的 BeanFactory 创建一个 BeanDefinitionReader 对象,该 Reader 对象为资源的解析器
  4. 装载资源

整个过程就分为三个步骤:资源定位、装载、注册,如下:

  • 资源定位。我们一般用外部资源来描述 Bean 对象,所以在初始化 IoC 容器的第一步就是需要定位这个外部资源。在上一篇博客(《【死磕 Spring】—— IoC 之 Spring 统一资源加载策略》)已经详细说明了资源加载的过程。

  • 装载。装载就是 BeanDefinition 的载入。BeanDefinitionReader 读取、解析 Resource 资源,也就是将用户定义的 Bean 表示成 IoC 容器的内部数据结构:BeanDefinition 。

    • 在 IoC 容器内部维护着一个 BeanDefinition Map 的数据结构
    • 在配置文件中每一个 <bean> 都对应着一个 BeanDefinition 对象。
  • 注册。向 IoC 容器注册在第二步解析好的 BeanDefinition,这个过程是通过 BeanDefinitionRegistry 接口来实现的。在 IoC 容器内部其实是将第二个过程解析得到的 BeanDefinition 注入到一个 HashMap 容器中,IoC 容器就是通过这个 HashMap 来维护这些 BeanDefinition 的。

    • 在这里需要注意的一点是这个过程并没有完成依赖注入(Bean 创建),Bean 创建是发生在应用第一次调用 #getBean(...) 方法,向容器索要 Bean 时。
    • 当然我们可以通过设置预处理,即对某个 Bean 设置 lazyinit = false 属性,那么这个 Bean 的依赖注入就会在容器初始化的时候完成。

    简单的说,上面步骤的结果是,XML Resource => XML Document => Bean Definition 。

3.1loadBeanDefinitions

1
2
3
4
5
// XmlBeanDefinitionReader.java
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
  • 从指定的 xml 文件加载 Bean Definition ,这里会先对 Resource 资源封装成 org.springframework.core.io.support.EncodedResource 对象。这里为什么需要将 Resource 封装成 EncodedResource 呢?主要是为了对 Resource 进行编码,保证内容读取的正确性。
  • 然后,再调用 #loadBeanDefinitions(EncodedResource encodedResource) 方法,执行真正的逻辑实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* 当前线程,正在加载的 EncodedResource 集合。
*/
private final ThreadLocal<Set<EncodedResource>> resourcesCurrentlyBeingLoaded = new NamedThreadLocal<>("XML bean definition resources currently being loaded");

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

// <1> 获取已经加载过的资源
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) { // 将当前资源加入记录中。如果已存在,抛出异常
throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
// <2> 从 EncodedResource 获取封装的 Resource ,并从 Resource 中获取其中的 InputStream
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) { // 设置编码
inputSource.setEncoding(encodedResource.getEncoding());
}
// 核心逻辑部分,执行加载 BeanDefinition
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
} finally {
inputStream.close();
}
} catch (IOException ex) {
throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), ex);
} finally {
// 从缓存中剔除该资源 <3>
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
  • <1>处,通过resourcesCurrentlyBeingLoaded.get()代码,来获取已经加载过的资源,然后将encodedResource加入其中,如果esourcesCurrentlyBeingLoaded 中已经存在该资源,则抛出 BeanDefinitionStoreException 异常。

    • 为什么需要这么做呢?答案在 "Detected cyclic loading" ,避免一个 EncodedResource 在加载时,还没加载完成,又加载自身,从而导致死循环
    • 也因此,在 <3> 处,当一个 EncodedResource 加载完成后,需要从缓存中剔除。
  • <2> 处理,从 encodedResource 获取封装的 Resource 资源,并从 Resource 中获取相应的 InputStream ,然后将 InputStream 封装为 InputSource ,最后调用 #doLoadBeanDefinitions(InputSource inputSource, Resource resource) 方法,执行加载 Bean Definition 的真正逻辑。

3.2 doLoadBeanDefinitions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* Actually load bean definitions from the specified XML file.
* @param inputSource the SAX InputSource to read from
* @param resource the resource descriptor for the XML file
* @return the number of bean definitions found
* @throws BeanDefinitionStoreException in case of loading or parsing errors
* @see #doLoadDocument
* @see #registerBeanDefinitions
*/
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
// <1> 获取 XML Document 实例
Document doc = doLoadDocument(inputSource, resource);
// <2> 根据 Document 实例,注册 Bean 信息
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
} catch (BeanDefinitionStoreException ex) {
throw ex;
} catch (SAXParseException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
} catch (SAXException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"XML document from " + resource + " is invalid", ex);
} catch (ParserConfigurationException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Parser configuration exception parsing XML from " + resource, ex);
} catch (IOException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"IOException parsing XML document from " + resource, ex);
} catch (Throwable ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Unexpected exception parsing XML document from " + resource, ex);
}
}
  • <1> 处,调用 #doLoadDocument(InputSource inputSource, Resource resource) 方法,根据 xml 文件,获取 Document 实例。
  • <2> 处,调用 #registerBeanDefinitions(Document doc, Resource resource) 方法,根据获取的 Document 实例,注册 Bean 信息。

3.2.1 doLoadDocument

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 获取 XML Document 实例
*
* Actually load the specified document using the configured DocumentLoader.
* @param inputSource the SAX InputSource to read from
* @param resource the resource descriptor for the XML file
* @return the DOM Document
* @throws Exception when thrown from the DocumentLoader
* @see #setDocumentLoader
* @see DocumentLoader#loadDocument
*/
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
  1. 调用 #getValidationModeForResource(Resource resource) 方法,获取指定资源(xml)的验证模式。详细解析,见 《【死磕 Spring】—— IoC 之获取验证模型》
  2. 调用 DocumentLoader#loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) 方法,获取 XML Document 实例。详细解析,见 《【死磕 Spring】—— IoC 之获取 Document 对象》

3.2.2 registerBeanDefinitions

该方法的详细解析,见 《【死磕 Spring】—— IoC 之注册 BeanDefinition》

4.获取验证模型

#doLoadBeanDefinitions(InputSource inputSource, Resource resource) 方法中,中主要是做三件事情:

  1. 调用 #getValidationModeForResource(Resource resource) 方法,获取指定资源(xml)的验证模式
  2. 调用 DocumentLoader#loadDocument(InputSource inputSource, EntityResolver entityResolver,ErrorHandler errorHandler, int validationMode, boolean namespaceAware) 方法,获取 XML Document 实例。
  3. 调用 #registerBeanDefinitions(Document doc, Resource resource) 方法,根据获取的 Document 实例,注册 Bean 信息。

DTD(Document Type Definition),即文档类型定义,为 XML 文件的验证机制,属于 XML 文件中组成的一部分。

1. getValidationModeForResource

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// XmlBeanDefinitionReader.java

// 禁用验证模式
public static final int VALIDATION_NONE = XmlValidationModeDetector.VALIDATION_NONE;
// 自动获取验证模式
public static final int VALIDATION_AUTO = XmlValidationModeDetector.VALIDATION_AUTO;
// DTD 验证模式
public static final int VALIDATION_DTD = XmlValidationModeDetector.VALIDATION_DTD;
// XSD 验证模式
public static final int VALIDATION_XSD = XmlValidationModeDetector.VALIDATION_XSD;

/**
* 验证模式。默认为自动模式。
*/
private int validationMode = VALIDATION_AUTO;

protected int getValidationModeForResource(Resource resource) {
// <1> 获取指定的验证模式
int validationModeToUse = getValidationMode();
// 首先,如果手动指定,则直接返回
if (validationModeToUse != VALIDATION_AUTO) {
return validationModeToUse;
}
// 其次,自动获取验证模式
int detectedMode = detectValidationMode(resource);
if (detectedMode != VALIDATION_AUTO) {
return detectedMode;
}
// 最后,使用 VALIDATION_XSD 做为默认
// Hmm, we didn't get a clear indication... Let's assume XSD,
// since apparently no DTD declaration has been found up until
// detection stopped (before finding the document's root tag).
return VALIDATION_XSD;
}
  • <1> 处,调用 #getValidationMode() 方法,获取指定的验证模式( validationMode )。如果有手动指定,则直接返回。

  • <2> 处,调用 #detectValidationMode(Resource resource) 方法,自动获取验证模式。代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /**
    * XML 验证模式探测器
    */
    ...
    // <x> 获取相应的验证模式
    try {
    return this.validationModeDetector.detectValidationMode(inputStream);
    } catch (IOException ex) {
    throw new BeanDefinitionStoreException("Unable to determine validation mode for [" +
    resource + "]: an error occurred whilst reading from the InputStream.", ex);
    }
    }
    • 核心在于 <x> 处,调用 XmlValidationModeDetector#detectValidationMode(InputStream inputStream) 方法,获取相应的验证模式。详细解析,见 「2. XmlValidationModeDetector」 中。
  • <3> 处,使用 VALIDATION_XSD 做为默认。

2. XmlValidationModeDetector

org.springframework.util.xml.XmlValidationModeDetector ,XML 验证模式探测器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public int detectValidationMode(InputStream inputStream) throws IOException {
// Peek into the file to look for DOCTYPE.
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
try {
// 是否为 DTD 校验模式。默认为,非 DTD 模式,即 XSD 模式
boolean isDtdValidated = false;
String content;
// <0> 循环,逐行读取 XML 文件的内容
while ((content = reader.readLine()) != null) {
content = consumeCommentTokens(content);
// 跳过,如果是注释,或者
if (this.inComment || !StringUtils.hasText(content)) {
continue;
}
// <1> 包含 DOCTYPE 为 DTD 模式
if (hasDoctype(content)) {
isDtdValidated = true;
break;
}
// <2> hasOpeningTag 方法会校验,如果这一行有 < ,并且 < 后面跟着的是字母,则返回 true 。
if (hasOpeningTag(content)) {
// End of meaningful data...
break;
}
}
// 返回 VALIDATION_DTD or VALIDATION_XSD 模式
return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
} catch (CharConversionException ex) {

// <3> 返回 VALIDATION_AUTO 模式
// Choked on some character encoding...
// Leave the decision up to the caller.
return VALIDATION_AUTO;
} finally {
reader.close();
}
}
  • <0> 处,从代码中看,主要是通过读取 XML 文件的内容,来进行自动判断。

  • <1> 处,调用 #hasDoctype(String content) 方法,判断内容中如果包含有 "DOCTYPE“ ,则为 DTD 验证模式。代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    /**
    * The token in a XML document that declares the DTD to use for validation
    * and thus that DTD validation is being used.
    */
    private static final String DOCTYPE = "DOCTYPE";

    private boolean hasDoctype(String content) {
    return content.contains(DOCTYPE);
    }
  • <2> 处,调用 #hasOpeningTag(String content) 方法,判断如果这一行包含 < ,并且 < 紧跟着的是字幕,则为 XSD 验证模式。代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /**
    * Does the supplied content contain an XML opening tag. If the parse state is currently
    * in an XML comment then this method always returns false. It is expected that all comment
    * tokens will have consumed for the supplied content before passing the remainder to this method.
    */
    private boolean hasOpeningTag(String content) {
    if (this.inComment) {
    return false;
    }
    int openTagIndex = content.indexOf('<');
    return (openTagIndex > -1 // < 存在
    && (content.length() > openTagIndex + 1) // < 后面还有内容
    && Character.isLetter(content.charAt(openTagIndex + 1))); // < 后面的内容是字幕
    }
  • <3> 处,如果发生 CharConversionException 异常,则为 VALIDATION_AUTO 模式。

  • 关于 #consumeCommentTokens(String content) 方法,代码比较复杂。感兴趣的胖友可以看看。代码如下:

5.获取document对象

XmlBeanDefinitionReader#doLoadDocument(InputSource inputSource, Resource resource) 方法,中做了两件事情:

  • 调用#getValidationModeForResource(Resource resource)方法,获取指定资源(xml)的验证模式

    • 上篇博客,我们已经详细解析。
  • 调用DocumentLoader#loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware)方法,获取 XML Document 实例。

1. DocumentLoader

获取 Document 的策略,由接口 org.springframework.beans.factory.xml.DocumentLoader 定义。代码如下:

FROM 《Spring 源码深度解析》P16 页

定义从资源文件加载到转换为 Document 的功能。

1
2
3
4
5
6
7
8
public interface DocumentLoader {

Document loadDocument(
InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware)
throws Exception;

}
  • inputSource 方法参数,加载 Document 的 Resource 资源。
  • entityResolver 方法参数,解析文件的解析器。
  • errorHandler 方法参数,处理加载 Document 对象的过程的错误。
  • validationMode 方法参数,验证模式。
  • namespaceAware 方法参数,命名空间支持。如果要提供对 XML 名称空间的支持,则需要值为 true

1.1 DefaultDocumentLoader

该方法由 DocumentLoader 的默认实现类 org.springframework.beans.factory.xml.DefaultDocumentLoader 实现。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* Load the {@link Document} at the supplied {@link InputSource} using the standard JAXP-configured
* XML parser.
*/
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
// <1> 创建 DocumentBuilderFactory
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isTraceEnabled()) {
logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
}
// <2> 创建 DocumentBuilder
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
// <3> 解析 XML InputSource 返回 Document 对象
return builder.parse(inputSource);
}
  • 首先,调用 #createDocumentBuilderFactory(...) 方法,创建 javax.xml.parsers.DocumentBuilderFactory 对象。代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    /**
    * JAXP attribute used to configure the schema language for validation.
    */
    private static final String SCHEMA_LANGUAGE_ATTRIBUTE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
    /**
    * JAXP attribute value indicating the XSD schema language.
    */
    private static final String XSD_SCHEMA_LANGUAGE = "http://www.w3.org/2001/XMLSchema";
    protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)
    throws ParserConfigurationException {
    // 创建 DocumentBuilderFactory
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    factory.setNamespaceAware(namespaceAware); // 设置命名空间支持
    if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {
    factory.setValidating(true); // 开启校验
    // XSD 模式下,设置 factory 的属性
    if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
    // Enforce namespace aware for XSD...
    factory.setNamespaceAware(true); // XSD 模式下,强制设置命名空间支持
    // 设置 SCHEMA_LANGUAGE_ATTRIBUTE
    try {
    factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);
    } catch (IllegalArgumentException ex) {
    ParserConfigurationException pcex = new ParserConfigurationException(
    "Unable to validate using XSD: Your JAXP provider [" + factory +
    "] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " +
    "Upgrade to Apache Xerces (or Java 1.5) for full XSD support.");
    pcex.initCause(ex);
    throw pcex;
    }
    }
    }
    return factory;
    }
  • 然后,调用 #createDocumentBuilder(DocumentBuilderFactory factory, EntityResolver entityResolver,ErrorHandler errorHandler) 方法,创建 javax.xml.parsers.DocumentBuilder 对象。代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory,
    @Nullable EntityResolver entityResolver, @Nullable ErrorHandler errorHandler)
    throws ParserConfigurationException {
    // 创建 DocumentBuilder 对象
    DocumentBuilder docBuilder = factory.newDocumentBuilder();
    // <x> 设置 EntityResolver 属性
    if (entityResolver != null) {
    docBuilder.setEntityResolver(entityResolver);
    }
    // 设置 ErrorHandler 属性
    if (errorHandler != null) {
    docBuilder.setErrorHandler(errorHandler);
    }
    return docBuilder;
    }
    • <x> 处,设置 DocumentBuilder 的 EntityResolver 属性。关于它,在 「2. EntityResolver」 会详细解析。
  • 最后,调用 DocumentBuilder#parse(InputSource) 方法,解析 InputSource ,返回 Document 对象。

2. EntityResolver

也就是说,对于解析一个xml,sax
首先会读取该xml文档上的声明,根据声明去寻找相应的dtd定义,以便对文档的进行验证,
默认的寻找规则,(即:通过网络,实现上就是声明DTD的地址URI地址来下载DTD声明),
并进行认证,下载的过程是一个漫长的过程,而且当网络不可用时,这里会报错,就是因为相应的dtd没找到,

通过 DocumentLoader#loadDocument(...) 方法来获取 Document 对象时,有一个方法参数 entityResolver 。该参数是通过 XmlBeanDefinitionReader#getEntityResolver() 方法来获取的。代码如下:

#getEntityResolver() 方法,返回指定的解析器,如果没有指定,则构造一个未指定的默认解析器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// XmlBeanDefinitionReader.java

/**
* EntityResolver 解析器
*/
@Nullable
private EntityResolver entityResolver;

protected EntityResolver getEntityResolver() {
if (this.entityResolver == null) {
// Determine default EntityResolver to use.
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader != null) {
this.entityResolver = new ResourceEntityResolver(resourceLoader);
} else {
this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
}
}
return this.entityResolver;
}
  • 如果 ResourceLoader 不为 null,则根据指定的 ResourceLoader 创建一个 ResourceEntityResolver 对象。
  • 如果 ResourceLoader 为 null ,则创建 一个 DelegatingEntityResolver 对象。该 Resolver 委托给默认的 BeansDtdResolver 和 PluggableSchemaResolver 。

2.1 子类

上面的方法,一共涉及四个 EntityResolver 的子类:

  • org.springframework.beans.factory.xm.BeansDtdResolver :实现 EntityResolver 接口,Spring Bean dtd 解码器,用来从 classpath 或者 jar 文件中加载 dtd 。部分代码如下:

    1
    2
    3
    private static final String DTD_EXTENSION = ".dtd";

    private static final String DTD_NAME = "spring-beans";
  • org.springframework.beans.factory.xml.PluggableSchemaResolver ,实现 EntityResolver 接口,读取 classpath 下的所有 "META-INF/spring.schemas" 成一个 namespaceURI 与 Schema 文件地址的 map 。代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    /**
    * The location of the file that defines schema mappings.
    * Can be present in multiple JAR files.
    *
    * 默认 {@link #schemaMappingsLocation} 地址
    */
    public static final String DEFAULT_SCHEMA_MAPPINGS_LOCATION = "META-INF/spring.schemas";

    @Nullable
    private final ClassLoader classLoader;

    /**
    * Schema 文件地址
    */
    private final String schemaMappingsLocation;

    /** Stores the mapping of schema URL -> local schema path. */
    @Nullable
    private volatile Map<String, String> schemaMappings; // namespaceURI 与 Schema 文件地址的映射集合
  • org.springframework.beans.factory.xml.DelegatingEntityResolver :实现 EntityResolver 接口,分别代理 dtd 的 BeansDtdResolver 和 xml schemas 的 PluggableSchemaResolver 。代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    /** Suffix for DTD files. */
    public static final String DTD_SUFFIX = ".dtd";

    /** Suffix for schema definition files. */
    public static final String XSD_SUFFIX = ".xsd";

    private final EntityResolver dtdResolver;

    private final EntityResolver schemaResolver;

    // 默认
    public DelegatingEntityResolver(@Nullable ClassLoader classLoader) {
    this.dtdResolver = new BeansDtdResolver();
    this.schemaResolver = new PluggableSchemaResolver(classLoader);
    }

    // 自定义
    public DelegatingEntityResolver(EntityResolver dtdResolver, EntityResolver schemaResolver) {
    Assert.notNull(dtdResolver, "'dtdResolver' is required");
    Assert.notNull(schemaResolver, "'schemaResolver' is required");
    this.dtdResolver = dtdResolver;
    this.schemaResolver = schemaResolver;
    }
  • org.springframework.beans.factory.xml.ResourceEntityResolver :继承自 DelegatingEntityResolver 类,通过 ResourceLoader 来解析实体的引用。代码如下:

    1
    2
    3
    4
    5
    6
    private final ResourceLoader resourceLoader;

    public ResourceEntityResolver(ResourceLoader resourceLoader) {
    super(resourceLoader.getClassLoader());
    this.resourceLoader = resourceLoader;
    }

2.2 作用

EntityResolver 的作用就是,通过实现它,应用可以自定义如何寻找【验证文件】的逻辑。


FROM 《Spring 源码深度解析》

在 loadDocument 方法中涉及一个参数 EntityResolver ,何为EntityResolver?官网这样解释:如果 SAX 应用程序需要实现自定义处理外部实体,则必须实现此接口并使用 setEntityResolver 方法向SAX 驱动器注册一个实例。也就是说,对于解析一个XML,SAX 首先读取该 XML 文档上的声明,根据声明去寻找相应的 DTD 定义,以便对文档进行一个验证。默认的寻找规则,即通过网络(实现上就是声明的DTD的URI地址)来下载相应的DTD声明,并进行认证。下载的过程是一个漫长的过程,而且当网络中断或不可用时,这里会报错,就是因为相应的DTD声明没有被找到的原因。

EntityResolver 的作用是项目本身就可以提供一个如何寻找 DTD 声明的方法,即由程序来实现寻找 DTD 声明的过程,比如我们将 DTD 文件放到项目中某处,在实现时直接将此文档读取并返回给 SAX 即可。这样就避免了通过网络来寻找相应的声明。

org.xml.sax.EntityResolver 接口,代码如下:

1
2
3
4
5
6
public interface EntityResolver {

public abstract InputSource resolveEntity (String publicId, String systemId)
throws SAXException, IOException;

}

接口方法接收两个参数 publicIdsystemId ,并返回 InputSource 对象。两个参数声明如下:

  • publicId :被引用的外部实体的公共标识符,如果没有提供,则返回 null
  • systemId :被引用的外部实体的系统标识符。

这两个参数的实际内容和具体的验证模式的关系如下:

2.3 DelegatingEntityResolver

我们知道在 Spring 中使用 DelegatingEntityResolver 为 EntityResolver 的实现类。#resolveEntity(String publicId, String systemId) 方法,实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
@Nullable
public InputSource resolveEntity(String publicId, @Nullable String systemId) throws SAXException, IOException {
if (systemId != null) {
// DTD 模式
if (systemId.endsWith(DTD_SUFFIX)) {
return this.dtdResolver.resolveEntity(publicId, systemId);
// XSD 模式
} else if (systemId.endsWith(XSD_SUFFIX)) {
return this.schemaResolver.resolveEntity(publicId, systemId);
}
}
return null;
}
  • 如果是 DTD 验证模式,则使用 BeansDtdResolver 来进行解析
  • 如果是 XSD 验证模式,则使用 PluggableSchemaResolver 来进行解析。

2.4 BeansDtdResolver

BeansDtdResolver 的解析过程,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
* DTD 文件的后缀
*/
private static final String DTD_EXTENSION = ".dtd";
/**
* Spring Bean DTD 的文件名
*/
private static final String DTD_NAME = "spring-beans";

@Override
@Nullable
public InputSource resolveEntity(String publicId, @Nullable String systemId) throws IOException {
if (logger.isTraceEnabled()) {
logger.trace("Trying to resolve XML entity with public ID [" + publicId +
"] and system ID [" + systemId + "]");
}
// 必须以 .dtd 结尾
if (systemId != null && systemId.endsWith(DTD_EXTENSION)) {
// 获取最后一个 / 的位置
int lastPathSeparator = systemId.lastIndexOf('/');
// 获取 spring-beans 的位置
int dtdNameStart = systemId.indexOf(DTD_NAME, lastPathSeparator);
if (dtdNameStart != -1) { // 找到
String dtdFile = DTD_NAME + DTD_EXTENSION;
if (logger.isTraceEnabled()) {
logger.trace("Trying to locate [" + dtdFile + "] in Spring jar on classpath");
}
try {
// 创建 ClassPathResource 对象
Resource resource = new ClassPathResource(dtdFile, getClass());
// 创建 InputSource 对象,并设置 publicId、systemId 属性
InputSource source = new InputSource(resource.getInputStream());
source.setPublicId(publicId);
source.setSystemId(systemId);
if (logger.isTraceEnabled()) {
logger.trace("Found beans DTD [" + systemId + "] in classpath: " + dtdFile);
}
return source;
}
catch (IOException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not resolve beans DTD [" + systemId + "]: not found in classpath", ex);
}
}
}
}

// 使用默认行为,从网络上下载
// Use the default behavior -> download from website or wherever.
return null;
}

从上面的代码中,我们可以看到,加载 DTD 类型的 BeansDtdResolver#resolveEntity(...) 过程,只是对 systemId 进行了简单的校验(从最后一个 / 开始,内容中是否包含 spring-beans),然后构造一个 InputSource 对象,并设置 publicIdsystemId 属性,然后返回。

2.5 PluggableSchemaResolver

PluggableSchemaResolver 的解析过程,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@Nullable
private final ClassLoader classLoader;

/**
* Schema 文件地址
*/
private final String schemaMappingsLocation;

/** Stores the mapping of schema URL -> local schema path. */
@Nullable
private volatile Map<String, String> schemaMappings; // namespaceURI 与 Schema 文件地址的映射集合

@Override
@Nullable
public InputSource resolveEntity(String publicId, @Nullable String systemId) throws IOException {
if (logger.isTraceEnabled()) {
logger.trace("Trying to resolve XML entity with public id [" + publicId +
"] and system id [" + systemId + "]");
}

if (systemId != null) {
// 获得 Resource 所在位置
String resourceLocation = getSchemaMappings().get(systemId);
if (resourceLocation != null) {
// 创建 ClassPathResource
Resource resource = new ClassPathResource(resourceLocation, this.classLoader);
try {
// 创建 InputSource 对象,并设置 publicId、systemId 属性
InputSource source = new InputSource(resource.getInputStream());
source.setPublicId(publicId);
source.setSystemId(systemId);
if (logger.isTraceEnabled()) {
logger.trace("Found XML schema [" + systemId + "] in classpath: " + resourceLocation);
}
return source;
}
catch (FileNotFoundException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not find XML schema [" + systemId + "]: " + resource, ex);
}
}
}
}
return null;
}
  • 首先调用 #getSchemaMappings() 方法,获取一个映射表(systemId 与其在本地的对照关系)。代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    private Map<String, String> getSchemaMappings() {
    Map<String, String> schemaMappings = this.schemaMappings;
    // 双重检查锁,实现 schemaMappings 单例
    if (schemaMappings == null) {
    synchronized (this) {
    schemaMappings = this.schemaMappings;
    if (schemaMappings == null) {
    if (logger.isTraceEnabled()) {
    logger.trace("Loading schema mappings from [" + this.schemaMappingsLocation + "]");
    }
    try {
    // 以 Properties 的方式,读取 schemaMappingsLocation
    Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader);
    if (logger.isTraceEnabled()) {
    logger.trace("Loaded schema mappings: " + mappings);
    }
    // 将 mappings 初始化到 schemaMappings 中
    schemaMappings = new ConcurrentHashMap<>(mappings.size());
    CollectionUtils.mergePropertiesIntoMap(mappings, schemaMappings);
    this.schemaMappings = schemaMappings;
    } catch (IOException ex) {
    throw new IllegalStateException(
    "Unable to load schema mappings from location [" + this.schemaMappingsLocation + "]", ex);
    }
    }
    }
    }
    return schemaMappings;
    }
    • 映射表如下(部分):映射表映射表
  • 然后,根据传入的 systemId 获取该 systemId 在本地的路径 resourceLocation

  • 最后,根据 resourceLocation ,构造 InputSource 对象。

2.6 ResourceEntityResolver

ResourceEntityResolver 的解析过程,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
private final ResourceLoader resourceLoader;

@Override
@Nullable
public InputSource resolveEntity(String publicId, @Nullable String systemId) throws SAXException, IOException {
// 调用父类的方法,进行解析
InputSource source = super.resolveEntity(publicId, systemId);
// 解析失败,resourceLoader 进行解析
if (source == null && systemId != null) {
// 获得 resourcePath ,即 Resource 资源地址
String resourcePath = null;
try {
String decodedSystemId = URLDecoder.decode(systemId, "UTF-8"); // 使用 UTF-8 ,解码 systemId
String givenUrl = new URL(decodedSystemId).toString(); // 转换成 URL 字符串
// 解析文件资源的相对路径(相对于系统根路径)
String systemRootUrl = new File("").toURI().toURL().toString();
// Try relative to resource base if currently in system root.
if (givenUrl.startsWith(systemRootUrl)) {
resourcePath = givenUrl.substring(systemRootUrl.length());
}
} catch (Exception ex) {
// Typically a MalformedURLException or AccessControlException.
if (logger.isDebugEnabled()) {
logger.debug("Could not resolve XML entity [" + systemId + "] against system root URL", ex);
}
// No URL (or no resolvable URL) -> try relative to resource base.
resourcePath = systemId;
}
if (resourcePath != null) {
if (logger.isTraceEnabled()) {
logger.trace("Trying to locate XML entity [" + systemId + "] as resource [" + resourcePath + "]");
}
// 获得 Resource 资源
Resource resource = this.resourceLoader.getResource(resourcePath);
// 创建 InputSource 对象
source = new InputSource(resource.getInputStream());
// 设置 publicId 和 systemId 属性
source.setPublicId(publicId);
source.setSystemId(systemId);
if (logger.isDebugEnabled()) {
logger.debug("Found XML entity [" + systemId + "]: " + resource);
}
}
}
return source;
}
  • 首先,调用父类的方法,进行解析。
  • 如果失败,使用 resourceLoader ,尝试读取 systemId 对应的 Resource 资源。

2.7 自定义 EntityResolver

老艿艿:本小节,为选读内容。

#getEntityResolver() 方法返回 EntityResolver 对象。那么怎么进行自定义 EntityResolver 呢?

If a SAX application needs to implement customized handling for external entities, it must implement this interface and register an instance with the SAX driver using the setEntityResolver method.

就是说:如果 SAX 应用程序需要实现自定义处理外部实体,则必须实现此接口,并使用 #setEntityResolver(EntityResolver entityResolver) 方法,向 SAX 驱动器注册一个 EntityResolver 实例。

示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyResolver implements EntityResolver {

@Override
public InputSource resolveEntity(String publicId, String systemId) {
if (systemId.equals("http://www.myhost.com/today")) {
MyReader reader = new MyReader();
return new InputSource(reader);
} else {
// use the default behaviour
return null;
}
}

}

我们首先将 "spring-student.xml" 文件中的 XSD 声明的地址改掉,如下:

spring-student.xmlspring-student.xml

如果我们再次运行,则会报如下错误:

报错报错

从上面的错误可以看到,是在进行文档验证时,无法根据声明找到 XSD 验证文件而导致无法进行 XML 文件验证。在 《【死磕 Spring】—— IoC 之获取验证模型》 中讲到,如果要解析一个 XML 文件,SAX 首先会读取该 XML 文档上的声明,然后根据声明去寻找相应的 DTD 定义,以便对文档进行验证。默认的加载规则是通过网络方式下载验证文件,而在实际生产环境中我们会遇到网络中断或者不可用状态,那么就应用就会因为无法下载验证文件而报错。

6.注册 BeanDefinitions

获取 XML Document 对象后,会根据该对象和 Resource 资源对象调用 XmlBeanDefinitionReader#registerBeanDefinitions(Document doc, Resource resource) 方法,开始注册 BeanDefinitions 之旅。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// AbstractBeanDefinitionReader.java
private final BeanDefinitionRegistry registry;

// XmlBeanDefinitionReader.java
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// <1> 创建 BeanDefinitionDocumentReader 对象
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
// <2> 获取已注册的 BeanDefinition 数量
int countBefore = getRegistry().getBeanDefinitionCount();
// <3> 创建 XmlReaderContext 对象
// <4> 注册 BeanDefinition
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
// 计算新注册的 BeanDefinition 数量
return getRegistry().getBeanDefinitionCount() - countBefore;
}
  • <1> 处,调用 #createBeanDefinitionDocumentReader() 方法,实例化 BeanDefinitionDocumentReader 对象。

FROM 《Spring 源码深度解析》P16 页

定义读取 Document 并注册 BeanDefinition 功能

  • <2> 处,调用 BeanDefinitionRegistry#getBeanDefinitionCount() 方法,获取已注册的 BeanDefinition 数量。
  • <3> 处,调用 #createReaderContext(Resource resource) 方法,创建 XmlReaderContext 对象。
  • <4> 处,调用 BeanDefinitionDocumentReader#registerBeanDefinitions(Document doc, XmlReaderContext readerContext) 方法,读取 XML 元素,注册 BeanDefinition 们。
  • <5> 处,计算新注册的 BeanDefinition 数量。

1. createBeanDefinitionDocumentReader

#createBeanDefinitionDocumentReader(),实例化 BeanDefinitionDocumentReader 对象。代码如下:

1
2
3
4
5
6
7
8
9
10
/**
* documentReader 的类
*
* @see #createBeanDefinitionDocumentReader()
*/
private Class<? extends BeanDefinitionDocumentReader> documentReaderClass = DefaultBeanDefinitionDocumentReader.class;

protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
return BeanUtils.instantiateClass(this.documentReaderClass);
}
  • documentReaderClass 的默认值为 DefaultBeanDefinitionDocumentReader.class 。关于它,我们在后续的文章,详细解析。

2. registerBeanDefinitions

BeanDefinitionDocumentReader#registerBeanDefinitions(Document doc, XmlReaderContext readerContext) 方法,注册 BeanDefinition ,在接口 BeanDefinitionDocumentReader 中定义。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface BeanDefinitionDocumentReader {

/**
* Read bean definitions from the given DOM document and
* register them with the registry in the given reader context.
* @param doc the DOM document
* @param readerContext the current context of the reader
* (includes the target registry and the resource being parsed)
* @throws BeanDefinitionStoreException in case of parsing errors
*/
void registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
throws BeanDefinitionStoreException;

}

从给定的 Document 对象中解析定义的 BeanDefinition 并将他们注册到注册表中。方法接收两个参数:

  • doc 方法参数:待解析的 Document 对象。
  • readerContext 方法,解析器的当前上下文,包括目标注册表和被解析的资源。它是根据 Resource 来创建的,见 「3. createReaderContext」

2.1 DefaultBeanDefinitionDocumentReader

BeanDefinitionDocumentReader 有且只有一个默认实现类 DefaultBeanDefinitionDocumentReader 。它对 #registerBeanDefinitions(...) 方法的实现代码如下:

DefaultBeanDefinitionDocumentReader 对该方法提供了实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
@Nullable
private XmlReaderContext readerContext;

@Nullable
private BeanDefinitionParserDelegate delegate;

/**
* This implementation parses bean definitions according to the "spring-beans" XSD
* (or DTD, historically).
* <p>Opens a DOM Document; then initializes the default settings
* specified at the {@code <beans/>} level; then parses the contained bean definitions.
*/
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
// 获得 XML Document Root Element
// 执行注册 BeanDefinition
doRegisterBeanDefinitions(doc.getDocumentElement());
}

/**
* Register each bean definition within the given root {@code <beans/>} element.
*/
@SuppressWarnings("deprecation") // for Environment.acceptsProfiles(String...)
protected void doRegisterBeanDefinitions(Element root) {
// Any nested <beans> elements will cause recursion in this method. In
// order to propagate and preserve <beans> default-* attributes correctly,
// keep track of the current (parent) delegate, which may be null. Create
// the new (child) delegate with a reference to the parent for fallback purposes,
// then ultimately reset this.delegate back to its original (parent) reference.
// this behavior emulates a stack of delegates without actually necessitating one.
// 记录老的 BeanDefinitionParserDelegate 对象
BeanDefinitionParserDelegate parent = this.delegate;
// <1> 创建 BeanDefinitionParserDelegate 对象,并进行设置到 delegate
this.delegate = createDelegate(getReaderContext(), root, parent);
// <2> 检查 <beans /> 根标签的命名空间是否为空,或者是 http://www.springframework.org/schema/beans
if (this.delegate.isDefaultNamespace(root)) {
// <2.1> 处理 profile 属性。可参见《Spring3自定义环境配置 <beans profile="">》http://nassir.iteye.com/blog/1535799
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
// <2.2> 使用分隔符切分,可能有多个 profile 。
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
// <2.3> 如果所有 profile 都无效,则不进行注册
// We cannot use Profiles.of(...) since profile expressions are not supported
// in XML config. See SPR-12458 for details.
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;
}
}
}

// <3> 解析前处理
preProcessXml(root);
// <4> 解析
parseBeanDefinitions(root, this.delegate);
// <5> 解析后处理
postProcessXml(root);

// 设置 delegate 回老的 BeanDefinitionParserDelegate 对象
this.delegate = parent;
}
  • <1> 处,创建 BeanDefinitionParserDelegate 对象,并进行设置到 delegate 。BeanDefinitionParserDelegate 是一个重要的类,它负责解析 BeanDefinition。代码如下:

    FROM 《Spring 源码深度解析》P16

    定义解析 XML Element 的各种方法

    1
    2
    3
    4
    5
    6
    7
    8
    protected BeanDefinitionParserDelegate createDelegate(
    XmlReaderContext readerContext, Element root, @Nullable BeanDefinitionParserDelegate parentDelegate) {
    // 创建 BeanDefinitionParserDelegate 对象
    BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);
    // 初始化默认
    delegate.initDefaults(root, parentDelegate);
    return delegate;
    }
  • <2> 处,检查 <beans /> 标签的命名空间是否为空,或者是 http://www.springframework.org/schema/beans

    • <2.1> 处,判断是否 <beans /> 上配置了 profile 属性。不了解这块的胖友,可以看下 《《Spring3自定义环境配置 》》
    • <2.2> 处,使用分隔符切分,可能有多个 profile 。
    • <2.3> 处,判断,如果所有 profile 都无效,则 return 不进行注册。
  • <4> 处,调用 #parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) 方法,进行解析逻辑。详细解析,见 「3.1 parseBeanDefinitions」

  • <3> / <5> 处,解析前后的处理,目前这两个方法都是空实现,交由子类来实现。代码如下:

    1
    2
    3
    protected void preProcessXml(Element root) {}

    protected void postProcessXml(Element root) {}
2.1.1 parseBeanDefinitions

#parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) 方法,进行解析逻辑。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* Parse the elements at the root level in the document:
* "import", "alias", "bean".
* @param root the DOM root element of the document
*/
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
// <1> 如果根节点使用默认命名空间,执行默认解析
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;
// <1> 如果该节点使用默认命名空间,执行默认解析
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
// 如果该节点非默认命名空间,执行自定义解析
} else {
delegate.parseCustomElement(ele);
}
}
}
// <2> 如果根节点非默认命名空间,执行自定义解析
} else {
delegate.parseCustomElement(root);
}
}
  • Spring 有

    两种

Bean 声明方式:

  • 配置文件式声明:<bean id="studentService" class="org.springframework.core.StudentService" /> 。对应 <1> 处。
  • 自定义注解方式:<tx:annotation-driven> 。对应 <2> 处。
  • <1> 处,如果节点或节点使用默认命名空间,调用 #parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) 方法,执行默认解析。代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { // import
    importBeanDefinitionResource(ele);
    } else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { // alias
    processAliasRegistration(ele);
    } else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { // bean
    processBeanDefinition(ele, delegate);
    } else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // beans
    // recurse
    doRegisterBeanDefinitions(ele);
    }
    }
    • 详细的解析,见后续文章。
  • <2> 处,如果节点或节点不使用默认命名空间,调用 BeanDefinitionParserDelegate#parseCustomElement(Element ele) 方法,执行自定义解析。详细的解析,见后续文章。

3. createReaderContext

#createReaderContext(Resource resource) 方法,创建 XmlReaderContext 对象。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private ProblemReporter problemReporter = new FailFastProblemReporter();

private ReaderEventListener eventListener = new EmptyReaderEventListener();

private SourceExtractor sourceExtractor = new NullSourceExtractor();

@Nullable
private NamespaceHandlerResolver namespaceHandlerResolver;

/**
* Create the {@link XmlReaderContext} to pass over to the document reader.
*/
public XmlReaderContext createReaderContext(Resource resource) {
return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
this.sourceExtractor, this, getNamespaceHandlerResolver());
}

关于 XmlReaderContext 的详细解析,见后续文章。

4. 小结

至此,XmlBeanDefinitionReader#doLoadBeanDefinitions(InputSource inputSource, Resource resource) 方法中,做的三件事情已经全部分析完毕,下面将对 BeanDefinition 的解析过程做详细分析说明。

另外,XmlBeanDefinitionReader#doLoadBeanDefinitions(InputSource inputSource, Resource resource) 方法,整体时序图如下:

image-20220716173642850

7.解析 import 标签

在博客 【死磕 Spring】—— IoC 之注册 BeanDefinitions 中分析到,Spring 中有两种解析 Bean 的方式:

  • 如果根节点或者子节点采用默认命名空间的话,则调用 #parseDefaultElement(...) 方法,进行默认标签解析
  • 否则,调用 BeanDefinitionParserDelegate#parseCustomElement(...) 方法,进行自定义解析。

所以,以下博客就这两个方法进行详细分析说明。而本文,先从默认标签解析过程开始。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// DefaultBeanDefinitionDocumentReader.java

public static final String IMPORT_ELEMENT = "import";
public static final String ALIAS_ATTRIBUTE = "alias";
public static final String BEAN_ELEMENT = BeanDefinitionParserDelegate.BEAN_ELEMENT;
public static final String NESTED_BEANS_ELEMENT = "beans";

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { // import
importBeanDefinitionResource(ele);
} else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { // alias
processAliasRegistration(ele);
} else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { // bean
processBeanDefinition(ele, delegate);
} else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // beans
// recurse
doRegisterBeanDefinitions(ele);
}
}

该方法的功能一目了然,分别是对四种不同的标签进行解析,分别是 importaliasbeanbeans 。咱门从第一个标签 import 开始。

1. import 示例

经历过 Spring 配置文件的小伙伴都知道,如果工程比较大,配置文件的维护会让人觉得恐怖,文件太多了,想象将所有的配置都放在一个 spring.xml 配置文件中,哪种后怕感是不是很明显?

所有针对这种情况 Spring 提供了一个分模块的思路,利用 import 标签,例如我们可以构造一个这样的 spring.xml

1
2
3
4
5
6
7
8
9
10
11
<?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">

<import resource="spring-student.xml"/>

<import resource="spring-student-dtd.xml"/>

</beans>

spring.xml 配置文件中,使用 import 标签的方式导入其他模块的配置文件。

  • 如果有配置需要修改直接修改相应配置文件即可。
  • 若有新的模块需要引入直接增加 import 即可。

这样大大简化了配置后期维护的复杂度,同时也易于管理。

2. importBeanDefinitionResource

Spring 使用 #importBeanDefinitionResource(Element ele) 方法,完成对 import 标签的解析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// DefaultBeanDefinitionDocumentReader.java

/**
* Parse an "import" element and load the bean definitions
* from the given resource into the bean factory.
*/
protected void importBeanDefinitionResource(Element ele) {
// <1> 获取 resource 的属性值
String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
// 为空,直接退出
if (!StringUtils.hasText(location)) {
getReaderContext().error("Resource location must not be empty", ele); // 使用 problemReporter 报错
return;
}

// <2> 解析系统属性,格式如 :"${user.dir}"
// Resolve system properties: e.g. "${user.dir}"
location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);

// 实际 Resource 集合,即 import 的地址,有哪些 Resource 资源
Set<Resource> actualResources = new LinkedHashSet<>(4);

// <3> 判断 location 是相对路径还是绝对路径
// Discover whether the location is an absolute or relative URI
boolean absoluteLocation = false;
try {
absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
} catch (URISyntaxException ex) {
// cannot convert to an URI, considering the location relative
// unless it is the well-known Spring prefix "classpath*:"
}

// Absolute or relative?
// <4> 绝对路径
if (absoluteLocation) {
try {
// 添加配置文件地址的 Resource 到 actualResources 中,并加载相应的 BeanDefinition 们
int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
if (logger.isTraceEnabled()) {
logger.trace("Imported " + importCount + " bean definitions from URL location [" + location + "]");
}
} catch (BeanDefinitionStoreException ex) {
getReaderContext().error(
"Failed to import bean definitions from URL location [" + location + "]", ele, ex);
}
// <5> 相对路径
} else {
// No URL -> considering resource location as relative to the current file.
try {
int importCount;
// 创建相对地址的 Resource
Resource relativeResource = getReaderContext().getResource().createRelative(location);
// 存在
if (relativeResource.exists()) {
// 加载 relativeResource 中的 BeanDefinition 们
importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
// 添加到 actualResources 中
actualResources.add(relativeResource);
// 不存在
} else {
// 获得根路径地址
String baseLocation = getReaderContext().getResource().getURL().toString();
// 添加配置文件地址的 Resource 到 actualResources 中,并加载相应的 BeanDefinition 们
importCount = getReaderContext().getReader().loadBeanDefinitions(
StringUtils.applyRelativePath(baseLocation, location) /* 计算绝对路径 */, actualResources);
}
if (logger.isTraceEnabled()) {
logger.trace("Imported " + importCount + " bean definitions from relative location [" + location + "]");
}
} catch (IOException ex) {
getReaderContext().error("Failed to resolve current resource location", ele, ex);
} catch (BeanDefinitionStoreException ex) {
getReaderContext().error(
"Failed to import bean definitions from relative location [" + location + "]", ele, ex);
}
}
// <6> 解析成功后,进行监听器激活处理
Resource[] actResArray = actualResources.toArray(new Resource[0]);
getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
}

解析 import 标签的过程较为清晰,整个过程如下:

  • <1>
    
    1
    2
    3
    4



    处,获取
    source
    1
    2
    3
    4
    5
    6
    7
    8



    属性的值,该值表示资源的路径。

    - `<2>` 处,解析路径中的系统属性,如 `"${user.dir}"` 。

    -
    <3>
    1
    2
    3
    4



    处,判断资源路径
    location
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21



    是绝对路径还是相对路径。详细解析,见



    「2.1 判断路径」





    - `<4>` 处,如果是绝对路径,则调递归调用 Bean 的解析过程,进行另一次的解析。详细解析,见 [「2.2 处理绝对路径」](http://svip.iocoder.cn/Spring/IoC-parse-BeanDefinitions-for-import/#) 。
    - `<5>` 处,如果是相对路径,则先计算出绝对路径得到 Resource,然后进行解析。详细解析,见 [「2.3 处理相对路径」](http://svip.iocoder.cn/Spring/IoC-parse-BeanDefinitions-for-import/#) 。

    - `<6>` 处,通知监听器,完成解析。

    #### 2.1 判断路径

    通过以下代码,来判断 `location` 是为相对路径还是绝对路径:
    absoluteLocation = ResourcePatternUtils.isUrl(location) // <1> || ResourceUtils.toURI(location).isAbsolute(); // <2>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    判断绝对路径的规则如下:

    - `<1>` 以 `classpath*:` 或者 `classpath:` 开头的为绝对路径。
    - `<1>` 能够通过该 `location` 构建出 `java.net.URL` 为绝对路径。
    - `<2>` 根据 `location` 构造 `java.net.URI` 判断调用 `#isAbsolute()` 方法,判断是否为绝对路径。

    #### 2.2 处理绝对路径

    如果 `location` 为绝对路径,则调用 `#loadBeanDefinitions(String location, Set<Resource> actualResources)`, 方法。该方法在 `org.springframework.beans.factory.support.AbstractBeanDefinitionReader` 中定义,代码如下:
    /** * Load bean definitions from the specified resource location. * <p>The location can also be a location pattern, provided that the * ResourceLoader of this bean definition reader is a ResourcePatternResolver. * @param location the resource location, to be loaded with the ResourceLoader * (or ResourcePatternResolver) of this bean definition reader * @param actualResources a Set to be filled with the actual Resource objects * that have been resolved during the loading process. May be {@code null} * to indicate that the caller is not interested in those Resource objects. * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of loading or parsing errors * @see #getResourceLoader() * @see #loadBeanDefinitions(org.springframework.core.io.Resource) * @see #loadBeanDefinitions(org.springframework.core.io.Resource[]) */ public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException { // 获得 ResourceLoader 对象 ResourceLoader resourceLoader = getResourceLoader(); if (resourceLoader == null) { throw new BeanDefinitionStoreException( "Cannot load bean definitions from location [" + location + "]: no ResourceLoader available"); } if (resourceLoader instanceof ResourcePatternResolver) { // Resource pattern matching available. try { // 获得 Resource 数组,因为 Pattern 模式匹配下,可能有多个 Resource 。例如说,Ant 风格的 location Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); // 加载 BeanDefinition 们 int count = loadBeanDefinitions(resources); // 添加到 actualResources 中 if (actualResources != null) { Collections.addAll(actualResources, resources); } if (logger.isTraceEnabled()) { logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]"); } return count; } catch (IOException ex) { throw new BeanDefinitionStoreException( "Could not resolve bean definition resource pattern [" + location + "]", ex); } } else { // Can only load single resources by absolute URL. // 获得 Resource 对象, Resource resource = resourceLoader.getResource(location); // 加载 BeanDefinition 们 int count = loadBeanDefinitions(resource); // 添加到 actualResources 中 if (actualResources != null) { actualResources.add(resource); } if (logger.isTraceEnabled()) { logger.trace("Loaded " + count + " bean definitions from location [" + location + "]"); } return count; } }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26

    整个逻辑比较简单:

    - 首先,获取 ResourceLoader 对象。
    - 然后,根据不同的 ResourceLoader 执行不同的逻辑,主要是可能存在多个 Resource 。
    - 最终,都会回归到 `XmlBeanDefinitionReader#loadBeanDefinitions(Resource... resources)` 方法,所以这是一个递归的过程。
    - 另外,获得到的 Resource 的对象或数组,都会添加到 `actualResources` 中。

    #### 2.3 处理相对路径

    如果 `location` 是相对路径,则会根据相应的 Resource 计算出相应的相对路径的 Resource 对象 ,然后:

    - 若该 Resource 存在,则调用 `XmlBeanDefinitionReader#loadBeanDefinitions()` 方法,进行 BeanDefinition 加载。
    - 否则,构造一个绝对 `location`( 即 `StringUtils.applyRelativePath(baseLocation, location)` 处的代码),并调用 `#loadBeanDefinitions(String location, Set<Resource> actualResources)` 方法,**与绝对路径过程一样**。

    ### 3. 小结

    至此,`import` 标签解析完毕,整个过程比较清晰明了:**获取 source 属性值,得到正确的资源路径,然后调用 `XmlBeanDefinitionReader#loadBeanDefinitions(Resource... resources)` 方法,进行递归的 BeanDefinition 加载**。

    ## 8. IoC 之解析 <bean> 标签:开启解析进程

    `import` 标签解析完毕了,我们一起来看看 Spring 中**最复杂也是最重要**的标签 `bean` 标签的解析过程。

    ### 1. processBeanDefinition

    在方法 `#parseDefaultElement(...)` 方法中,如果遇到标签为 `bean` 时,则调用 `#processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate)` 方法,进行 `bean` 标签的解析。代码如下:
    // DefaultBeanDefinitionDocumentReader.java

/**

  • Process the given bean element, parsing the bean definition
  • and registering it with the registry.
  • /
    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    // 进行 bean 元素解析。
    // <1> 如果解析成功,则返回 BeanDefinitionHolder 对象。而 BeanDefinitionHolder 为 name 和 alias 的 BeanDefinition 对象
    // 如果解析失败,则返回 null 。
    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    if (bdHolder != null) {
    // <2> 进行自定义标签处理
    bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
    try {
        // <3> 进行 BeanDefinition 的注册
        // Register the final decorated instance.
        BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
    } catch (BeanDefinitionStoreException ex) {
        getReaderContext().error("Failed to register bean definition with name '" +
                bdHolder.getBeanName() + "'", ele, ex);
    }
    // <4> 发出响应事件,通知相关的监听器,已完成该 Bean 标签的解析。
    // Send registration event.
    getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
    }
    }
    1
    2
    3
    4

    整个过程分为四个步骤:

    1. 调用
    BeanDefinitionParserDelegate#parseBeanDefinitionElement(Element ele, BeanDefinitionParserDelegate delegate)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18



    方法,进行元素解析。

    - 如果解析**失败**,则返回 `null`,错误由 ProblemReporter 处理。
    - 如果解析**成功**,则返回 BeanDefinitionHolder 实例 `bdHolder` 。BeanDefinitionHolder 为持有 `name` 和 `alias` 的 BeanDefinition。
    - 详细解析,见 [「2. parseBeanDefinitionElement」](http://svip.iocoder.cn/Spring/IoC-parse-BeanDefinitions-in-processBeanDefinition/#) 。

    2. 若实例 `bdHolder` 不为空,则调用 `BeanDefinitionParserDelegate#decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder bdHolder)` 方法,进行自定义标签处理。

    3. 解析完成后,则调用 `BeanDefinitionReaderUtils#registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)` 方法,对 `bdHolder` 进行 BeanDefinition 的注册。

    4. 发出响应事件,通知相关的监听器,完成 Bean 标签解析。

    ### 2. parseBeanDefinitionElement

    `BeanDefinitionParserDelegate#parseBeanDefinitionElement(Element ele, BeanDefinitionParserDelegate delegate)` 方法,进行 `<bean>` 元素解析。代码如下:
    // BeanDefinitionParserDelegate.java

/**

  • Parses the supplied {@code } element. May return {@code null}
  • if there were errors during parse. Errors are reported to the
  • {@link org.springframework.beans.factory.parsing.ProblemReporter}.
  • /
    @Nullable
    public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
    return parseBeanDefinitionElement(ele, null);
    }

/**

  • Parses the supplied {@code } element. May return {@code null}

  • if there were errors during parse. Errors are reported to the

  • {@link org.springframework.beans.factory.parsing.ProblemReporter}.

  • @param containingBean TODO 芋艿,需要进一步确认

  • /
    @Nullable
    public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
    // <1> 解析 id 和 name 属性
    String id = ele.getAttribute(ID_ATTRIBUTE);
    String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

    // <1> 计算别名集合
    List aliases = new ArrayList<>();
    if (StringUtils.hasLength(nameAttr)) {

    String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
    aliases.addAll(Arrays.asList(nameArr));

    }

    // <3.1> beanName ,优先,使用 id
    String beanName = id;
    // <3.2> beanName ,其次,使用 aliases 的第一个
    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");
    }

    }

    // <2> 检查 beanName 的唯一性
    if (containingBean == null) {

    checkNameUniqueness(beanName, aliases, ele);

    }

    // <4> 解析属性,构造 AbstractBeanDefinition 对象
    AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
    if (beanDefinition != null) {

    // <3.3> beanName ,再次,使用 beanName 生成规则
    if (!StringUtils.hasText(beanName)) {
        try {
            if (containingBean != null) {
                // <3.3> 生成唯一的 beanName
                beanName = BeanDefinitionReaderUtils.generateBeanName(
                        beanDefinition, this.readerContext.getRegistry(), true);
            } else {
                // <3.3> 生成唯一的 beanName
                beanName = this.readerContext.generateBeanName(beanDefinition);
                // TODO 芋艿,需要进一步确认
                // 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)) {
                    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;
        }
    }
    // <5> 创建 BeanDefinitionHolder 对象
    String[] aliasesArray = StringUtils.toStringArray(aliases);
    return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);

    }
    return null;
    }

    1
    2
    3
    4
    5
    6

    这个方法还没有对 `bean` 标签进行解析,只是在解析动作之前做了一些功能架构,主要的工作有:

    - `<1>` 处,解析 `id`、`name` 属性,确定 `aliases` 集合

    - `<2>` 处,检测 `beanName` 是否唯一。代码如下:

    /**

    • 已使用 Bean 名字的集合
    • Stores all used bean names so we can enforce uniqueness on a per
    • beans-element basis. Duplicate bean ids/names may not exist within the
    • same level of beans element nesting, but may be duplicated across levels.
    • /
      private final Set usedNames = new HashSet<>();

    /**

    • Validate that the specified bean name and aliases have not been used already

    • within the current level of beans element nesting.

    • /
      protected void checkNameUniqueness(String beanName, List aliases, Element beanElement) {
      // 寻找是否 beanName 已经使用
      String foundName = null;
      if (StringUtils.hasText(beanName) && this.usedNames.contains(beanName)) {

      foundName = beanName;

      }
      if (foundName == null) {

      foundName = CollectionUtils.findFirstMatch(this.usedNames, aliases);

      }
      // 若已使用,使用 problemReporter 提示错误
      if (foundName != null) {

      error("Bean name '" + foundName + "' is already used in this <beans> element", beanElement);

      }

      // 添加到 usedNames 集合
      this.usedNames.add(beanName);
      this.usedNames.addAll(aliases);
      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10

      - 这里有必要说下 `beanName` 的命名规则:

      - `<3.1>` 处,如果 `id` 不为空,则 `beanName = id` 。
      - `<3.2>` 处,如果 `id` 为空,但是 `aliases` 不空,则 `beanName` 为 `aliases` 的**第一个**元素
      - `<3.3>` 处,如果两者都为空,则根据**默认规则**来设置 beanName 。因为**默认规则**不是本文的重点,所以暂时省略。感兴趣的胖友,自己研究下哈。

      - `<4>` 处,调用 `#parseBeanDefinitionElement(Element ele, String beanName, BeanDefinition containingBean)` 方法,对属性进行解析并封装成 AbstractBeanDefinition 实例 `beanDefinition` 。详细解析,见 [「2.1 parseBeanDefinitionElement」](http://svip.iocoder.cn/Spring/IoC-parse-BeanDefinitions-in-processBeanDefinition/#) 。

      - `<5>` 处,根据所获取的信息(`beanName`、`aliases`、`beanDefinition`)构造 BeanDefinitionHolder 实例对象并返回。其中,BeanDefinitionHolder 的简化代码如下:

      /**

    • BeanDefinition 对象

    • /
      private final BeanDefinition beanDefinition;
      /**

    • Bean 名字

    • /
      private final String beanName;
      /**

    • 别名集合

    • /
      @Nullable
      private final String[] aliases;

      1
      2
      3
      4
      5
      6

      > TODO 芋艿,需要进一步确认,未来参考下 [《Spring专题之IOC源码分析》](https://segmentfault.com/a/1190000016261917) ,进行细化。

      #### 2.1 parseBeanDefinitionElement

      `#parseBeanDefinitionElement(Element ele, String beanName, BeanDefinition containingBean)` 方法,对属性进行解析并封装成 AbstractBeanDefinition 实例,代码如下:

      /**

  • Parse the bean definition itself, without regard to name or aliases. May return

  • {@code null} if problems occurred during the parsing of the bean definition.

  • /
    @Nullable
    public AbstractBeanDefinition parseBeanDefinitionElement(

    Element ele, String beanName, @Nullable BeanDefinition containingBean) {

    this.parseState.push(new BeanEntry(beanName));

    // 解析 class 属性
    String className = null;
    if (ele.hasAttribute(CLASS_ATTRIBUTE)) {

    className = ele.getAttribute(CLASS_ATTRIBUTE).trim();

    }
    // 解析 parent 属性
    String parent = null;
    if (ele.hasAttribute(PARENT_ATTRIBUTE)) {

    parent = ele.getAttribute(PARENT_ATTRIBUTE);

    }

    try {

    // 创建用于承载属性的 AbstractBeanDefinition 实例
    AbstractBeanDefinition bd = createBeanDefinition(className, parent);
    
    // 解析默认 bean 的各种属性
    parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
    // 提取 description
    bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
    
    // tips:
    // 下面的一堆是解析 <bean>......</bean> 内部的子元素,
    // 解析出来以后的信息都放到 bd 的属性中
    
    // 解析元数据 <meta />
    parseMetaElements(ele, bd);
    // 解析 lookup-method 属性 <lookup-method />
    parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
    // 解析 replaced-method 属性 <replaced-method />
    parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
    
    // 解析构造函数参数 <constructor-arg />
    parseConstructorArgElements(ele, bd);
    // 解析 property 子元素 <property />
    parsePropertyElements(ele, bd);
    // 解析 qualifier 子元素 <qualifier />
    parseQualifierElements(ele, bd);
    
    bd.setResource(this.readerContext.getResource());
    bd.setSource(extractSource(ele));
    
    return bd;

    } catch (ClassNotFoundException ex) {

    error("Bean class [" + className + "] not found", ele, ex);

    } catch (NoClassDefFoundError err) {

    error("Class that bean class [" + className + "] depends on not found", ele, err);

    } catch (Throwable ex) {

    error("Unexpected failure during bean definition parsing", ele, ex);

    } finally {

    this.parseState.pop();

    }

    return null;
    }

    1
    2
    3
    4
    5
    6

    到这里,`bean` 标签的所有属性我们都可以看到其解析的过程,也就说到这里我们已经解析一个基本可用的 BeanDefinition 。

    #### 2.2 createBeanDefinition

    `#createBeanDefinition(String className, String parentName)` 方法,创建 AbstractBeanDefinition 对象。代码如下:

    /**

  • Create a bean definition for the given class name and parent name.

  • @param className the name of the bean class

  • @param parentName the name of the bean’s parent bean

  • @return the newly created bean definition

  • @throws ClassNotFoundException if bean class resolution was attempted but failed

  • /
    protected AbstractBeanDefinition createBeanDefinition(@Nullable String className, @Nullable String parentName)

    throws ClassNotFoundException {

    return BeanDefinitionReaderUtils.createBeanDefinition(

    parentName, className, this.readerContext.getBeanClassLoader());

    }

3. 小节

由于解析过程 bean 标签的属性较为漫长,篇幅较大,为了更好的观看体验,将这篇博文进行拆分

下篇博客主要介绍 BeanDefinition ,以及解析默认 bean 标签的各种属性的过程,即 #parseBeanDefinitionAttributes(Element ele, String beanName, BeanDefinition containingBean, AbstractBeanDefinition bd) 方法。