Spring5:容器的基本实现

容器基本用法

bean是Spring 中最核心的东西,Spring就像是个大水桶,而bean就像是容器中的水。

bean定义

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyTestBean {

private String testStr = "testStr";

public String getTestStr () {
return testStr ;
}

public void setTestStr(String testStr) {
this.testStr = testStr ;
}

}

bean配置

1
2
3
4
5
6
7
8
9
<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.Spring:ramework.org/schema/beεns/Spring-beans.xsd">

<bean id="myTestBean" class="bean.MyTestBean"/>

</beans>

测试代码

1
2
3
4
5
6
7
8
9
@SuppressWarnings("deprecation")
public class BeanFactoryTest {
@Test
public void testSimpleLoad() {
BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));
MyTestBean bean = (MyTestBean)bf.getBean("myTestBean");
assertEquals("testStr", bean.getTestStr());
}
}

功能分析

  1. 读取配置文件beanFactoryTest.xml。
  2. 根据beanFactoryTest.xml中的配置找到对应的类的配置,并实例化。
  3. 调用实例化后的实例。

  • ConfigReader:用于读取及验证配置文件,然后放置在内存中。
  • ReflectionUtil:用于根据配置文件中的配置进行反射实例化。
  • App:用于完成整个逻辑的串联。

Spring结构组成

beans包的层级结构

  • src/main/java用于展现Spring的主要逻辑。
  • src/main/resources用于存放系统的配置文件。
  • src/test/java用于对主要逻辑进行单元测试。
  • src/test/resources用于存放测试用的配置文件。

核心类介绍

DefaultListableBeanFactory

DefaultListableBeanFactory是整个bean加载的核心部分,是Spring注册及加载bean的默认实现.

XmlBeanFactory

XmlBeanFactory继承自DefaultListableBeanFactory,主要用于从XML文档中读取BeanDefinition,对于注册及获取bean都是使用从父类DefaultListableBeanFactory继承的方法去实现。与父类不同的是,在XmlBeanFactory中使用了自定义的XML读取器XmlBeanDefinitionReader,通过XmlBeanDefinitionReader类型的reader属性对资源文件进行读取和注册,实现个性化的BeanDefinitionReader读取。

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
@Deprecated
@SuppressWarnings({"serial", "all"})
public class XmlBeanFactory extends DefaultListableBeanFactory {

private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);


/**
* Create a new XmlBeanFactory with the given resource,
* which must be parsable using dom.
* @param resource the XML resource to load bean definitions from
* @throws BeansException in case of loading or parsing errors
*/
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}

/**
* Create a new XmlBeanFactory with the given input stream,
* which must be parsable using DOM.
* @param resource the XML resource to load bean definitions from
* @param parentBeanFactory parent bean factory
* @throws BeansException in case of loading or parsing errors
*/
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}

}

XmlBeanDefinitionReader

1
2
3
4
5
6
7
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
//2
private DocumentLoader documentLoader = new DefaultDocumentLoader();
//3
private Class<? extends BeanDefinitionDocumentReader> documentReaderClass =
DefaultBeanDefinitionDocumentReader.class;
}
1
2
3
4
public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader, EnvironmentCapable {
@Nullable
private ResourceLoader resourceLoader;//1
}
1
2
3
4
public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {
@Nullable
private BeanDefinitionParserDelegate delegate;//4
}
  1. 通过继承向AbstractBeanDefinitionReader中的方法,来使用ResourLoader将资源文件路径转换为对应的Resource文件。
  2. 通过DocumentLoader对Resource 文件进行转换,将Resource 文件转换为Document文件。
  3. 通过实现接口BeanDefinitionDocumentReader的DefaultBeanDefinitionDocumentReader类对Document进行解析注册,并使用BeanDefinitionParserDelegate对Element进行解析。

容器实现准备

1
BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));

首先调用ClassPathResource的构造函数来构造Resource资源文件的实例对象,后续的资源处理就可以用Resource提供的各种服务来操作了,有了Resource后就可以进行XmlBeanFactory的初始化了。

配置文件封装

Spring对其内部使用到的资源实现了自己的抽象结构:Resource接口封装底层资源。

