在【Spring源码】IoC之注册BeanDefinitions 提到,Spring 有两种解析 Bean 的方式:
如果根节点或者子节点采用默认命名空间的话,则调用 #parseDefaultElement(...) 方法,进行默认 标签解析
否则,调用 BeanDefinitionParserDelegate#parseCustomElement(...) 方法,进行自定义 解析。
本文就对这两个方法进行详细分析说明。
默认标签解析过程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public static final String IMPORT_ELEMENT = "import" ;public static final String ALIAS_ATTRIBUTE = "alias" ;public static final String BEAN_ELEMENT = BeanDefinitionParserDelegate.BEAN_ELEMENT;public static final String NESTED_BEANS_ELEMENT = "beans" ;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)) { doRegisterBeanDefinitions(ele); } }
该方法的功能一目了然,分别是对四种不同的标签进行解析,分别是 import、alias、bean、beans 。咱门从第一个标签 import 开始。
import 示例 经历过 Spring 配置文件的小伙伴都知道,如果工程比较大,配置文件的维护会让人觉得恐怖,文件太多了,想象将所有的配置都放在一个 spring.xml 配置文件中,哪种后怕感是不是很明显?
所有针对这种情况 Spring 提供了一个分模块的思路,利用 import 标签,例如我们可以构造一个这样的 spring.xml 。
1 2 3 4 5 6 7 8 9 10 11 <?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" > <import resource="spring-student.xml" /> <import resource="spring-student-dtd.xml" /> </beans>
spring.xml 配置文件中,使用 import 标签的方式导入其他模块的配置文件。
如果有配置需要修改直接修改相应配置文件即可。
若有新的模块需要引入直接增加 import 即可。
这样大大简化了配置后期维护的复杂度,同时也易于管理。
importBeanDefinitionResource Spring 使用 #importBeanDefinitionResource(Element ele) 方法,完成对 import 标签的解析。
1 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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 protected void importBeanDefinitionResource (Element ele) { String location = ele.getAttribute(RESOURCE_ATTRIBUTE); if (!StringUtils.hasText(location)) { getReaderContext().error("Resource location must not be empty" , ele); return ; } location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location); Set<Resource> actualResources = new LinkedHashSet<>(4 ); boolean absoluteLocation = false ; try { absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute(); } catch (URISyntaxException ex) { } if (absoluteLocation) { try { int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources); if (logger.isTraceEnabled()) { logger.trace("Imported " + importCount + " bean definitions from URL location [" + location + "]" ); } } catch (BeanDefinitionStoreException ex) { getReaderContext().error( "Failed to import bean definitions from URL location [" + location + "]" , ele, ex); } } else { try { int importCount; Resource relativeResource = getReaderContext().getResource().createRelative(location); if (relativeResource.exists()) { importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource); actualResources.add(relativeResource); } else { String baseLocation = getReaderContext().getResource().getURL().toString(); importCount = getReaderContext().getReader().loadBeanDefinitions( StringUtils.applyRelativePath(baseLocation, location) , actualResources); } if (logger.isTraceEnabled()) { logger.trace("Imported " + importCount + " bean definitions from relative location [" + location + "]" ); } } catch (IOException ex) { getReaderContext().error("Failed to resolve current resource location" , ele, ex); } catch (BeanDefinitionStoreException ex) { getReaderContext().error( "Failed to import bean definitions from relative location [" + location + "]" , ele, ex); } } Resource[] actResArray = actualResources.toArray(new Resource[0 ]); getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele)); }
解析 import 标签过程较为清晰,整个过程如下:
<1> 处,获取 source 属性的值,该值表示资源的路径
<2> 处,解析路径中的系统属性,如 “${user.dir}”
<3> 处,判断资源路径 location 是绝对路径还是相对路径。
<4> 处,如果是绝对路径,则递归调用 Bean 的解析过程,进行另一次的解析。
<5> 处,若果是相对路径,则先计算出绝对路径得到 Resource,然后进行解析。
<6> 处,通知监听器,完成解析。
判断路径 通过以下代码,来判断 location 是为相对路径还是绝对路径
1 2 absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
判断绝对路径的规则如下
<1> 以 classpath*: 或者 classpath: 开头的为绝对路径
<1> 能够通过该 location 构建出 java.net.URL 为绝对路径
<2> 根据 location 构造 java.net.URI 判断调用 #isAbsolute() 方法,判断是否为绝对路径。
参考 原创出处 http://cmsblogs.com/?p=2724 「小明哥」,略作修改及补充