体能状态先于精神状态,习惯先于决心,聚焦先于喜好。
本文讲述的是Spring 在加载配置文件时的流程,从 application-*.xml 这种到具体分类型接续。
其中有颜色背景的两个是for循环处理。
蓝色线表示递归调用。
红线部分表示别名。
Spring 对配置文件的处理思路即使,location-多个配置文件-每个配置文件区分 profile——每个 profile 区分四种标签 import、alias、beans、bean
org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(
负责将加载到的bean definitions 文件解析为具体的bean实例
从指定的 location 资源路径加载 bean 定义信息。location 可以是简单的路径,但是也可以是 ResourcePatternResolver 类型,这样的话就需要对 ResourcePatternResolver 进行处理了,在初始阶段其包含一个 资源的set。
location 值可能为空,表示调用者对于资源不感兴趣。
最终返回发现的bean definitions 的数量
/**
* @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, Set<Resource> actualResources) throws BeanDefinitionStoreException {
//获取 资源加载器
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
//如果资源加载器属于 ResourcePatternResolver,则需要解析
if (resourceLoader instanceof ResourcePatternResolver) {
// 对 location 进行解析,Pattern模式可以生成多个 Resource 类型数组
try {
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
//具体的对资源进行解析-你可以看到,这里是一个递归调用
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
}
return loadCount;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
else {
// 使用 绝对URL只能加载单个的资源
Resource resource = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
}
}
ResourcePatternResolver 类型表示资源包含多个 Resource的资源,而Spring 是以Resource作为基本单位进行Bean definitions 的解析的。
简单来说就是,加入你配置的是 classpath:application-*.xml,他就是寻找所有符合该通配符命名的文件,比如你有文件是 application-A.xml 、application-B.xml,那么就会生成两个Resource来进行解析,然后每个 resource就会进入int loadCount = loadBeanDefinitions(resource);
的逻辑
org.springframework.core.io.support.PathMatchingResourcePatternResolver.getResources(
//locationPattern 的形式如 classpath:applicationContext.xml 或 WEB-INF/*.xml等
@Override
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
//路径是否以 classpath*: 开头
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
// 一个类路径资源-可能包含多个同名文件
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
//满足一个类路径的通配,Ant风格
return findPathMatchingResources(locationPattern);
}
else {
// 所有的类资源使用给定的名字
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
else {
// 仅仅查找去除前缀的匹配,比如 classpath:123.xml,则只匹配123.xml
// (避免奇怪的前缀).
int prefixEnd = locationPattern.indexOf(":") + 1;
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
// 查找 Ant 风格
return findPathMatchingResources(locationPattern);
}
else {
// 单个给定名字的文件
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}
org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(
@Override
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
int counter = 0;
for (Resource resource : resources) {
counter += loadBeanDefinitions(resource);
}
return counter;
}
org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(
从指定的xml文件加载 bean definitions
/**
* @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
*/
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
//调用方法如下
return loadBeanDefinitions(new EncodedResource(resource));
}
/**
* @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 {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isInfoEnabled()) {
logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<EncodedResource>(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());
}
//xml文件以指定字符格式转化为了输入流,在这里进行解析
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();
}
}
}
org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions
对输入流进行具体解析,到这里才开始真正的解析xml文件
/**
* @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
*/
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
//将输入流转化为 Document 格式
Document doc = doLoadDocument(inputSource, resource);
//注册bean
return registerBeanDefinitions(doc, resource);
}
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);
}
}
org.springframework.beans.factory.xml.XmlBeanDefinitionReader.registerBeanDefinitions
注册 DOM document 中包含的 bean definitions
/**
* @param doc the DOM document
* @param resource the resource descriptor (for context information)
* @return the number of bean definitions found
* @throws BeanDefinitionStoreException in case of parsing errors
* @see #loadBeanDefinitions
* @see #setDocumentReaderClass
* @see BeanDefinitionDocumentReader#registerBeanDefinitions
*/
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;
}
org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.registerBeanDefinitions(
开始解析Document 文件了,但是依旧是委托给了另一个方法
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);
}
org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions(
- beans 标签和 profile 标签一起使用,即多版本
<beans profile="test2">
<import resource="">import>
<beans profile="test1">
<import resource="">import>
<beans profile="dev">
<import resource="">import>
beans>
beans>
beans>
这个标签用于划分版本,指定多版本时可以指定一个为激活, 关于 profile标签的使用可以参考 Spring 中的 profile
解析 profile 标签
/**
* Register each bean definition within the given root {@code } element.
*/
protected void doRegisterBeanDefinitions(Element root) {
// Any nested elements will cause recursion in this method. In
// order to propagate and preserve 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 parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
//profile 标签
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
//MULTI_VALUE_ATTRIBUTE_DELIMITERS 为 “,;”
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isInfoEnabled()) {
logger.info("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;
}
org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseBeanDefinitions(
解析 “import”, “alias”, “bean”. 标签
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;
//判断是否是默认 namespaceUri 是否是 http://www.springframework.org/schema/beans
if (delegate.isDefaultNamespace(ele)) {
//对该节点进行具体解析-按照类型 import、alias、bean、beans
parseDefaultElement(ele, delegate);
}
else {
//用户自定义了 namespaceUri
delegate.parseCustomElement(ele);
}
}
}
}
else {
//用户自定义的标签
delegate.parseCustomElement(root);
}
}
org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseDefaultElement(
按照类型解析 import、alias、bean、beans。
进入具体源码后我们可以发现一个规律,以下四种类型的划分不是绝对平等的,因为 import可以引用其他文件,beans可以包含多个bean,
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
//解析 import 标签,可以引用其他配置文件
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
// 解析 alias 标签,可以为bean设置不同的别名
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
//解析 bean 标签
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
//解析 beans 标签
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
限于篇幅,对于具体 import、alias、bean、beans 标签的具体解析我将再新起一篇文章( Spring源码学习:DefaultBeanDefinitionDocumentReader.parseDefaultElement),但是看完本文基本可以看到Spring在处理配置文件的基本思路了。