1
2
3
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public interface Resource extends InputStreamSource {
boolean exists();
default boolean isReadable() {
return exists();
}
default boolean isOpen() {
return false;
}
default boolean isFile() {
return false;
}
URL getURL() throws IOException;
URI getURI() throws IOException;
File getFile() throws IOException;
default ReadableByteChannel readableChannel() throws IOException {
return Channels.newChannel(getInputStream());
}
long contentLength() throws IOException;
long lastModified() throws IOException;
Resource createRelative(String relativePath) throws IOException;
@Nullable
String getFilename();
String getDescription();
}

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

在日常开发工作中,资源文件的加载可以直接使用Spring提供的类:

1
2
Resource resource = new ClassPathResource("beanFactoryTest.xml");
InputStream inputStream = resource.getInputStream();

Resource及其子类为我们提供了诸多特性,方便可以对所有资源文件进行统一处理。但实现非常简单,以getInputStream为例:

ClassPathResource.java

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
public ClassPathResource(String path) {
this(path, (ClassLoader) null);
}
public ClassPathResource(String path, @Nullable ClassLoader classLoader) {
Assert.notNull(path, "Path must not be null");
String pathToUse = StringUtils.cleanPath(path);
if (pathToUse.startsWith("/")) {
pathToUse = pathToUse.substring(1);
}
this.path = pathToUse;
this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
}
public ClassPathResource(String path, @Nullable Class<?> clazz) {
Assert.notNull(path, "Path must not be null");
this.path = StringUtils.cleanPath(path);
this.clazz = clazz;
}
@Deprecated
protected ClassPathResource(String path, @Nullable ClassLoader classLoader, @Nullable Class<?> clazz) {
this.path = StringUtils.cleanPath(path);
this.classLoader = classLoader;
this.clazz = clazz;
}
@Override
public InputStream getInputStream() throws IOException {
InputStream is;
if (this.clazz != null) {
is = this.clazz.getResourceAsStream(this.path);
}else if (this.classLoader != null) {
is = this.classLoader.getResourceAsStream(this.path);
}else {
is = ClassLoader.getSystemResourceAsStream(this.path);
}
if (is == null) {
throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
}
return is;
}

FileSystemResource.java

1
2
3
4
5
6
7
8
@Override
public InputStream getInputStream() throws IOException {
try {
return Files.newInputStream(this.filePath);
}catch (NoSuchFileException ex) {
throw new FileNotFoundException(ex.getMessage());
}
}

传入配置资源

XmlBeanFactory.java

1
2
3
4
5
6
7
8
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
//parentBeanFactory为父类BeanFactory用于factory合并,可以为空
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);//加载资源的真正实现
}

在加载数据前有一个调用父类构造函数初始化的过程super(parentBeanFactory),跟踪代码到父类DefaultListableBeanFactory的父类AbstractAutowireCapableBeanFactory的构造函数中:

1
2
3
public DefaultListableBeanFactory(@Nullable BeanFactory parentBeanFactory) {
super(parentBeanFactory);
}
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
public AbstractAutowireCapableBeanFactory() {
super();
ignoreDependencyInterface(BeanNameAware.class);
ignoreDependencyInterface(BeanFactoryAware.class);
ignoreDependencyInterface(BeanClassLoaderAware.class);
}

public AbstractAutowireCapableBeanFactory(@Nullable BeanFactory parentBeanFactory) {
this();
setParentBeanFactory(parentBeanFactory);
}
/**
* Ignore the given dependency interface for autowiring.
* <p>This will typically be used by application contexts to register
* dependencies that are resolved in other ways, like BeanFactory through
* BeanFactoryAware or ApplicationContext through ApplicationContextAware.
* <p>By default, only the BeanFactoryAware interface is ignored.
* For further types to ignore, invoke this method for each type.
* @see org.springframework.beans.factory.BeanFactoryAware
* @see org.springframework.context.ApplicationContextAware
* 自动装配时忽略给定的依赖接口,典型应用是通过其他方式解析Application上下文
* 注册依赖,类似于BeanFactory通过BeanFactoryAware进行注入或者
* ApplicationContext通过ApplicationContextAware进行注入。
*/
public void ignoreDependencyInterface(Class<?> ifc) {
this.ignoredDependencyInterfaces.add(ifc);
}

这里有必要提及ignoreDependencylnterface方法。ignoreDependencyInterface的主要功能是忽略给定接口的向动装配功能。

