原文出自:http://cmsblogs.comjava
在博客【死磕Spring】----- IOC 之 注册 BeanDefinition中分析到,Spring 中有两种解析 Bean 的方式。若是根节点或者子节点采用默认命名空间的话,则调用 parseDefaultElement()
进行默认标签解析,不然调用 delegate.parseCustomElement()
方法进行自定义解析。因此如下博客就这两个方法进行详细分析说明,先从默认标签解析过程开始,源码以下:node
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { // 对 import 标签的解析 if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { importBeanDefinitionResource(ele); } // 对 alias 标签的解析 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。咱门从第一个标签 import 开始。spring
经历过 Spring 配置文件的小伙伴都知道,若是工程比较大,配置文件的维护会让人以为恐怖,文件太多了,想象将全部的配置都放在一个 spring.xml 配置文件中,哪一种后怕感是否是很明显?全部针对这种状况 Spring 提供了一个分模块的思路,利用 import 标签,例如咱们能够构造一个这样的 spring.xml。app
<?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 便可,这样大大简化了配置后期维护的复杂度,同时也易于管理。less
Spring 利用 importBeanDefinitionResource()
方法完成对 import 标签的解析。ide
protected void importBeanDefinitionResource(Element ele) { // 获取 resource 的属性值 String location = ele.getAttribute(RESOURCE_ATTRIBUTE); // 为空,直接退出 if (!StringUtils.hasText(location)) { getReaderContext().error("Resource location must not be empty", ele); return; } // 解析系统属性,格式如 :"${user.dir}" location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location); Set<Resource> actualResources = new LinkedHashSet<>(4); // 判断 location 是相对路径仍是绝对路径 boolean absoluteLocation = false; try { absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute(); } catch (URISyntaxException ex) { // cannot convert to an URI, considering the location relative // unless it is the well-known Spring prefix "classpath*:" } // 绝对路径 if (absoluteLocation) { try { // 直接根据地质加载相应的配置文件 int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources); if (logger.isDebugEnabled()) { logger.debug("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.isDebugEnabled()) { logger.debug("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 过程较为清晰,整个过程以下:ui
判断路径spa
方法经过如下方法来判断 location 是为相对路径仍是绝对路径:.net
absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
判断绝对路径的规则以下:debug
java.net.URL
为绝对路径java.net.URI
判断调用 isAbsolute()
判断是否为绝对路径绝对路径
若是 location 为绝对路径则调用 loadBeanDefinitions()
,该方法在 AbstractBeanDefinitionReader 中定义。
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException { ResourceLoader resourceLoader = getResourceLoader(); if (resourceLoader == null) { throw new BeanDefinitionStoreException( "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available"); } if (resourceLoader instanceof ResourcePatternResolver) { // Resource pattern matching available. 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 { // Can only load single resources by absolute 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; } }
整个逻辑比较简单,首先获取 ResourceLoader,而后根据不一样的 ResourceLoader 执行不一样的逻辑,主要是可能存在多个 Resource,可是最终都会回归到 XmlBeanDefinitionReader.loadBeanDefinitions()
,因此这是一个递归的过程。
相对路径
若是是相对路径则会根据相应的 Resource 计算出相应的绝对路径,而后根据该路径构造一个 Resource,若该 Resource 存在,则调用 XmlBeanDefinitionReader.loadBeanDefinitions()
进行 BeanDefinition 加载,不然构造一个绝对 location ,调用 AbstractBeanDefinitionReader.loadBeanDefinitions()
方法,与绝对路径过程同样。
至此,import 标签解析完毕,整个过程比较清晰明了:获取 source 属性值,获得正确的资源路径,而后调用 loadBeanDefinitions()
方法进行递归的 BeanDefinition 加载