• 《spring源码解读》


    spring-framework.jpg

    在上一文中我们分析了注册 BeanDefinition 的过程,在其中我们了解到在解析跟节点和子节点时分两种情况,对于默认名称空间的标签我们通过 DefaultBeanDefinitionDocumentReader#parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) 进行处理,而对于自定义标签则通过 BeanDefinitionParserDelegate#parseCustomElement(Element ele) 方法进行处理。


    这里我们首先对默认名称空间的解析进行开始解读, #parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) 方法的代码如下:

    	public static final String NESTED_BEANS_ELEMENT = "beans";
    
    	public static final String ALIAS_ELEMENT = "alias";
    
    	public static final String NAME_ATTRIBUTE = "name";
    
    	public static final String ALIAS_ATTRIBUTE = "alias";
    
    	public static final String IMPORT_ELEMENT = "import";
    	/**
    	 * 如果根节点或者子节点采用默认命名空间的话 采用默认的解析方式
    	 * @param ele
    	 * @param delegate
    	 */
    	private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    		// import 标签
    		if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
    			importBeanDefinitionResource(ele);
    		}
    		else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
    			//alias 标签
    			processAliasRegistration(ele);
    		}
    		else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
    			//处理 bean 标签 这是spring中很核心的标签处理
    			processBeanDefinition(ele, delegate);
    		}
    		else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
    			// 处理 beans 标签
    			doRegisterBeanDefinitions(ele);
    		}
    	}
    
    • 通过上述代码我们可以的看到默认标签包含 importaliasbeanbeans , 本文将对 import 的解析进行解读

    1. Import 案例

    经历过 Spring 配置文件的小伙伴都知道,如果工程比较大,配置文件的维护会让人觉得恐怖,文件太多了,想象将所有的配置都放在一个 spring.xml 配置文件中,哪种后怕感是不是很明显?
    所有针对这种情况 Spring 提供了一个分模块的思路,利用 import 标签,例如我们可以构造一个这样的 spring.xml

    <?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 即可。

    这样大大简化了配置后期维护的复杂度,同时也易于管理。

    2. importBeanDefinitionResource

    DefaultBeanDefinitionDocumentReader#parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) 方法用户解析 import 标签,方法代码如下:

    	protected void importBeanDefinitionResource(Element ele) {
    		// 1.获取 节点 属性resource的值
    		String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
    		//2. 判断是否为空,为空直接返回
    		if (!StringUtils.hasText(location)) {
    			getReaderContext().error("Resource location must not be empty", ele);
    			return;
    		}
    
    		//3.解析 系统属性  ${user.dir}
    		// Resolve system properties: e.g. "${user.dir}"
    		location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);
    
    		// 实际 Resource 集合, 即 import 的地址,
    		Set<Resource> actualResources = new LinkedHashSet<>(4);
    
    		// Discover whether the location is an absolute or relative URI
    		// 检查路径 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*:"
    		}
    
    		// Absolute or relative?
    		// 绝对路径
    		if (absoluteLocation) {
    			try {
    				// 解析 location 得到 resource 并且添加到 actualResources中
    				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 {
    			//相对路径
    			// No URL -> considering resource location as relative to the current file.
    			try {
    				int importCount;
    				// 解析 location 路径,得到相对路径的 Resource relativeResource
    				Resource relativeResource = getReaderContext().getResource().createRelative(location);
    				//存在
    				if (relativeResource.exists()) {
    					// 加载 resource 中的 Definition
    					importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
    					// 添加 Resource 到 relativeResource
    					actualResources.add(relativeResource);
    				}
    				else {
    					// 获取根路径
    					String baseLocation = getReaderContext().getResource().getURL().toString();
    					// 通过 根路径与相对路径获取到 Resource 并且添加到 actualResources,同时加载相应的 BeanDefinition
    					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> 处,获取 Resource 属性的值,该值表示资源的路径。

    • <2> 处,解析路径中的系统属性,如 "${user.dir}"

    • <3> 处,判断资源路径 location 是绝对路径还是相对路径。详细解析,见 「2.1 判断路径」 。

    • <4> 处,如果是绝对路径,则调递归调用 Bean 的解析过程,进行另一次的解析。详细解析,见 「2.2 处理绝对路径」 。

    • <5> 处,如果是相对路径,则先计算出绝对路径得到 Resource ,然后进行解析。详细解析,见 「2.3 处理相对路径」 。

    • <6> 处,通知监听器,完成解析。

    • 上述代码的执行过程 UML 如下:


    2.1 判断路径

    在上述代码中,通过判断 location 是否是绝对路径的代码如下:

    absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
    
    • ResourcePatternUtils.isUrl(location) 如果是以 classpath*: 或者 classpath: 开头则为绝对路径,能够通过该 location 构建 java.net.URL 为绝对路径
    • 根据 location 构建 java.net.URI 判断调用 #isAbsolute() 方法,判断是否为绝对路径

    2.2 处理绝对路径


    如果  location  为绝对路径,则调用  #loadBeanDefinitions(String location, Set<Resource> actualResources) , 方法。该方在  org.springframework.beans.factory.support.AbstractBeanDefinitionReader  中定义,代码如下:

    	public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
    		// 获得 ResourceLoader 对象
    		ResourceLoader resourceLoader = getResourceLoader();
    		if (resourceLoader == null) {
    			throw new BeanDefinitionStoreException(
    					"Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
    		}
    
    		if (resourceLoader instanceof ResourcePatternResolver) {
    			// Resource pattern matching available.
    			try {
    				// 获得 Resource 数组,因为 Pattern 模式匹配下,可能有多个 Resource 。例如说,Ant 风格的 location
    				Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
    				// 加载 BeanDefinition 们
    				int count = loadBeanDefinitions(resources);
    				if (actualResources != null) {
    					// 添加到 actualResources
    					Collections.addAll(actualResources, resources);
    				}
    				if (logger.isTraceEnabled()) {
    					logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
    				}
    				return count;
    			}
    			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 resource = resourceLoader.getResource(location);
    			// 加载 BeanDefinition 们
    			int count = loadBeanDefinitions(resource);
    			if (actualResources != null) {
    				// 添加到 actualResources
    				actualResources.add(resource);
    			}
    			if (logger.isTraceEnabled()) {
    				logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
    			}
    			return count;
    		}
    	}
    
    • 上述代码执行过程的 UML 图如下





    整个逻辑比较简单 :

    • 首先,获取 ResourceLoader 对象。
    • 然后,根据不同的 ResourceLoader 执行不同的逻辑,主要是可能存在多个 Resource 。
    • 最终,都会回归到 XmlBeanDefinitionReader#loadBeanDefinitions(Resource... resources) 方法,所以这是一个递归的过程。
    • 另外,获得到的 Resource 的对象或数组,都会添加到 actualResources 中。

    2.3 处理相对路径


    如果 location 是相对路径,则会根据相应的 Resource 计算出相应的相对路径的 Resource 对象 ,然后:

    • 若该 Resource 存在,则调用 XmlBeanDefinitionReader#loadBeanDefinitions() 方法,进行 BeanDefinition 加载。

    • 否则,构造一个绝对 location ( 即 StringUtils.applyRelativePath(baseLocation, location) 处的代码),并调用 #loadBeanDefinitions(String location, Set<Resource> actualResources) 方法,与绝对路径过程一样

    3. 小结


    至此, import 标签解析完毕,整个过程比较清晰明了:获取 source 属性值,得到正确的资源路径,然后调用 XmlBeanDefinitionReader#loadBeanDefinitions(Resource... resources) 方法,进行递归的 BeanDefinition 加载。

    本文由AnonyStar 发布,可转载但需声明原文出处。
    仰慕「优雅编码的艺术」 坚信熟能生巧,努力改变人生
    欢迎关注微信公账号 :云栖简码 获取更多优质文章
    更多文章关注笔者博客 :云栖简码

  • 相关阅读:
    java的类继承(与c++对比)
    java的数据类型、自动拆装箱、字面量
    java中关键字static和final
    JVM之JIT
    java之JIT(Just in time)
    栈和堆
    C++中vector的使用
    canvas
    ajax笔记
    CSS笔记
  • 原文地址:https://www.cnblogs.com/i-code/p/13602096.html
Copyright © 2020-2023  润新知