举例来说,当A中有属性B,那么当Spring在获取A的Bean的时候如果其属性B还没有初始化,那么Spring会自动初始化B,这也是Spring中提供的一个重要特性。但是,某些情况下,B不会被初始化,其中的一种情况就是B实现了BeanNameAware接口。

资源转换处理

(1)XmlBeanFactory构造函数中调用XmlBeanDefinitionReader类型的reader属性提供的方法。

1
this.reader.loadBeanDefinitions(resource);//整个资源加载的切入点

(2)封装资源文件。考虑到Resource可能存在编码要求的情况,当进入XmlBeanDefinitionReader后首先对参数Resource使用EncodedResource类进行封装。

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

EncodeResource主要用于对资源文件的编码进行处理,主要逻辑体现在getReader()方法中,当设置了编码属性的时候Spring会使用相应的编码作为输入流的编码。

1
2
3
4
5
6
7
8
9
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);
}else {
return new InputStreamReader(this.resource.getInputStream());
}
}

(3)获取输入流。从Resource中获取对应的InputStream并构造InputSource。

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
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);
}
//通过ThreadLocal属性来记录已经加载的资源
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 {
//从encodedResource中获取已经封装的Resource对象并再次从Resource中获取其中的inputStream
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
//InputSource这个类并不来自于Spring,它的全路径是org.xml.sax.InputSource
//通过SAX读取XML文件的方式来准备InputSource对象
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
//真正进入逻辑核心部分
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}finally {
//关闭输入流
inputStream.close();
}
}catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}

容器实现核心

核心处理部分doLoadBeanDefinitions(inputSource,encodedResource.getResource())主要做三件事,每一件都必不可少,在三个步骤支撑着整个Spring容器部分的实现,尤其第三步对配置文件的解析:

  1. 获取对XML文件的验证模式。
  2. 加载XML文件,并得到对应的Document。
  3. 根据返回的Document注册Bean信息。
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
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
//步骤1、2
return this.documentLoader.loadDocument(inputSource, getEntityResolver(),
this.errorHandler,getValidationModeForResource(resource), isNamespaceAware());
}

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
Document doc = doLoadDocument(inputSource, resource);//步骤1、2
int count = registerBeanDefinitions(doc, resource);//步骤3
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);
}
}

获取XML验证模式

比较常用的XML文件的验证模式有两种:DTD和XSD

DTD与XSD区别

DTD(Document Type Definition)即文档类型定义,是一种XML约束模式语言,是XML文件的验证机制,属于XML文件组成的一部分。一个DTD文档包含:元素的定义规则,元素间关系的定义规则,元素可使用的属性,可使用的实体或符号规则。通过比较XML文档和DTD文件来看文档是否符合规范。

要使用DTD验证模式时需要在XML文件头部声明,以下是在Spring中使用DTD声明方式的代码:

1
2
3
4
5
6
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE bean PUBLIC "-//Spring//DTD BEAN 2.0//EN" "http://www.Springframework.org/
dtd/Spring-beans-2.0.dtd">
<beans>
... ...
</beans>

Spring-beans-2.0.dtd部分如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!ELEMENT beans (
description?,
(import | alias | bean)*
)>
<!--
Default values for all bean definitions. Can be overridden at
the "bean" level. See those attribute definitions for details.
-->
<!ATTLIST beans default-lazy-init (true | false) "false">
<!ATTLIST beans default-autowire (no | byName | byType | constructor | autodetect) "no">
<!ATTLIST beans default-dependency-check (none | objects | simple | all) "none">
<!ATTLIST beans default-init-method CDATA #IMPLIED>
<!ATTLIST beans default-destroy-method CDATA #IMPLIED>
<!ATTLIST beans default-merge (true | false) "false">
... ...

XSD(XML Schemas Definition)即XML Schema语言,用来描述XML文档的结构。文档设计者可以通过XML Schema指定XML文档所允许的结构和内容,并可据此检查验证XML文档是否是有效的。XML Schema本身是符合XML语法结构的XML文档,可以用通用的XML解析器解析它。

