# SpringContext(3)-配置文件读取成Resource数组
调用getResource(location)方法获取Resource数组时,程序中判断resourceLoader是否是ResourcePatternResolver实例,是则调用ResourcePatternResolver实例的方法,否则调用resourceLoader的方法来加载,在本例中,由于AbstractApplicationContext实现ResourcePatternResolver接口,因此调用了实现方法:return this.resourcePatternResolver.getResources(locationPattern);
,在这里this.resourcePatternResolver为PathMatchingResourcePatternResolver对象,实际调用PathMatchingResourcePatternResolver实例方法:getResources(locationPattern)
此方法根据传入的字符串的特征来调用相应的策略,将传入的字符串解析成Resource数组,策略按顺序如下:
"classpath*:"开头的,首先检测是否有通配符('*','?',{})等,如果有,则遍历所有的classpath上的文件包括jar和zip文件来匹配并返回,如果没有通配符,则调用内部ClassLoader的getResources方法匹配相关路径,将结果包裹在UrlResource对象数组中返回
非"classpath*:"开头的,首先检测是否有通配符('*','?',{})等,如果有,则遍历所有的classpath上的文件包括jar和zip文件来匹配并返回,否则就是当前实例的情况,调用内部ClassLoader的getResources方法匹配相关路径,将结果包裹在UrlResource对象数组中返回,当前实例的ResourceLoader是当前的ClassPathXmlApplicationContext,其中resourceLoader相关的方法在父类DefaultResourceLoader中实现,此方法同样是对传入值进行各种模式判断,再根据判断结果使用不同的策略来返回结果,
- ”/“开头的,将参数包裹为ClassPathContextResource对象返回
- ”classpath:"开头的,去掉”classpath:"后将参数包裹为ClassPathResource对象返回
- 上述都不符合,首先尝试包装参数成URL对象,再根据Url对象的protocol包装成FileUrlResource或者UrlResource对象
- 无法解析成URL对象的,如本例的入参,则将参数包装成ClassPathContextResource对象返回,本例符合这种情况
至此所有的configLocation字符串已经解析成Resource数组,当前实例中为一个ClassPathContextResource对象的数组,返回后,程序将继续进行Bean定义的读取
# AbstractBeanDefinitionReader从Resource对象中加载Bean定义
AbstractBeanDefinitionReader获取到Resource数组后,会迭代数组元素,对每个Resource执行loadBeanDefinitions(Resource resource)
方法来加载Bean定义,并将读取到的Bean定义数目加和后返回,具体的读取流程见下一节AbstractBeanDefinitionReader从Resource对象中加载Bean定义
每个configLocation的每个Resource对象的Bean全部加载完成后,beanFactory被存入ApplicationContext,继续进行refresh操作
在第二节:规整configLocation成Resource数组中,在初始化完BeanFactory和XmlBeanDefinitionReader对象后,XmlBeanDefinitionReader对象将每一个configLocation字符串规整为Resource数组完成后,开始遍历获取到的Resource数组,并在每个对象上调用的loadBeanDefinitions(Resource resource)方法来加载BeanDefinition到reader内部的BeanFactory中,代码如下:
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
2
3
调用下面的方法
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);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
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();
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
代码逻辑如下:
- 判断是当前reader中是否有相同的resource正在加载,如果有说明配置错误,报错
- 获取resource的inputStream,并包裹成sax类型的InputSource
- 调用doLoadBeanDefinitions(inputSource, encodedResource.getResource())方法来读取xml文件并加载BeanDefinition
其中第三步的doLoadBeanDefinitions(inputSource, encodedResource.getResource())方法中,先加载xml到Document对象实例中,再调用public int registerBeanDefinitions(Document doc, Resource resource)
方法进行bean的注册,并处理过程中的异常与打印,后者是核心方法
# 1.1 ClassPathResource获取inputStream
首先,底层调用ClassLoader类中getResource(String name)方法获取文件对应的URL,其内部执行的顺序为:
- 调用父ClassLoader的getResource(name)的方法,如果返回值不为空则返回
- 在虚拟机的内置BootStrap ClassLoader里寻找name对应的url,有则返回结果
- 调用本对象的findResource(name)方法(默认是返回null,可能被子类复写),无论是否返回调用结果
在本例中,实际上是在第三步中,调用当前的UrlClassLoader的方法获取到URL,获取到的URL为file协议,指向项目target/classes目录下的application-context.xml目录,形式上类似"file:/myproject/target/classes/application-context.xml"文件,获取到文件后,通过调用URL类的openConnection方法返回相关的文件流InputStream
# 1.2 XmlBeanDefinitionReader从xml流中加载Bean定义:doLoadBeanDefinitions方法
- 调用doLoadDocument(inputSource, resource)方法将xml加载为Document对象
- 调用registerBeanDefinitions(doc, resource)方法,将上一步加载的Document对象作为第一个参数传入来注册Bean定义
当然,无论是文件读取,xml解析,bean定义解析都会碰到各种各样的异常,比如SAX解析异常,bean定义错误,IO异常等,在这个方法中将碰到的所有异常全部包裹为BeanDefinitionStoreException并抛出,下面一步步解析这两步具体的过程
# 1.2.1 xml文件流读取为Document对象
Spring采用SAX方式读取xml配置文件,实际调用的是DefaultDocumentLoader对象的Document loadDocument(InputSource inputSource,EntityResolver entityResolver,ErrorHandler errorHandler, int validationMode, boolean namespaceAware)throws Exception;
方法,各参数如下:
- entityResolver使用ResourceEntityResolver
- xml校验模式调用XmlValidationModeDetector对象的detectValidationMode方法进行探测,具体策略为假如xml开头有"DOCTYPE"字样证明是DTD校验,没有则为XSD校验,在本例使用的xml配置文件没有"DOCTYPE"字段,因此校验模式为XSD校验
- namespaceAware为false
具体简化为
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
return builder.parse(inputSource);
2
3
第一步构建DocumentBuilderFactory,第二步构建DocumentBuilder,第三步调用parse方法来加载文件流到Document对象
# 1.2.2 DOM文件解析成beanDefinition
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
2
3
4
5
6
第一步初始化了BeanDefinitionDocumentReader类型的对象,在此是DefaultBeanDefinitionDocumentReader类,然后调用其registerBeanDefinitions方法来进行bean的注册,调用完成后通过检查BeanFactory内部Bean的数量变化,返回实际初始化的bean,此方法第二个参数是XmlReaderContext对象,调用的构造函数为
public XmlReaderContext(
Resource resource, ProblemReporter problemReporter,
ReaderEventListener eventListener, SourceExtractor sourceExtractor,
XmlBeanDefinitionReader reader, NamespaceHandlerResolver namespaceHandlerResolver) {
super(resource, problemReporter, eventListener, sourceExtractor);
this.reader = reader;
this.namespaceHandlerResolver = namespaceHandlerResolver;
}
2
3
4
5
6
7
8
9
其中参数:
- problemReporter为FailFastProblemReporter对象,这个类中碰到error和fatal均包裹成BeanDefinitionParsingException并抛出
- eventListener为EmptyReaderEventListener对象,不做任何操作
- sourceExtractor允许自定义如何从定义中提取额外的bean的meta data并存入beanFactory,这里为了节约内存一律不提取
- namespaceHandlerResolver为DefaultNamespaceHandlerResolver对象
# 1.2.2.1 DefaultBeanDefinitionDocumentReader实例方法:protected void doRegisterBeanDefinitions(Element root)
此方法负责初始化delegate,校验处理profile,并调用方法来处理DOM对象,代码简化如下:
protected void doRegisterBeanDefinitions(Element root) {
// <beans>元素的嵌套会导致递归调用,为保证<beans>元素的default-*属性能正确传导,因此对于每个子元素的delegate,
// 均记录父delegate以便fallback和回溯
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
// 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;
}
}
}
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
处理过程一共5步:
- 创建delegate并与父delegate合并
- 校验profile
- DOM对象前处理:preProcessXml(root)
- DOM对象解析成BeanDefinition:parseBeanDefinitions(root, this.delegate);
- DOM对象后处理:preProcessXml(root)
在DefaultBeanDefinitionDocumentReader对象中DOM对象前处理和后处理方法均为空方法体
# 1.2.2.1.1 创建delegate
调用createDelegate(getReaderContext(), root, parent)方法来创建解析代理,此方法首先初始化BeanDefinitionParserDelegate实例,并将readerContext传入,然后将一些beans元素的默认属性从父节点扩散到当前节点,这些属性列举如下:
@Nullable
private String lazyInit;
@Nullable
private String merge;
@Nullable
private String autowire;
@Nullable
private String autowireCandidates;
@Nullable
private String initMethod;
@Nullable
private String destroyMethod;
@Nullable
private Object source;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 1.2.2.1.2 校验profile
如果当前处理的是默认命名空间,并且根元素有定义profile属性,则判断运行时参数"spring.profiles.active"是否包含当前的profile,如果不包含,则记录并返回,不进行下一步操作
# 1.2.2.1.3 DOM对象解析成BeanDefinition:preProcessXml(root)
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)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
在此方法中,沿传入的root节点遍历DOM树,对于每个节点,判断namespace,如果是Spring的默认命名空间,则沿树向下遍历到类型为Element的叶子节点,调用delegate的parseDefaultElement(ele,delegate)方法,如果某个节点namespace非默认,则调用delegate的parseCustomElement(ele)方法
# 1.2.2.3.4 parseDefaultElement(ele,delegate)方法
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
通过判断DOM节点的name或localname,比对预定义的三种bean类型(import,alias,bean)来调用对应的process方法,如果发现是嵌套的<beans>对象,则递归调用doRegisterBeanDefinitions(ele)方法。
对于当前实例,xml定义了一个<bean>节点,因此调用processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate)方法,此方法的具体加载过程参见第四节
所有节点parse完毕后,由于处理完毕后没有操作,因此,将当前reader的delegate回溯到parent,如此循环到整个DOM树处理完毕,此时,当前Resource处理完毕,计算Resource处理前后beanDefinition数量的变化,返回变化值,此时整个XmlBeanDefinitionReader的loadBeanDefinitions方法执行完成