Spring版本
5.2.5.RELEASE
源码解析
1. 加载自定义bean.xml
首先看一下平时我们是怎么手动加载bean.xml文件的,示例代码如下:
ClassPathResource resource = new ClassPathResource("bean.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(resource);
可以先看到,加载BeanDefinition入口在XmlBeanDefinitionReader#loadBeanDefinitions
2. loadBeanDefinitions
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
// 进行编码,不过这里只传入了resource一个参数,所以并没有指定具体的编码方式和字符集
return loadBeanDefinitions(new EncodedResource(resource));
}
可以看到这里对resource进行了一个资源的编码,因为我们加载BeanDefinition其实是一个解析xml文件的过程,那么读取文件就会涉及到编码。
不过这里并没有明确指定具体的编码方式和字符集:
public EncodedResource(Resource resource) {
this(resource, null, null);
}
private EncodedResource(Resource resource, @Nullable String encoding, @Nullable Charset charset) {
super();
Assert.notNull(resource, "Resource must not be null");
this.resource = resource;
this.encoding = encoding;
this.charset = charset;
}
编码结束后交由重载方法loadBeanDefinitions
继续处理:
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);
}
// 获取当前正在被加载的资源
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
// 为空,初始化一个集合
if (currentResources == null) {
currentResources = new HashSet<>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
// 加入缓存中
// 增加缓存的原因:避免bean定义异常的时候导致死循环,即有可能存在这样的情况:
// bean A开始加载,加载过程中又由于bean定义的时候有问题,再次要求加载Bean A,第二次加载Bean A走到这一步就会被拦截,避免一直死循环下去
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// 开始记载bean定义
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
// 移除缓存
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
这里的逻辑主要是利用缓存对加载流程做了一个限制,防止重复加载同一个bean导致的死循环,通过判断之后,可以看到交由doLoadBeanDefinitions
来做真正的解析工作:
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
// 加载bean定义的xml文档
Document doc = doLoadDocument(inputSource, resource);
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
// 其余代码均为异常处理,此处略
}
该方法逻辑主要有俩步:
- 解析xml,获取一个Document对象
- 注册Bean
2.1 doLoadDocument:
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
// getValidationModeForResource 获取bean校验模型,校验bean的定义是否符合规范
// getEntityResolver用于获取xml文件校验规则的获取方式,比如说
// 我们在xml文件可以看到这样的声明:http://www.springframework.org/schema/beans/spring-beans.xsd
// 这种声明是用来寻找XSD的定义,以便对xml进行验证,而默认的方式就是通过网络下载(实际上这个声明就是一个url)
// 而寻找XSD这个行为,就是交由EntityResolver去实现的
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
这里通过documentLoader.loadDocument
去解析xml,在调用之前,主要做了俩件事:
2.1.1. getEntityResolver
protected EntityResolver getEntityResolver() {
if (this.entityResolver == null) {
// Determine default EntityResolver to use.
ResourceLoader resourceLoader = getResourceLoader();
// ResourceEntityResolver 是 DelegatingEntityResolver 的子类
if (resourceLoader != null) {
this.entityResolver = new ResourceEntityResolver(resourceLoader);
}
else {
this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
}
}
return this.entityResolver;
}
EntityResolver
主要作用在于提供一个获取验证模型的规则,我们验证一个xml文档是否正确,简单理解就是xml语法是否正确,而我们往往都能在xml文档的开头处看到类似这样的一个声明: http://www.springframework.org/schema/beans/spring-beans.xsd
这种声明就是用来指定xml文档的校验规则的,则默认方式是通过网络方式获取规则,所以是一个url,而获取规则这么一件差事,就是交由EntityResolver
去处理的
2.1.2. getValidationModeForResource
protected int getValidationModeForResource(Resource resource) {
// 获取用户通过set方法指定的验证模型
int validationModeToUse = getValidationMode();
// 如果不是自动验证模式,返回该值
if (validationModeToUse != VALIDATION_AUTO) {
return validationModeToUse;
}
// 否则,检测应该使用哪种验证模型
// 检测方法:通过验证文档是否存在DOCTYPE节点,如果是,判断为DTD,否则XSD
int detectedMode = detectValidationMode(resource);
if (detectedMode != VALIDATION_AUTO) {
return detectedMode;
}
// 使用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;
}
/**
* Detect which kind of validation to perform on the XML file identified
* by the supplied {@link Resource}. If the file has a {@code DOCTYPE}
* definition then DTD validation is used otherwise XSD validation is assumed.
* <p>Override this method if you would like to customize resolution
* of the {@link #VALIDATION_AUTO} mode.
*/
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 {
// 读取输入流,确定是否存在DOCTYPE节点,若存在,返回DTD模式,否则返回XSD模式
// 内部代码相对简单,不扩展解析
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);
}
}
该方法的主要逻辑在于获取xml文件的校验模型,校验模型可以是:
XSD
-
DTD
具体使用哪种方式首先由用户指定,若没有指定,则通过判断xml文件是否存在DOCTYPE
,若存在,使用DTD
模式,否则使用XSD
模式
现在回过头来看看loadDocument的具体实现逻辑:
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isTraceEnabled()) {
logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
}
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
return builder.parse(inputSource);
}
/**
* Create the {@link DocumentBuilderFactory} instance.
* @param validationMode the type of validation: {@link XmlValidationModeDetector#VALIDATION_DTD DTD}
* or {@link XmlValidationModeDetector#VALIDATION_XSD XSD})
* @param namespaceAware whether the returned factory is to provide support for XML namespaces
* @return the JAXP DocumentBuilderFactory
* @throws ParserConfigurationException if we failed to build a proper DocumentBuilderFactory
*/
protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)
throws ParserConfigurationException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(namespaceAware);
if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {
factory.setValidating(true);
if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
// Enforce namespace aware for XSD...
factory.setNamespaceAware(true);
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;
}
/**
* Create a JAXP DocumentBuilder that this bean definition reader
* will use for parsing XML documents. Can be overridden in subclasses,
* adding further initialization of the builder.
* @param factory the JAXP DocumentBuilderFactory that the DocumentBuilder
* should be created with
* @param entityResolver the SAX EntityResolver to use
* @param errorHandler the SAX ErrorHandler to use
* @return the JAXP DocumentBuilder
* @throws ParserConfigurationException if thrown by JAXP methods
*/
protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory,
@Nullable EntityResolver entityResolver, @Nullable ErrorHandler errorHandler)
throws ParserConfigurationException {
DocumentBuilder docBuilder = factory.newDocumentBuilder();
if (entityResolver != null) {
docBuilder.setEntityResolver(entityResolver);
}
if (errorHandler != null) {
docBuilder.setErrorHandler(errorHandler);
}
return docBuilder;
}
代码块稍长,但还是很清晰的,代码已经很清楚地展示了对应的逻辑含义,最后解析的核心在于:
builder.parse(inputSource)
这里的实现类使用了apache的包去做了解析,不在本文探讨范围内,就不展开了,解析完毕之后,封装成一个Document对象,供后续注册使用
到这里为止,我们仅仅只是验证了xml文件是否合法,并且将输入流转化为一个Document对象,尚未解析成BeanDefinition,也还没有进行注册,对于这俩部分内容,在下一篇 《Spring源码解析(二)-BeanDefinition注册》进行讲解
网友评论