使用XML Schema文档对XML实例文档进行检查,除了要声明名称空间外(xmlns=http://www.Srpingframework.org/schema/beans),还必须指定该名称空间所对应的XML Schema文档的存储位置,它包含两个部分,一部分是名称空间的URI,另一部分是该名称空间所标识的XML Schema文件位置或URL地址(xsi:schemaLocation=”http://www.springframework.org/schema/beans http://www.Springframework.org/schema/beans/Spring-beans.xsd")。

1
2
3
4
5
6
7
<?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">
... ...
</beans>

Spring-beans-3.0.xsd部分代码如下:

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
<xsd:schema xmlns="http://www.springframework.org/schema/beans"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.springframework.org/schema/beans">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:annotation>
<xsd:documentation>
<![CDATA[
... ...
]]>
</xsd:documentation>
</xsd:annotation>
<!-- base types -->
<xsd:complexType name="identifiedType" abstract="true">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
... ...
]]>
</xsd:documentation>
</xsd:annotation>
<xsd:attribute name="id" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
... ...
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
... ...
</xsd:schema>

验证模式的读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//XmlBeanDefinitionReader.java
protected int getValidationModeForResource(Resource resource) {
int validationModeToUse = getValidationMode();
//如果手动指定了验证模式则使用指定的验证模式
if (validationModeToUse != VALIDATION_AUTO) {
return validationModeToUse;
}
//如果未指定则使用自动检测
int detectedMode = detectValidationMode(resource);
if (detectedMode != VALIDATION_AUTO) {
return detectedMode;
}
// 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
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
//XmlBeanDefinitionReader.java
protected int detectValidationMode(Resource resource) {
if (resource.isOpen()) {
throw new BeanDefinitionStoreException(
"Passed-in Resource [" + resource + "] contains an open stream: " +
"cannot determine validation mode automatically. Either pass in a Resource " +
"that is able to create fresh streams, or explicitly specify the validationMode " +
"on your XmlBeanDefinitionReader instance.");
}

InputStream inputStream;
try {
inputStream = resource.getInputStream();
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " +
"Did you attempt to load directly from a SAX InputSource without specifying the " +
"validationMode on your XmlBeanDefinitionReader instance?", ex);
}

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);
}
}
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
//XmlValidationModeDetector.java
public int detectValidationMode(InputStream inputStream) throws IOException {
// Peek into the file to look for DOCTYPE.
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
try {
boolean isDtdValidated = false;
String content;
while ((content = reader.readLine()) != null) {
content = consumeCommentTokens(content);
//如果读取的行是空或者是注释则略过
if (this.inComment || !StringUtils.hasText(content)) {
continue;
}
//如果包含DOCTYPE,就是DTD,否则是XSD
if (hasDoctype(content)) {
isDtdValidated = true;
break;
}
//读取到<开始符号,验证模式一定会在开始符号之前
if (hasOpeningTag(content)) {
// End of meaningful data...
break;
}
}
return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
}
catch (CharConversionException ex) {
// Choked on some character encoding...
// Leave the decision up to the caller.
return VALIDATION_AUTO;
}
finally {
reader.close();
}
}

获取Document

1
2
3
4
5
6
private DocumentLoader documentLoader = new DefaultDocumentLoader();

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

获取了XML的验证模式后,就开始进行Document加载,同样XmlBeanFactoryReader类将文档读取的任务委托给了DocumentLoader去执行,这里的DocumentLoader是个接口,真正调用的是DefaultDocumentLoader:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class DefaultDocumentLoader implements DocumentLoader {
... ...
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
//通过SAX解析XML文档
//1.创建DocumentBuilderFactory
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isTraceEnabled()) {
logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
}
//2.通过DocumentBuilderFactory创建DocumentBuilder
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
//3.解析inputSource来返回Document对象
return builder.parse(inputSource);
}
... ...
}

EntityResolver用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//XmlBeanDefinitionReader.java
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;
}

对于解析一个XML,SAX首先读取该XML文档上的声明,根据声明去寻找相应的DTD定义,以便对文档进行一个验证。默认通过网络(声明的DTD的URI地址)来下载相应的DTD声明进行认证。下载是一个漫长的过程,当网络中断或不可用时,会因为相应DTD声明没有被找到而报错。

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

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

1
2
3
4
5
6
7
8
package org.xml.sax;

import java.io.IOException;

public interface EntityResolver {
public abstract InputSource resolveEntity (String publicId,String systemId)
throws SAXException, IOException;
}

resolveEntity方法接收两个参数publicId和systemId,并返回一个inputSource对象。

举例:

1.解析验证模式未XSD的配置文件:

1
2
3
4
5
6
7
<?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">
... ...
</beans>

读取到以下两个参数:

