1、基本用法
java
public class MySpringBean {
private String beanName = "beanName";
public String getBeanName() {
return beanName;
}
public void setBeanName(String beanName) {
this.beanName = beanName;
}
}
xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="myBeanName" class="com.reray.spring.study.MySpringBean"/>
</beans>
java
@Test
public void test() {
XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("application.xml"));
MySpringBean beanName = (MySpringBean) xmlBeanFactory.getBean("myBeanName");
System.out.println(beanName.getBeanName());
}
2、核心类介绍
2.1 DefaultListableBeanFactory
XmlBeanFactory 继承自 DefaultListableBeanFactory,DefaultListableBeanFactory 是整个 bean 加载的核心部分,是 Spring 注册和加载 bean 的默认实现。XmlBeanFactory 和 DefaultListableBeanFactory 不同地方在于 XmlBeanFactory 使用了自定义的 xml 读取器 XmlBeanDefinitionReader 个性化的 BeanDefinition 读取。DefaultListableBeanFactory 的结构如下

XmlBeanFactory 对 DefaultListableBeanFactory 进行拓展,主要用于从 xml 文档中读取 BeanDefinition,注册和获取 bean 都是使用的父类的 DefaultListableBeanFactory 方法实现,增加了 XmlBeanDefinitionReader 对资源文件进行读取和注册
2.2 XmlBeanDefinitionReader
XmlBeanDefinitionReader 对资源文件进行读取、解析和注册

