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配置文件的细节的主要内容,如果未能解决你的问题,请参考以下文章