2.解析验证模式为DTD的配置文件:

1
2
3
4
5
6
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE bean PUBLIC "-//Spring//DTD BEAN 2.0//EN" "http://www.Springframework.org/
dtd/Spring-beans-2.0.dtd">
<beans>
... ...
</beans>

读取到以下两个参数:

Spring中使用DelegatingEntityResolver类为EntityResolver的实现类,将声明URL转换为自己工程里对应的地址文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//DelegatingEntityResolver.java
@Override
@Nullable
public InputSource resolveEntity(String publicId, @Nullable String systemId) throws SAXException, IOException {
if (systemId != null) {
if (systemId.endsWith(DTD_SUFFIX)) {
//如果是dtd从这里解析
return this.dtdResolver.resolveEntity(publicId, systemId);
}else if (systemId.endsWith(XSD_SUFFIX)) {
//通过调用META-INF/Spring.schemas解析
return this.schemaResolver.resolveEntity(publicId, systemId);
}
}
return null;
}
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
public class BeansDtdResolver implements EntityResolver {
... ...
@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 && systemId.endsWith(DTD_EXTENSION)) {
int lastPathSeparator = systemId.lastIndexOf('/');
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 {
Resource resource = new ClassPathResource(dtdFile, getClass());
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);
}
}
}
}

// Fall back to the parser's default behavior.
return null;
}
... ...
}

解析注册

当把文件转换为Document后,根据返回的Document注册Bean信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
private Class<? extends BeanDefinitionDocumentReader> documentReaderClass =
DefaultBeanDefinitionDocumentReader.class;

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;
}
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
return BeanUtils.instantiateClass(this.documentReaderClass);
}
}
1
2
3
4
5
6
7
8
9
10
public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader{
... ...
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
//提取文档元素,将其作为参数注册BeanDefinition
doRegisterBeanDefinitions(doc.getDocumentElement());
}
... ...
}

doRegisterBeanDefinitions是核心逻辑的底部:

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
public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader{
... ...
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 parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);

if (this.delegate.isDefaultNamespace(root)) {
//处理profile属性,步骤1.2.3
//1.获取beans节点是否定义了profile属性
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
//2.因为profile是可以同时指定多个的,需要拆分
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
// We cannot use Profiles.of(...) since profile expressions are not supported
// in XML config. See SPR-12458 for details.
//3.判断每个profile都是符合环境变量中所定义的,不定义就不会浪费性能去解析
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;
}
protected void preProcessXml(Element root) {
}
protected void postProcessXml(Element root) {
}
... ...
}

preProcessXml和postProcessXml是空实现,因为一个类要么是面向继承设计的,要么就用final修饰,在DefaultBeanDefinitionDocumentReader中并没有用final修饰,所以它是面向继承而设计的。这两个方法正是为子类而设计的,如果继承自DefaultBeanDefinitionDocumentReader的子类需要在Bean解析前后做一些处理的话,只需重写这两个方法即可(设计模式:模板方法模式)。

profile属性的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
<beans xmlns="http://www.Springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.Springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<beans profile="dev">
... ...
</beans>

<beans profile="production">
... ...
</beans>
</beans>

集成到Web环境中时,在web.xml中加入:

1
2
3
4
<context-param>
<param-name>Spring.profiles.active</param-name>
<param-value>dev</param-value>
</context-param>

这个特性,使我们可以在配置文件中部署两套配置来适用于生产环境和开发环境,可以方便的进行切换开发、部署环境、更换不同数据库。

解析注册BeanDefinition

处理了profile后就开始进行XML的读取解析,parseBeanDefinitions(root, this.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
29
public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {
... ...
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
//对beans的处理<bean id="test" class="test.TestBean"/>
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 {
//<tx:annotation-driven/>
delegate.parseCustomElement(root);
}
}
... ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class BeanDefinitionParserDelegate {

public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans";

//3.true:默认,false:自定义
public boolean isDefaultNamespace(Node node) {
return isDefaultNamespace(getNamespaceURI(node));
}

@Nullable
public String getNamespaceURI(Node node) {
return node.getNamespaceURI();//1.获取命名空间
}
//2.与静态常量直接比对
public boolean isDefaultNamespace(@Nullable String namespaceUri) {
return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));
}
}

关于默认标签解析与自定义标签解析,重要内容见下篇。

0%