- ResourceLoader
- BeanDefinitionReader
- EnvironmentCapable
- DocumentLoader
- AbstractBeanDefinitionReader
- BeanDefinitionDocumentReader
- BeanDefinitionParserDelegate
(1)通过 AbstractBeanDefinitionReader 的 ResourceLoader 将资源文件路径转化为 Resource 文件
(2)通过 DocumentLoader 对 Resource 转化为 Document 文件
(3)通过 BeanDefinitionDocumentReader 的实现类 DefaultBeanDefinitionDocumentReader 对 Document 进行解析,并通过 BeanDefinitionParserDelegate 对 Element 进行解析
2.3 容器基础 XmlBeanFactory
java
XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("application.xml"));
上面代码执行逻辑
- 调用 ClassPathResource 构造函数创建 Resource 资源文件实例
- Resource 传入 XmlBeanFactory 后调用 loadBeanDefinitions
2.3.1 配置文件封装
在 Java 中将不同来源的资源对象抽象为 URL,通过注册不同的 handler(URLStreamHandler)来处理不同来源的资源读取逻辑。Spring 对其内部使用到的资源实现了自己的抽象结构:Resource 接口来封装底层资源。
java
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
java
public interface Resource extends InputStreamSource {
boolean exists();
boolean isReadable();
boolean isOpen();
URL getURL() throws IOException;
URI getURI() throws IOException;
File getFile() throws IOException;
long contentLength() throws IOException;
long lastModified() throws IOException;
Resource createRelative(String var1) throws IOException;
String getFilename();
String getDescription();
}
InputStreamSource 只有 getInputStream() 返回 InputStream,可以返回任何 InputStream 类。Resource 接口抽象了所有 Spring 内部使用到的底层资源,例如 File、URL、ClassPath 等,不同来源的资源文件都有其 Resource 实现:文件(FileSystemResource)、Classpath 资源(ClassPathResource)、URL 资源(UrlResource)、InputStream 资源(InputStreamResource)、Byte 数组(ByteArrayResource)等。
- exists:存在性
- isReadable:可读性
- isOpen:是否处于打开状态
- getURL:获取 URL
- getURI:获取 URI
- getFile:获取 File 类型
- lastModified:获取资源最后一次被修改的时间戳
- getFilename:获取文件名
- createRelative:基于当前资源创建一个相对资源
- getDescription:在错误处理中打印信息
通过 Resource 相关类对配置文件进行封装后配置文件的读取工作交给 XmlBeanDefinitionReader 处理。
java
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
this.reader.loadBeanDefinitions(resource) 是资源加载的真正实现,在此之前调用父类的构造函数初始化过程 super(parentBeanFactory),为父类 AbstractAutowireCapableBeanFactory 的构造函数。
java
public AbstractAutowireCapableBeanFactory() {
super();
ignoreDependencyInterface(BeanNameAware.class);
ignoreDependencyInterface(BeanFactoryAware.class);
ignoreDependencyInterface(BeanClassLoaderAware.class);
}
ignoreDependencyInterface 的主要功能是忽略给定接口的自动装配功能,自动装配功能比如 A 中有属性 B,A 在初始化的过程中会默认初始化 B,也是 Spring 的一个重要特性。在某些情况下 B 不会被初始化,例如 B 实现了 BeanNameAware 接口,典型应用是通过其他方式解析 Application 上下文注册依赖,类似 BeanFactory 通过 BeanFactoryAware 注入或者 ApplicationContext 通过 ApplicationContextAware 注入。
2.3.2 加载 Bean
XmlBeanDefinitionReader 的 loadBeanDefinitions 方法是整个资源加载的切入点
- new EncodedResource(resource)
- loadBeanDefinitions(new EncodedResource(resource))
- encodedResource.getResource().getInputStream()
- new InputSource(inputStream)
- doLoadBeanDefinitions(inputSource, encodedResource.getResource())
调用逻辑如下:
- 对 Resource 进行 EncodedResource 类封装,目的是考虑 Resource 可能存在的编码要求情况
- 获取 Resource 对应的 InputStream 并构造 InputSource,后续通过 SAX 读取 xml 文件
- 根据 InputSource 和 Resource 调用核心方法 doLoadBeanDefinitions
java
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
EncodedResource 的作用是对资源文件的编码进行处理,主要逻辑在 getReader 方法中
java
public Reader getReader() throws IOException {
if (this.charset != null) {
return new InputStreamReader(this.resource.getInputStream(), this.charset);
} else {
return this.encoding != null ? new InputStreamReader(this.resource.getInputStream(), this.encoding) : new InputStreamReader(this.resource.getInputStream());
}
}
回到 loadBeanDefinitions(new EncodedResource(resource)) 方法
java
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isInfoEnabled()) {
logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
// 记录已加载的资源
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<EncodedResource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
// 获取 InputStream
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
// 封装为 org.xml.sax.InputSource 便于后面处理 xml 文件
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
java
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
int validationMode = getValidationModeForResource(resource);
Document doc = this.documentLoader.loadDocument(
inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
return registerBeanDefinitions(doc, resource);
}
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 文件的验证模式
getValidationModeForResource - 加载 xml 文件,获取对应的 Document
loadDocument - 根据 Document 注册 Bean 信息
registerBeanDefinitions
接下来对这三个步骤进行分析
2.4 获取 xml 的验证模式
xml 文件的验证模式保证 xml 文件的正确性,常见的验证模式为 DTD 和 XSD
2.4.1 DTD 和 XSD 区别
DTD(Document Type Definition)即文档类型定义,是一种 xml 约束模式语言,是 xml 文件的验证机制,属于 xml 文件组成的一部分。DTD 是一种保证 xml 文档格式正确的有效方法,可以通过比较 xml 文档和 DTD 文件来看 xml 是否符合规范,元素和标签是否正确。DTD 文档包含:元素的定义规则,元素间关系的定义规则,元素可使用的属性,可使用的实体或符号规则。
Spring 中使用 DTD 文件的声明方式如下
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
"http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
...
</beans>
Spring 的 spring-beans-2.0.dtd 如下
dtd
<!ELEMENT beans (
description?,
(import | alias | bean)*
)>
<!ATTLIST beans default-lazy-init (true | false) "false">
<!ATTLIST beans default-merge (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>
<!ELEMENT description (#PCDATA)>
...
XML Schemas 是 XSD(XML Schemas Definition),描述了 xml 文档的结构,可以来指定 xml 文档所允许的结构,并由此来校验 xml 文档的结构是否是有效的。XML Schemas 本身就是一个 xml 文档,符合 xml 的语法结构,可以用通用的 xml 解析器解析。
XML Schemas 需要声明的部分如下:
- 名称空间
xmlns="http://www.springframework.org/schema/beans - XML Schemas 的存储位置 schemaLocation
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd- 名称空间 URI
- 该名称空间所标识的 XML Schemas 文件位置或 URL 地址
xml
<?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 的 spring-beans-3.0.xsd 如下
xsd
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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>
....
2.4.2 验证模式的读取
java
protected int getValidationModeForResource(Resource resource) {
// VALIDATION_AUTO
int validationModeToUse = getValidationMode();
// 如果手动指定了验证模式则使用指定验证模式
if (validationModeToUse != VALIDATION_AUTO) {
return validationModeToUse;
}
// 自动检测
int detectedMode = detectValidationMode(resource);
if (detectedMode != VALIDATION_AUTO) {
return detectedMode;
}
return VALIDATION_XSD;
}
上面代码如果指定了验证模式(可通过 XmlBeanDefinitionReader 的 public void setValidationMode(int validationMode) 指定)则用指定的验证模式,否则通过 detectValidationMode 自动检测,自动检测验证模式的有专门处理类 XmlValidationModeDetector 的 detectValidationMode(InputStream inputStream) 实现
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);
}
}
XmlValidationModeDetector 的 detectValidationMode(InputStream inputStream) 方法如下
java
public int detectValidationMode(InputStream inputStream) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
byte var4;
try {
// 是否为 DTD 验证模式
boolean isDtdValidated = false;
while(true) {
String content;
if ((content = reader.readLine()) != null) {
content = this.consumeCommentTokens(content);
if (this.inComment || !StringUtils.hasText(content)) {
continue;
}
if (this.hasDoctype(content)) {
isDtdValidated = true;
} else if (!this.hasOpeningTag(content)) {
continue;
}
}
int var5 = isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD;
return var5;
}
} catch (CharConversionException var9) {
var4 = VALIDATION_AUTO;
} finally {
reader.close();
}
return var4;
}
java
private boolean hasDoctype(String content) {
return content.indexOf("DOCTYPE") > -1;
}
Spring 自动检测验证模式的方法是判断是否包含 DOCTYPE,若包含则为 DTD,否则为 XSD
2.5 获取 Document
获取到 xml 验证模式后需要进行 Document 加载,使用的是 DocumentLoader 接口的 DefaultDocumentLoader 实现类 loadDocument 方法进行加载,代码如下
java
private DocumentLoader documentLoader = new DefaultDocumentLoader();
Document doc = this.documentLoader.loadDocument(
inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
java
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isDebugEnabled()) {
logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
}
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
return builder.parse(inputSource);
}
通过 SAX 解析 xml 文档
- 创建 DocumentBuilderFactory
- 通过 DocumentBuilderFactory 创建 DocumentBuilder
- 使用 DocumentBuilder 进行解析
分析下 loadDocument 方法的 EntityResolver 参数,getEntityResolver 方法代码如下:
java
protected EntityResolver getEntityResolver() {
if (this.entityResolver == null) {
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader != null) {
this.entityResolver = new ResourceEntityResolver(resourceLoader);
}
else {
this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
}
}
return this.entityResolver;
}
2.5.1 EntityResolver 用法
对于解析一个 xml,SAX 会首先读取 xml 文档上的声明,根据声明去寻找相应的 DTD 定义来对文档进行验证。默认的寻找规则通过网络(声明 DTD 的 URI 地址)进行下载 DTD 声明然后验证。但是下载遇到网络中断或者不可用就会报错。
EntityResolver 的作用是项目本身可以提供寻找 DTD 声明的方法,由程序来实现寻找 DTD 的过程。EntityResolver 接口代码如下:
java
public interface EntityResolver {
public abstract InputSource resolveEntity (String publicId,
String systemId)
throws SAXException, IOException;
}
接收两个参数 publicId 和 systemId,返回 InputSource
(1)如果是 XSD 验证模式的配置文件,代码如下:
xml
<?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>
其中关键部分是 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd 两个参数为:
publicId: nullsystemId: http://www.springframework.org/schema/beans/spring-beans.xsd
(2)如果是 DTD 验证模式的配置文件,代码如下:
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
"http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
...
</beans>
上面的两个参数为:
publicId: -//SPRING//DTD BEAN 2.0//ENsystemId: http://www.springframework.org/dtd/spring-beans-2.0.dtd
如果将验证文件通过网络下载用户体验会有延迟,一般做法是将验证文件放到自己的工程里。在 Spring 中使用的是 DelegatingEntityResolver 作为 EntityResolver 的实现类,resolveEntity 实现方法如下:
java
public static final String DTD_SUFFIX = ".dtd";
public static final String XSD_SUFFIX = ".xsd";
public InputSource resolveEntity(String publicId, 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)) {
// XSD 文件从这里解析
return this.schemaResolver.resolveEntity(publicId, systemId);
}
}
return null;
}
从上面可以看到,DTD 和 XSD 文件使用不同的解析器进行解析

