Spring加载Xml配置文件的细节

Posted 沉迷Spring

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring加载Xml配置文件的细节相关的知识,希望对你有一定的参考价值。

前言

我们知道Spring的传统方式是把一系列bean配置放在xml配置文件中,然后通过ApplicationContext去加载这个配置文件,在加载Xml配置文件的时候Spring内部也做了很多设计,这些设计值得我们借鉴和学习。

首先我们前文提到过XmlBeanDefinitionReader这个类,继承于AbstractBeanDefinitionReader的,这两个类是加载Xml配置文件的入口。这边我们主要截取他们获取xml配置文件资源的主要方法看看。

1.通过配置文件路径字符串加载

  public static void main(String[] args) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.loadBeanDefinitions("spring/spring-config.xml");
String[] beanNames = beanFactory.getBeanDefinitionNames();
for (String beanName : beanNames) {
System.out.println(beanName);
}
}

2.定义ClassPathResource资源

public static void main(String[] args) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
ClassPathResource classPathResource= new ClassPathResource("spring/spring-config.xml");
beanDefinitionReader.loadBeanDefinitions(classPathResource);
String[] beanNames = beanFactory.getBeanDefinitionNames();
for (String beanName : beanNames) {
System.out.println(beanName);
}
}

结合这两个加载的方法我们具体来分析下源码。

首先通过字符串路径加载配置文件的时候,最终是通过调用XmlBeanDefinitionReader的父类AbstractBeanDefinitionReader的方法来执行的:

AbstractBeanDefinitionReader

loadBeanDefinitions

/**
* Load bean definitions from the specified resource location.
* <p>The location can also be a location pattern, provided that the
* ResourceLoader of this bean definition reader is a ResourcePatternResolver.
*
@param location the resource location, to be loaded with the ResourceLoader
* (or ResourcePatternResolver) of this bean definition reader
*
@param actualResources a Set to be filled with the actual Resource objects
* that have been resolved during the loading process(要填充加载过程中已解析的实际资源对象*的集合). May be {
@code null}
* to indicate that the caller is not interested in those Resource objects.
*
@return the number of bean definitions found
*
@throws BeanDefinitionStoreException in case of loading or parsing errors
*
@see #getResourceLoader()
*
@see #loadBeanDefinitions(org.springframework.core.io.Resource)
*
@see #loadBeanDefinitions(org.springframework.core.io.Resource[])
*/

public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {

//AbstractXmlApplicationContext 也实现了resourceLoader
//todo 应该说它的抽象父类AbstractApplicationContext继承了 DefaultResourceLoader 2020-11-17
//XmlBeanDefinitionReader 实例化的时候会设置它的ResourceLoader 2020-11-17
ResourceLoader resourceLoader = getResourceLoader();
//todo DefaultResourceLoader 没实现 ResourcePatternResolver
//todo 但是 AbstractApplicationContext 实现了 ResourcePatternResolver
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
//todo important 通过 PathMatchingResourcePatternResolver 获取resource 2020-08-31
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
//还是交给子类去加载bean,转换成EncodedResource
int count = loadBeanDefinitions(resources);

return count;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}

}

我们只关注重点代码,首先获取resourceLoader,这边通常来说就是PathMatchingResourcePatternResolver这个类,这个类可以通过location获取资源集合,也就是说我们的location里还能定义通配符来一次性获取多个资源,比如classpath:spring/config-*.xml等。

其次通过资源加载配置文件的时候,XmlBeanDefinitionReader会调用自己的方法:

XmlBeanDefinitionReader

loadBeanDefinitions

/**
* Load bean definitions from the specified XML file.
*
@param encodedResource the resource descriptor for the XML file,
* allowing to specify an encoding to use for parsing the file
*
@return the number of bean definitions found
*
@throws BeanDefinitionStoreException in case of loading or parsing errors
*/

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {

try {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
//省略部分代码
}

doLoadBeanDefinitions

/**
* Actually load bean definitions from the specified XML file.
*
@param inputSource the SAX InputSource to read from
*
@param resource the resource descriptor for the XML file
*
@return the number of bean definitions found
*
@throws BeanDefinitionStoreException in case of loading or parsing errors
*
@see #doLoadDocument
*
@see #registerBeanDefinitions
*/

//todo xml方式装载BeanDefinition
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException
{
try {
//todo important 获取Document对象 2020-09-05
Document doc = doLoadDocument(inputSource, resource);
//todo 开始注册BeanDefinitions
int count = registerBeanDefinitions(doc, resource);
return count;
}
}

这边我们看到Resource是单一的,也就是说这边不支持通配符加载多个资源文件。而且最终还是通过InputSource来加载Xml配置资源。那么接下来我们可以猜想下通过这个inputSource最终会传给谁来加载和解析呢?

doLoadDocument

/**
* Actually load the specified document using the configured DocumentLoader.
*
@param inputSource the SAX InputSource to read from
*
@param resource the resource descriptor for the XML file
*
@return the DOM Document
*
@throws Exception when thrown from the DocumentLoader
*
@see #setDocumentLoader
*
@see DocumentLoader#loadDocument
*/

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

这里的documentLoader提供了默认实现就是DefaultDocumentLoader,当然Spring也提供了设置方法来自定义documentLoader.

    private DocumentLoader documentLoader = new DefaultDocumentLoader();
/**
* Specify the {
@link DocumentLoader} to use.
* <p>The default implementation is {
@link DefaultDocumentLoader}
* which loads {
@link Document} instances using JAXP.
*/

public void setDocumentLoader(@Nullable DocumentLoader documentLoader) {
this.documentLoader = (documentLoader != null ? documentLoader : new DefaultDocumentLoader());
}

我们再来看看DefaultDocumentLoader是怎么加载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
{

DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isTraceEnabled()) {
logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
}
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);

//TODO 开始解析xml配置文件 包括对立面schema的解析 2020-09-11
return builder.parse(inputSource);
}

创建了基于jdk的一个DocumentBuilderFactory,然后通过这个工厂再创建一个DocumentBuilder。最终通过这个builder来解析我们的inputSource,返回一个Document文档。

createDocumentBuilderFactory

/**
* 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
{
//todo 使用jdk的 DocumentBuilderFactory 读取xml配置文件
//https://blog.csdn.net/hua1017177499/article/details/78985166
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
//https://blog.csdn.net/yfgsqgq/article/details/51800827
factory.setNamespaceAware(namespaceAware);
//省略部分代码
return factory;
}

createDocumentBuilder

/**
* 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();
return docBuilder;
}

最终我们可以通过解析的Document来获取文档中的节点,然后根据节点名称来配置Beans,下篇文章我们就来谈谈注册Beans的具体细节。


以上是关于Spring加载Xml配置文件的细节的主要内容,如果未能解决你的问题,请参考以下文章

Spring bean加载多个配置文件

spring加载配置文件

maven整理项目spring配置文件加载问题

Spring详解加载配置文件

Spring加载配置文件的方式

spring学习的一些细节问题