文前说明
作为码农中的一员,需要不断的学习,我工作之余将一些分析总结和学习笔记写成博客与大家一起交流,也希望采用这种方式记录自己的学习之旅。
本文仅供学习交流使用,侵权必删。
不用于商业目的,转载请注明出处。
1. 概述
- IoC 容器的 初始化过程 可以分为三步骤:Resource 定位、BeanDefinition 装载和解析,BeanDefinition 注册。
-
Resource
定位。用外部资源描述 Bean 对象,初始化 IoC 容器定位外部资源,具体实现在 【Spring 笔记】资源加载策略相关整理 中说明。 -
BeanDefinition
的装载和解析。载入BeanDefinition
,通过BeanDefinitionReader
读取、解析Resource
资源,将用户定义的 Bean 表示成 IoC 容器的内部数据结构BeanDefinition
。- 在 IoC 容器内部维护着一个 BeanDefinition Map 的数据结构。
- 配置文件中每一个 <bean> 都对应着一个
BeanDefinition
对象。
-
BeanDefinition
注册。向 IoC 容器注册解析好的BeanDefinition
,这个过程通过BeanDefinitionRegistry
接口实现。- 将解析得到的
BeanDefinition
注入到一个 HashMap 容器中,IoC 容器通过这个 HashMap 来维护BeanDefinition
。- 这个过程并没有完成依赖注入(Bean 创建),Bean 创建是发生在应用第一次调用
getBean()
方法,向容器索要 Bean 时。 - 也可以通过设置预处理,即对某个 Bean 设置 lazyinit = false 属性,那么这个 Bean 的依赖注入就会在容器初始化时完成。
- 这个过程并没有完成依赖注入(Bean 创建),Bean 创建是发生在应用第一次调用
- 将解析得到的
-
ClassPathResource resource = new ClassPathResource("bean.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(resource);
- 如上例所示,根据 XML 配置文件(bean.xml)创建 Resource 资源对象,bean.xml 文件中的内容是定义的 Bean 信息。
- 创建
XmlBeanDefinitionReader
读取器,可用于装载BeanDefinition
。 - 通过
loadBeanDefinitions
方法,开始BeanDefinition
的装载和注册进程,完成后的BeanDefinition
放置在 IoC 容器中。
2. 原理
2.1 创建读取器
- 通过
Resource
和ResourceLoader
两个接口的互相配合,已经定位了资源。 - 创建
XmlBeanDefinitionReader
读取器,后续可用于读取配置文件的内容,并将其转换成 Ioc 容器内部的数据结构BeanDefinition
。
// XmlBeanDefinitionReader.java
public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
super(registry);
}
// AbstractBeanDefinitionReader.java
protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
this.registry = registry;
// Determine ResourceLoader to use.
if (this.registry instanceof ResourceLoader) {
this.resourceLoader = (ResourceLoader) this.registry;
} else {
this.resourceLoader = new PathMatchingResourcePatternResolver();
}
// Inherit Environment if possible
if (this.registry instanceof EnvironmentCapable) {
this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
} else {
this.environment = new StandardEnvironment();
}
}
- 如果设置了
ResourceLoader
则用设置的,否则使用PathMatchingResourcePatternResolver
。
- 以上接口或类归属于 spring-beans 项目。
2.2 BeanDefinition 的装载和解析
loadBeanDefinitions(加载过程)
// XmlBeanDefinitionReader.java
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
- 将
Resource
资源封装成 org.springframework.core.io.support.EncodedResource 对象,对Resource
进行编码,保证内容读取的正确性。
/**
* 当前线程,正在加载的 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,获取已经加载过的资源,将
encodedResource
加入其中,如果resourcesCurrentlyBeingLoaded
中已经存在该资源,则抛出BeanDefinitionStoreException
异常。- 这样做可以避免一个
EncodedResource
正在加载还没加载完成时,又加载自身,从而导致 死循环。
- 这样做可以避免一个
-
步骤 2,从
encodedResource
获取封装的Resource
资源,并从中获取相应的InputStream
,将其封装为InputSource
,调用doLoadBeanDefinitions()
方法,执行加载逻辑。 -
步骤 3,
EncodedResource
加载完成后,从缓存中剔除。
-
步骤 1,获取已经加载过的资源,将
doLoadBeanDefinitions
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,根据 XML 文件,获取
Document
实例。 -
步骤 2,根据获取的
Document
实例,注册 Bean 信息。
-
步骤 1,根据 XML 文件,获取
doLoadDocument
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
- 方法的调用流程。
-
步骤 1, 调用
getValidationModeForResource()
方法,获取指定资源(XML)的验证模型。 -
步骤 2,调用
DocumentLoader#loadDocument()
方法,获取 XML Document 实例。
-
步骤 1, 调用
2.2.1 获取 XML 的验证模型
- 分析获取 XML 文件的验证模式,以保证 XML 文件的正确性。
DTD
- DTD(Document Type Definition)即文档类型定义,为 XML 文件的验证机制,属于 XML 文件中组成的一部分。
- DTD 是一种保证 XML 文档格式正确的有效验证方式,它定义了相关 XML 文档的元素、属性、排列方式、元素的内容类型以及元素的层次结构。
- DTD 相当于 XML 中的词汇和语法,通过比较 XML 文件和 DTD 文件查看文档是否符合规范,元素和标签使用是否正确。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
- 在 Spring 中使用 DTD,在 Spring XML 文件头部声明。
XSD
- XSD(XML Schemas Definition)即 XML Schema 语言。
- XML Schema 本身是一个 XML文档,使用 XML 语法,可以很方便的解析 XSD 文档。
- 相对于 DTD,XSD 具有如下优势。
- XML Schema 基于 XML ,没有专门的语法。
- XML Schema 可以象其他 XML 文件一样解析和处理。
- XML Schema 比 DTD 提供了更丰富的数据类型。
- XML Schema 提供可扩充的数据模型。
- XML Schema 支持综合命名空间。
- XML Schema 支持属性组。
getValidationModeForResource(获取验证模式)
// 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;
}
// 2. 自动获取验证模式
int detectedMode = detectValidationMode(resource);
if (detectedMode != VALIDATION_AUTO) {
return detectedMode;
}
// 3. 使用 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,获取指定的验证模式(validationMode)。手动指定,则直接返回。
- 步骤 2,自动获取验证模式。
/**
* XML 验证模式探测器
*/
private final XmlValidationModeDetector validationModeDetector = new XmlValidationModeDetector();
protected int detectValidationMode(Resource resource) {
// 不可读,抛出 BeanDefinitionStoreException 异常
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 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);
}
// 2.1. 获取相应的验证模式
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);
}
}
public void setValidationMode(int validationMode) {
this.validationMode = validationMode;
}
public int getValidationMode() {
return this.validationMode;
}
- 步骤 2.1,获取相应的验证模式。
- 步骤 3,使用 VALIDATION_XSD 做为默认。
2.2.1.1 XmlValidationModeDetector
- org.springframework.util.xml.XmlValidationModeDetector 是 XML 验证模式探测器。
detectValidationMode(自动获取验证模式)
detectValidationMode 自动获取验证模式流程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;
// 1. 循环,逐行读取 XML 文件的内容
while ((content = reader.readLine()) != null) {
content = consumeCommentTokens(content);
// 跳过注释
if (this.inComment || !StringUtils.hasText(content)) {
continue;
}
// 2. 包含 DOCTYPE 为 DTD 模式
if (hasDoctype(content)) {
isDtdValidated = true;
break;
}
// 3. hasOpeningTag 方法会校验,如果这一行有 < ,并且 < 后面跟着的是字母,则返回 true 。
if (hasOpeningTag(content)) {
// End of meaningful data...
break;
}
}
// 返回 VALIDATION_DTD or VALIDATION_XSD 模式
return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
} catch (CharConversionException ex) {
// 4. 返回 VALIDATION_AUTO 模式
// Choked on some character encoding...
// Leave the decision up to the caller.
return VALIDATION_AUTO;
} finally {
reader.close();
}
}
private static final String DOCTYPE = "DOCTYPE";
private boolean hasDoctype(String content) {
return content.contains(DOCTYPE);
}
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))); // < 后面的内容是字幕
}
private String consumeCommentTokens(String line) {
if(!line.contains("<!--") && !line.contains("-->")) {//如果这一行不包含注释标记,则直接把这一行返回
return line;
} else {
do {//包含标记的话,进入consume方法,进一步判断
if((line = this.consume(line)) == null) {
return line;
}
} while(this.inComment || line.trim().startsWith("<!--"));
return line;
}
}
//如果包含头标记,则返回头标记以后的内容;如果包含结束标记,则返回标记后面的内容。
private String consume(String line) {
int index = this.inComment?this.endComment(line):this.startComment(line);
return index == -1?null:line.substring(index);
}
- 方法的调用流程。
- 步骤 1,通过读取 XML 文件的内容,进行自动判断。
- 步骤 2,判断内容中如果包含有 " DOCTYPE ",则为 DTD 验证模式。
- 步骤 3,判断行包含 <,并且紧跟着字母,则为 XSD 验证模式。
-
步骤 4,发生
CharConversionException
异常,则为 VALIDATION_AUTO 模式。
2.2.2 获取 XML Document 实例
DocumentLoader
- 获取
Document
的策略,由接口 org.springframework.beans.factory.xml.DocumentLoader 定义。
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 。 |
DefaultDocumentLoader
-
DocumentLoader
的默认实现类 org.springframework.beans.factory.xml.DefaultDocumentLoader。
/**
* 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);
}
- 方法的调用流程。
- 步骤 1,创建 javax.xml.parsers.DocumentBuilderFactory 对象。
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;
}
- 步骤 2,创建 javax.xml.parsers.DocumentBuilder 对象。
protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory,
@Nullable EntityResolver entityResolver, @Nullable ErrorHandler errorHandler)
throws ParserConfigurationException {
// 创建 DocumentBuilder 对象
DocumentBuilder docBuilder = factory.newDocumentBuilder();
// 2.1. 设置 EntityResolver 属性
if (entityResolver != null) {
docBuilder.setEntityResolver(entityResolver);
}
// 设置 ErrorHandler 属性
if (errorHandler != null) {
docBuilder.setErrorHandler(errorHandler);
}
return docBuilder;
}
-
步骤 2.1,设置
DocumentBuilder
的EntityResolver
属性。 -
步骤 3,通过 javax.xml 库进行解析,从 XML 中获取
Document
对象。
2.2.3 EntityResolver(解析器)
- 返回指定的解析器,如果没有指定,则构造一个未指定的默认解析器。
// 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
。
- 该
-
EntityResolver
的作用在于,通过实现它,应用可以自定义如何寻找 验证文件 的逻辑。- 解析一个 XML,首先会读取该 XML 文档上的声明,根据声明去寻找相应的 DTD 定义,以便对文档进行一个验证。默认的寻找规则,即通过网络下载相应的 DTD 声明,并进行认证。下载过程很慢,而且当网络中断或不可用时会报错。
-
EntityResolver
可以提供一个如何寻找 DTD 声明的方法由程序实现(比如将 DTD 文件放到项目中),实现时直接读取并返回即可,这样避免了通过网络寻找相应的声明。
- org.springframework.beans.factory.xm.BeansDtdResolver,Spring Bean dtd 解码器,用来从 classpath 或者 jar 文件中加载 DTD。
- org.springframework.beans.factory.xml.PluggableSchemaResolver,读取 classpath 下的所有 " META-INF/spring.schemas " 形成一个 namespaceURI 与 Schema 文件地址的 map 。
-
org.springframework.beans.factory.xml.DelegatingEntityResolver,分别代理 DTD 的
BeansDtdResolver
和 xml schemas 的PluggableSchemaResolver
。 -
org.springframework.beans.factory.xml.ResourceEntityResolver,通过
ResourceLoader
解析实体的引用。
public interface EntityResolver {
public abstract InputSource resolveEntity (String publicId, String systemId)
throws SAXException, IOException;
}
参数 | 说明 |
---|---|
publicId | 被引用的外部实体的公共标识符,如果没有提供,则返回 null 。 |
systemId | 被引用的外部实体的系统标识符。 |
XSD 验证模式
- publicId:null
- systemId:http://www.springframework.org/schema/beans/spring-beans.xsd
DTD 验证模式
- publicId:-//SPRING//DTD BEAN 2.0//EN
- systemId:http://www.springframework.org/dtd/spring-beans.dtd
2.2.3.1 BeansDtdResolver
- Spring Bean dtd 解码器。对 systemId 进行了简单的校验(从最后一个 / 开始,内容中是否包含 spring-beans),构造一个 InputSource 对象,并设置 publicId、systemId 属性,然后返回。
/**
* 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;
}
2.2.3.2 PluggableSchemaResolver
@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;
}
- 获取一个映射表(systemId 与其在本地的对照关系)。
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.2.3.3 DelegatingEntityResolver
@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.2.3.4 ResourceEntityResolver
private final ResourceLoader resourceLoader;
@Override
@Nullable
public InputSource resolveEntity(String publicId, @Nullable String systemId) throws SAXException, IOException {
// 1. 调用父类的方法,进行解析
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;
}
- 方法的调用流程。
- 步骤 1,调用父类的方法,进行解析。
-
步骤 2,如果失败,使用 resourceLoader ,尝试读取 systemId 对应的
Resource
资源。
2.2.3.5 自定义 EntityResolver
- 实现
EntityResolver
接口,并使用setEntityResolver()
方法,向 SAX 驱动器注册一个EntityResolver
实例。
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;
}
}
}
// XmlBeanDefinitionReader.java
public void setEntityResolver(EntityResolver entityResolver) {
this.entityResolver = entityResolver;
}
protected EntityResolver getEntityResolver() {
if (this.entityResolver == null) {
ResourceLoader resourceLoader = this.getResourceLoader();
if (resourceLoader != null) {
this.entityResolver = new ResourceEntityResolver(resourceLoader);
} else {
this.entityResolver = new DelegatingEntityResolver(this.getBeanClassLoader());
}
}
return this.entityResolver;
}
2.3 BeanDefinition 的注册
- 获取 XML Document 对象后,会根据该对象和
Resource
资源对象调用XmlBeanDefinitionReader#registerBeanDefinitions()
方法,开始注册BeanDefinition
。
// 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,实例化
BeanDefinitionDocumentReader
对象。 -
步骤 2,获取已注册的
BeanDefinition
数量。 -
步骤 3,创建
XmlReaderContext
对象。 -
步骤 4,读取 XML 元素,注册
BeanDefinition
。
-
步骤 1,实例化
2.3.1 createBeanDefinitionDocumentReader
/**
* documentReader 的类
*
* @see #createBeanDefinitionDocumentReader()
*/
private Class<? extends BeanDefinitionDocumentReader> documentReaderClass = DefaultBeanDefinitionDocumentReader.class;
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
return BeanUtils.instantiateClass(this.documentReaderClass);
}
2.3.1.1 BeanDefinitionDocumentReader
- 从给定的
Document
对象中解析定义的BeanDefinition
并注册到注册表中。
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;
}
参数 | 说明 |
---|---|
doc 方法参数 | 待解析的 Document 对象。 |
readerContext | 解析器的当前上下文,包括目标注册表和被解析的资源。根据 Resource 创建。 |
2.3.1.2 DefaultBeanDefinitionDocumentReader
-
BeanDefinitionDocumentReader
接口的默认实现。
@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
对象,它负责解析BeanDefinition
。 -
步骤 2,检查
<beans/>
根 标签的命名空间是否为空,或者是 http://www.springframework.org/schema/beans。-
步骤 2.1,判断
<beans/>
上是否配置了 profile 属性。 - 步骤 2.2,使用分隔符切分,可能存在多个 profile。
- 步骤 2.3,如果所有 profile 都无效,则 return 不进行注册。
-
步骤 2.1,判断
- 步骤 3,解析前处理。
- 步骤 4,解析逻辑处理。
- 步骤 5,解析后处理。
-
步骤 1,创建
parseBeanDefinitions(解析逻辑处理)
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;
// 如果该节点使用默认命名空间,执行默认解析
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
// 如果该节点非默认命名空间,执行自定义解析
} else {
delegate.parseCustomElement(ele);
}
}
}
// 2. 如果根节点非默认命名空间,执行自定义解析
} else {
delegate.parseCustomElement(root);
}
}
- 方法的调用流程。
-
步骤 1,如果根节点或子节点使用默认命名空间,执行默认解析(例如:
<bean id="studentService" class="org.springframework.core.StudentService" />
)。
-
步骤 1,如果根节点或子节点使用默认命名空间,执行默认解析(例如:
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,如果根节点或子节点不使用默认命名空间,执行自定义解析(例如:
<tx:annotation-driven>
)。 -
步骤 1 和 步骤 2 中的标签解析可以查看 【Spring 笔记】Bean 解析相关整理
processBeanDefinition
- 完成 Bean 标签解析的核心工作。
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
// 进行 bean 元素解析。
// 如果解析成功,则返回 BeanDefinitionHolder 对象。而 BeanDefinitionHolder 为 name 和 alias 的 BeanDefinition 对象
// 如果解析失败,则返回 null 。
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
// 进行自定义标签处理
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// 进行 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);
}
// 发出响应事件,通知相关的监听器,已完成该 Bean 标签的解析。
// Send registration event.
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
- 然后调用
BeanDefinitionReaderUtils#registerBeanDefinition()
方法进行注册。
2.3.2 BeanDefinitionReaderUtils
registerBeanDefinition
// BeanDefinitionReaderUtils.java
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// 1. 注册 beanName
// Register bean definition under primary name.
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// 2. 注册 alias
// Register aliases for bean name, if any.
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
- 方法的调用流程。
-
步骤 1,通过 beanName 注册
BeanDefinition
。 - 步骤 2,注册别名 alias 和 beanName 的映射。
-
步骤 1,通过 beanName 注册
2.3.2.1 BeanDefinitionRegistry
-
BeanDefinition
的注册,由接口 org.springframework.beans.factory.support.BeanDefinitionRegistry 定义。
- 通过 beanName 注册
BeanDefinition
。
// DefaultListableBeanFactory.java
/** Whether to allow re-registration of a different definition with the same name. */
private boolean allowBeanDefinitionOverriding = true;
/** Map of bean definition objects, keyed by bean name. */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
/** List of bean definition names, in registration order. */
private volatile List<String> beanDefinitionNames = new ArrayList<>(256);
/** List of names of manually registered singletons, in registration order. */
private volatile Set<String> manualSingletonNames = new LinkedHashSet<>(16);
/** Cached array of bean definition names in case of frozen configuration. */
@Nullable
private volatile String[] frozenBeanDefinitionNames;
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
// 校验 beanName 与 beanDefinition 非空
Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
// 1. 校验 BeanDefinition 。
// 这是注册前的最后一次校验了,主要是对属性 methodOverrides 进行校验。
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
((AbstractBeanDefinition) beanDefinition).validate();
} catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}
// 2. 从缓存中获取指定 beanName 的 BeanDefinition
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
// 3. 如果已经存在
if (existingDefinition != null) {
// 如果存在但是不允许覆盖,抛出异常
if (!isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
// 覆盖 beanDefinition 大于 被覆盖的 beanDefinition 的 ROLE ,打印 info 日志
} else if (existingDefinition.getRole() < beanDefinition.getRole()) {
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
if (logger.isInfoEnabled()) {
logger.info("Overriding user-defined bean definition for bean '" + beanName +
"' with a framework-generated bean definition: replacing [" +
existingDefinition + "] with [" + beanDefinition + "]");
}
// 覆盖 beanDefinition 与 被覆盖的 beanDefinition 不相同,打印 debug 日志
} else if (!beanDefinition.equals(existingDefinition)) {
if (logger.isDebugEnabled()) {
logger.debug("Overriding bean definition for bean '" + beanName +
"' with a different definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]");
}
// 其它,打印 debug 日志
} else {
if (logger.isTraceEnabled()) {
logger.trace("Overriding bean definition for bean '" + beanName +
"' with an equivalent definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]");
}
}
// 允许覆盖,直接覆盖原有的 BeanDefinition 到 beanDefinitionMap 中。
this.beanDefinitionMap.put(beanName, beanDefinition);
// 4. 如果未存在
} else {
// 检测创建 Bean 阶段是否已经开启,如果开启了则需要对 beanDefinitionMap 进行并发控制
if (hasBeanCreationStarted()) {
// beanDefinitionMap 为全局变量,避免并发情况
// Cannot modify startup-time collection elements anymore (for stable iteration)
synchronized (this.beanDefinitionMap) {
// 添加到 BeanDefinition 到 beanDefinitionMap 中。
this.beanDefinitionMap.put(beanName, beanDefinition);
// 添加 beanName 到 beanDefinitionNames 中
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
// 从 manualSingletonNames 移除 beanName
if (this.manualSingletonNames.contains(beanName)) {
Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
updatedSingletons.remove(beanName);
this.manualSingletonNames = updatedSingletons;
}
}
} else {
// Still in startup registration phase
// 添加到 BeanDefinition 到 beanDefinitionMap 中。
this.beanDefinitionMap.put(beanName, beanDefinition);
// 添加 beanName 到 beanDefinitionNames 中
this.beanDefinitionNames.add(beanName);
// 从 manualSingletonNames 移除 beanName
this.manualSingletonNames.remove(beanName);
}
this.frozenBeanDefinitionNames = null;
}
// 5. 重新设置 beanName 对应的缓存
if (existingDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
}
- 方法的调用流程。
-
步骤 1,对
BeanDefinition
进行校验,主要对AbstractBeanDefinition
的 methodOverrides 属性进行校验。 -
步骤 2,根据 beanName 从缓存中获取
BeanDefinition
对象。 -
步骤 3, 如果缓存中存在,则根据 allowBeanDefinitionOverriding 标志来判断是否允许覆盖。如果允许则直接覆盖。否则,抛出
BeanDefinitionStoreException
异常。 -
步骤 4,若缓存中没有指定 beanName 的
BeanDefinition
,则判断当前阶段是否已经开始了 Bean 的创建阶段,如果是,则需要对 beanDefinitionMap 进行加锁控制并发问题,否则直接设置即可。 -
步骤 5,若缓存中存在该 beanName 或者单例 bean 集合中存在该 beanName ,则调用
resetBeanDefinition()
方法,重置BeanDefinition
缓存。
-
步骤 1,对
-
registerAlias()
方法,注册 alias 和 beanName 的映射关系。
// SimpleAliasRegistry.java
private final Map<String, String> aliasMap = new ConcurrentHashMap<>(16);
@Override
public void registerAlias(String name, String alias) {
// 校验 name 、 alias
Assert.hasText(name, "'name' must not be empty");
Assert.hasText(alias, "'alias' must not be empty");
synchronized (this.aliasMap) {
// name == alias 则去掉alias
if (alias.equals(name)) {
this.aliasMap.remove(alias);
if (logger.isDebugEnabled()) {
logger.debug("Alias definition '" + alias + "' ignored since it points to same name");
}
} else {
// 获取 alias 已注册的 beanName
String registeredName = this.aliasMap.get(alias);
// 已存在
if (registeredName != null) {
// 相同,则 return ,无需重复注册
if (registeredName.equals(name)) {
// An existing alias - no need to re-register
return;
}
// 不允许覆盖,则抛出 IllegalStateException 异常
if (!allowAliasOverriding()) {
throw new IllegalStateException("Cannot define alias '" + alias + "' for name '" +
name + "': It is already registered for name '" + registeredName + "'.");
}
if (logger.isDebugEnabled()) {
logger.debug("Overriding alias '" + alias + "' definition for registered name '" +
registeredName + "' with new target name '" + name + "'");
}
}
// 校验,是否存在循环指向
checkForAliasCircle(name, alias);
// 注册 alias
this.aliasMap.put(alias, name);
if (logger.isTraceEnabled()) {
logger.trace("Alias definition '" + alias + "' registered for name '" + name + "'");
}
}
}
}
-
checkForAliasCircle()
对别名进行循环检测。- 假设 name、alias 分别为 X 和 Y,则构成 (X,Y) 的映射,如果集合中存在(A,X)、(Y,A)的映射,则意味着出现循环指向的情况,抛出
IllegalStateException
异常。
- 假设 name、alias 分别为 X 和 Y,则构成 (X,Y) 的映射,如果集合中存在(A,X)、(Y,A)的映射,则意味着出现循环指向的情况,抛出
protected void checkForAliasCircle(String name, String alias) {
if (hasAlias(alias, name)) {
throw new IllegalStateException("Cannot register alias '" + alias +
"' for name '" + name + "': Circular reference - '" +
name + "' is a direct or indirect alias for '" + alias + "' already");
}
}
public boolean hasAlias(String name, String alias) {
for (Map.Entry<String, String> entry : this.aliasMap.entrySet()) {
String registeredName = entry.getValue();
if (registeredName.equals(name)) {
String registeredAlias = entry.getKey();
if (registeredAlias.equals(alias) || hasAlias(registeredAlias, alias)) {
return true;
}
}
}
return false;
}
3. 总结
- 至此
BeanDefinition
基于 beanName 和 alias 的维度,都已经注入到缓存中。- 整个 IoC 的初始化过程就已经完成,从 Bean 资源的定位,转换为 Document 对象,接着对其进行解析,最后注册到 IoC 容器中。
- IoC 容器中已经建立了整个 Bean 的配置信息,这些 Bean 可以被检索、使用、维护,他们是控制反转的基础,是后面注入 Bean 的依赖。
网友评论