加载 DTD 类型的 BeansDtdResolver 截取 systemId 作为最后的 .dtd 然后从当前路径下寻找,代码如下:
java
private static final String DTD_EXTENSION = ".dtd";
private static final String[] DTD_NAMES = {"spring-beans-2.0", "spring-beans"};
public InputSource resolveEntity(String publicId, 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("/");
for (String DTD_NAME : DTD_NAMES) {
int dtdNameStart = systemId.indexOf(DTD_NAME);
if (dtdNameStart > lastPathSeparator) {
String dtdFile = systemId.substring(dtdNameStart);
if (logger.isTraceEnabled()) {
logger.trace("Trying to locate [" + dtdFile + "] in Spring jar");
}
try {
Resource resource = new ClassPathResource(dtdFile, getClass());
InputSource source = new InputSource(resource.getInputStream());
source.setPublicId(publicId);
source.setSystemId(systemId);
if (logger.isDebugEnabled()) {
logger.debug("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 class path", ex);
}
}
}
}
}
return null;
}
加载 XSD 类型的 PluggableSchemaResolver 默认从 META-INF/spring.schemas 文件中找到 systemId 对应的 XSD 文件并加载,代码如下:
java
public static final String DEFAULT_SCHEMA_MAPPINGS_LOCATION = "META-INF/spring.schemas";
public InputSource resolveEntity(String publicId, 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) {
String resourceLocation = getSchemaMappings().get(systemId);
if (resourceLocation != null) {
Resource resource = new ClassPathResource(resourceLocation, this.classLoader);
try {
InputSource source = new InputSource(resource.getInputStream());
source.setPublicId(publicId);
source.setSystemId(systemId);
if (logger.isDebugEnabled()) {
logger.debug("Found XML schema [" + systemId + "] in classpath: " + resourceLocation);
}
return source;
}
catch (FileNotFoundException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Couldn't find XML schema [" + systemId + "]: " + resource, ex);
}
}
}
}
return null;
}
/**
* Load the specified schema mappings lazily.
*/
private Map<String, String> getSchemaMappings() {
if (this.schemaMappings == null) {
synchronized (this) {
if (this.schemaMappings == null) {
if (logger.isDebugEnabled()) {
logger.debug("Loading schema mappings from [" + this.schemaMappingsLocation + "]");
}
try {
Properties mappings =
PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader);
if (logger.isDebugEnabled()) {
logger.debug("Loaded schema mappings: " + mappings);
}
Map<String, String> schemaMappings = new ConcurrentHashMap<String, String>(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 this.schemaMappings;
}
2.6 解析及注册 BeanDefinitions
获取到 Document 后,接下来对 registerBeanDefinitions(doc, resource) 进行分析,代码如下:
java
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 使用 DefaultBeanDefinitionDocumentReader 实例化 BeanDefinitionDocumentReader
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
// 设置环境变量
documentReader.setEnvironment(this.getEnvironment());
// 实例化 BeanDefinitionReader 时默认传入 BeanDefinitionRegistry,实现是 DefaultListableBeanFactory。记录统计前的 BeanDefinition 的加载个数
int countBefore = getRegistry().getBeanDefinitionCount();
// 加载及注册 bean
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
// 记录本次加载的 BeanDefinition 个数
return getRegistry().getBeanDefinitionCount() - countBefore;
}
BeanDefinitionDocumentReader 是一个接口,实例化为 DefaultBeanDefinitionDocumentReader,进入后发现核心方法 registerBeanDefinitions 的重要目的之一是提取 root,以便再次使用 root 作为参数继续 BeanDefinition 的注册,代码如下:
java
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);
}
可以看到核心逻辑的底部是 doRegisterBeanDefinitions 方法,代码如下:
java
public static final String PROFILE_ATTRIBUTE = "profile";
protected void doRegisterBeanDefinitions(Element root) {
// 处理 profile 属性
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
Assert.state(this.environment != null, "Environment must be set for evaluating profiles");
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!this.environment.acceptsProfiles(specifiedProfiles)) {
return;
}
}
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createHelper(this.readerContext, root, parent);
// 解析前置处理,留给子类实现
preProcessXml(root);
// 解析并注册
parseBeanDefinitions(root, this.delegate);
// 解析后置处理,留给子类实现
postProcessXml(root);
this.delegate = parent;
}
可以看到 preProcessXml 和 postProcessXml 代码为空,这是面向对象设计方法中,一个类要么是面向继承设计,要么是 final 修饰,而 DefaultBeanDefinitionDocumentReader 没有被修饰为 final,这也是模板方法模式,子类可以继承 DefaultBeanDefinitionDocumentReader 重写这两个方法在 Bean 解析前后做出一些处理。
2.6.1 profile 属性的使用
profile 属性可以根据不同的环境(例如开发、测试、生产)来激活不同的配置,在上面的代码方法中,Spring 会先获取 beans 是否定义了 profile 属性,如果定义了需要从环境变量中获取,因此断言 environment 不为空,因为 profile 可能为多个,因此拆分解析验证每个 profile 是否符合环境变量中的定义。例子如下:
xml
<?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 profile="dev">
<bean id="myBeanName" class="com.reray.spring.study.MySpringBeanDev"/>
</beans>
<beans profile="prod">
<bean id="myBeanName" class="com.reray.spring.study.MySpringBeanProd"/>
</beans>
</beans>
2.6.2 解析注册 BeanDefinition
处理完 profile 后对 xml 读取,进入 parseBeanDefinitions(root, this.delegate) 代码如下:
java
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
// 对 Bean 处理
parseDefaultElement(ele, delegate);
}
else {
// 对 Bean 处理
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
Spring 中 xml 配置有两种 Bean 声明方式,默认的和自定义的。如果节点使用默认命名空间则采用 parseDefaultElement 进行解析,否则使用 parseCustomElement 解析。判断是默认的还是自定义的则使用 delegate.isDefaultNamespace 进行判断,使用 node.getNamespaceURI() 获取命名空间后和 http://www.springframework.org/schema/beans 进行对比
java
public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans";
public boolean isDefaultNamespace(String namespaceUri) {
return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));
}
public boolean isDefaultNamespace(Node node) {
return isDefaultNamespace(getNamespaceURI(node));
}
java
public String getNamespaceURI(Node node) {
return node.getNamespaceURI();
}