【Spring源码解析】Spring xml配置import、alias、beans标签解析

1、前言

上文Spring XML配置默认bean标签解析对bean标签进行的了解析,此文继续解析默认标签标签进行解析。

这三个标签其实都是对bean功能的扩展,下面一一讲解。

2、源码解析

先查看解析的开始位置:

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    if (delegate.nodeNameEquals(ele, "import")) {
        this.importBeanDefinitionResource(ele);
    } else if (delegate.nodeNameEquals(ele, "alias")) {
        this.processAliasRegistration(ele);
    } else if (delegate.nodeNameEquals(ele, "bean")) {
        this.processBeanDefinition(ele, delegate);
    } else if (delegate.nodeNameEquals(ele, "beans")) {
        this.doRegisterBeanDefinitions(ele);
    }

}

2.1、 alias 标签解析

对bean 进行定义的时候,除了可以使用id 命名,为了提供多个别名,使用alias来指定,这些所有的名称都指向同一个bean。

  • 实例
<bean class="com.fans.User1" name="user1"/>
<alias name="user1" alias="user2,user3"/>

这里alias的name 属性指向真实bean标签的 beanName 或者别名,使用idea的时候会提示已注册的内容。

  • 源码解析
this.processAliasRegistration(ele);

进入方法:

  1. 这里获取name属性和alias属性,这两个属性都是获取别名的方式。
  2. this.getReaderContext().getRegistry().registerAlias(name, alias); 完成通过别名注册的过程。
  3. 通知注册时间通知监听器。
protected void processAliasRegistration(Element ele) {
    String name = ele.getAttribute("name");
    String alias = ele.getAttribute("alias");
    boolean valid = true;
    if (!StringUtils.hasText(name)) {
        this.getReaderContext().error("Name must not be empty", ele);
        valid = false;
    }

    if (!StringUtils.hasText(alias)) {
        this.getReaderContext().error("Alias must not be empty", ele);
        valid = false;
    }

    if (valid) {
        try {
            this.getReaderContext().getRegistry().registerAlias(name, alias);
        } catch (Exception var6) {
            this.getReaderContext().error("Failed to register alias '" + alias + "' for bean with name '" + name + "'", ele, var6);
        }

        this.getReaderContext().fireAliasRegistered(name, alias, this.extractSource(ele));
    }

}

以上说到监听器可以看到,都是通过 ReaderContext 读取文件上下文对象中的 ReaderEventListener 进行处理的,这里alias标签对应的是 ReaderEventListener ## aliasRegistered 方法,上文bean标签解析使用的是componentRegistered 方法。其他的标签也都是使用的这里的监听事件触发方法。

继续说注册的过程,this.getReaderContext() 获取XmlReadContext ,也就是XML读取上下文对象,
再获取 BeanDefinitionRegistry 注册对象。

然后通过 registerAlias 方法完成别名的注册,这里接口也有多种实现,通过断点调试发现是
SimpleAliasRegistry 实现类中的方法(其实GenericApplicationContext 实现类只是跳转,还是指向这里)。

 public void registerAlias(String name, String alias) {
    Assert.hasText(name, "'name' must not be empty");
    Assert.hasText(alias, "'alias' must not be empty");
    synchronized(this.aliasMap) {
        if (alias.equals(name)) {
            this.aliasMap.remove(alias);
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Alias definition '" + alias + "' ignored since it points to same name");
            }
        } else {
            String registeredName = (String)this.aliasMap.get(alias);
            if (registeredName != null) {
                if (registeredName.equals(name)) {
                    return;
                }

                if (!this.allowAliasOverriding()) {
                    throw new IllegalStateException("Cannot define alias '" + alias + "' for name '" + name + "': It is already registered for name '" + registeredName + "'.");
                }

                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Overriding alias '" + alias + "' definition for registered name '" + registeredName + "' with new target name '" + name + "'");
                }
            }

            this.checkForAliasCircle(name, alias);
            this.aliasMap.put(alias, name);
            if (this.logger.isTraceEnabled()) {
                this.logger.trace("Alias definition '" + alias + "' registered for name '" + name + "'");
            }
        }

    }
}

这里也有几个步骤,不过和bean属性相比还是比较简单的:

  1. 为了数据安全,对别名缓存aliasMap进行加锁
  2. 判断当前的别名是否和已存在alias name属性值相同 alias.equals(name), 如果相同移除当前别名
  3. 如果不同通过参数别名获取真实注册名,然后和已存在alias name属性值进行比较,如果相同直接返回
  4. 如果可以通过别名获取注册名,判断是否运行重写,不允许则报错
  5. this.checkForAliasCircle(name, alias); 判断是否存在循环依赖,这里和之前bean标签的别名相同
  6. this.aliasMap.put(alias, name); 通过别名进行注册。

通过以上就可以发现,其实和bean标签的别名操作完全相同。
具体可以查看bean标签解析。

2.2、import 标签解析

import 就是为了处理大量的配置文件的编写,所以使用可分模块的方式。

  • 实例
<beans>
  <import reource="consumerContext.xml">
  <import reource="systemContext.xml">
beans>
  • 查看源码
this.importBeanDefinitionResource(ele);

进入方法查看:

protected void importBeanDefinitionResource(Element ele) {
	// (1) 获取Resouce 的属性对应的路径
	String location = ele.getAttribute("resource");
    if (!StringUtils.hasText(location)) {
        this.getReaderContext().error("Resource location must not be empty", ele);
    } else {
    	// (2)解析文件中的特殊属性,例如${user.dir} 等
        location = this.getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);
        Set<Resource> actualResources = new LinkedHashSet(4);
        boolean absoluteLocation = false;

        try {
        	// (3)判断是相对路径还是据对路径
            absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
        } catch (URISyntaxException var11) {
        }

        int importCount;
        if (absoluteLocation) {
            try {
            	// (4) 如果是绝对路径,进行递归调用使用另外一次的解析
                importCount = this.getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
                if (this.logger.isTraceEnabled()) {
                    this.logger.trace("Imported " + importCount + " bean definitions from URL location [" + location + "]");
                }
            } catch (BeanDefinitionStoreException var10) {
                this.getReaderContext().error("Failed to import bean definitions from URL location [" + location + "]", ele, var10);
            }
        } else {
            try {
            	// (5) 如果是相对路径则先解析成绝对路径,然后进行解析,加载资源中的bean 对象
                Resource relativeResource = this.getReaderContext().getResource().createRelative(location);
                if (relativeResource.exists()) {
                    importCount = this.getReaderContext().getReader().loadBeanDefinitions(relativeResource);
                    actualResources.add(relativeResource);
                } else {
                    String baseLocation = this.getReaderContext().getResource().getURL().toString();
                    importCount = this.getReaderContext().getReader().loadBeanDefinitions(StringUtils.applyRelativePath(baseLocation, location), actualResources);
                }

                if (this.logger.isTraceEnabled()) {
                    this.logger.trace("Imported " + importCount + " bean definitions from relative location [" + location + "]");
                }
            } catch (IOException var8) {
                this.getReaderContext().error("Failed to resolve current resource location", ele, var8);
            } catch (BeanDefinitionStoreException var9) {
                this.getReaderContext().error("Failed to import bean definitions from relative location [" + location + "]", ele, var9);
            }
        }

        Resource[] actResArray = (Resource[])actualResources.toArray(new Resource[0]);
        this.getReaderContext().fireImportProcessed(location, actResArray, this.extractSource(ele));
    }
}

查看上述代码可以得到:
(1)获取Resouce 的属性对应的路径
(2)解析文件中的特殊属性,例如${user.dir} 等
(3)判断是相对路径还是据对路径
(4)如果是绝对路径,进行递归调用使用另外一次的解析,加载资源中的bean 对象
(5)如果是相对路径则先解析成绝对路径,然后进行解析,加载资源中的bean 对象
(6)通知监听器,解析完成。这里也是使用

对于第(2)步骤特殊属性处理的位置,进入源码并使用断点查看,

this.getReaderContext().getEnvironment().resolveRequiredPlaceholders(location)

首先进入 PropertyResolver 实现类 AbstractEnvironment ,然后就会发现这只是一个代理。
进入 PropertyResolver 的另外一个实现类 AbstractPropertyResolver。

 public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
    if (this.strictHelper == null) {
        this.strictHelper = this.createPlaceholderHelper(false);
    }

    return this.doResolvePlaceholders(text, this.strictHelper);
}

之后创建 PropertyPlaceholderHelper 协助解析,然后进入创建 PropertyPlaceholderHelper 对象的方法 replacePlaceholders ,再进入 parseStringValue 内部进行的处理:

  1. 获取${ 的坐标 int startIndex = value.indexOf(this.placeholderPrefix);
  2. 获取 } 的坐标 int endIndex = this.findPlaceholderEndIndex(result, startIndex);
  3. 进行截取或者 ${} 内部的值,result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
  4. 递归处理多级结构 ,placeholder = this.parseStringValue(placeholder, placeholderResolver, (Set)visitedPlaceholders);

    后续还有一些操作,此处就不在看了,有兴趣可以自己打断点看下。

解析import resouce内容其实可以理解,这个和解析单独的XML文件也是没有区别的,断点就可以查看进入了bean标签同样的解析方法。

2.3、beans 标签解析

我们使用beans标签的时候,发现其实包含了其他的bean,可以解析其实和 import 功能类似,并完成内部bean的解析注册。查看一下:

this.doRegisterBeanDefinitions(ele);

然后就会发现其实其实更干脆,这个和xml文件解析是相同的,也就是和外部的beans标签功能相同。

protected void doRegisterBeanDefinitions(Element root) {
    BeanDefinitionParserDelegate parent = this.delegate;
    this.delegate = this.createDelegate(this.getReaderContext(), root, parent);
    if (this.delegate.isDefaultNamespace(root)) {
        String profileSpec = root.getAttribute("profile");
        if (StringUtils.hasText(profileSpec)) {
            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
            if (!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + this.getReaderContext().getResource());
                }

                return;
            }
        }
    }

    this.preProcessXml(root);
    this.parseBeanDefinitions(root, this.delegate);
    this.postProcessXml(root);
    this.delegate = parent;
}

3、总结

以上就是import、alias、beans 标签的解析。其实和开始说的一样,都是对bean标签解析功能的增强和扩展。

下面会讲下自定义标签的内容,希望每次查看源码都有收获,共勉!

你可能感兴趣的:(#,Spring,spring,xml,java)