通俗理解spring源码(六)—— 默认标签(import、alias、beans)的解析
上节讲到了documentReader的parseDefaultElement方法,从这就开始解析各种标签了,其中bean标签的解析最为复杂,所以先来看看其他三个默认标签的解析
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); } }
1、import标签的解析
进入importBeanDefinitionResource方法
protected void importBeanDefinitionResource(Element ele) { //获取import标签的resource属性值 String location = ele.getAttribute(RESOURCE_ATTRIBUTE); if (!StringUtils.hasText(location)) { getReaderContext().error("Resource location must not be empty", ele); return; } //对location中的占位符进行替换 // Resolve system properties: e.g. "${user.dir}" location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location); Set<Resource> actualResources = new LinkedHashSet<>(4); //判断是绝对路径还是相对路径 // Discover whether the location is an absolute or relative URI 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 { //如果是绝对路径,这里的readerContext.getReader()获取到的就是最开始的XmlBeanDefinitionReader, //相当于会递归调用loadBeanDefinitions方法,根据location加载新的配置文件 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; //如果是相对路径,则根据当前的Resource解析出其相对的Resource Resource relativeResource = getReaderContext().getResource().createRelative(location); //这是还是调用loadBeanDefinitions方法,只不过是另一个方法重载 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标签的处理,主要是对其resource属性的处理,不管是相对路径还是绝对路径,最终还是拿到相对应的resource资源,调用最开头的loadBeanDefinitions方法,只不过是调用的是loadBeanDefinitions不同的重载方法,核心处理是一样的,相当于递归调用。
最后发起事件的处理,以后会解释。
2、alias标签的解析
spring的alias标签应该是用的比较少的,即使是在之后的注解中,也用的不多,这里还是讲一下,重点是学习它的设计思路。
protected void processAliasRegistration(Element ele) { //解析name属性 String name = ele.getAttribute(NAME_ATTRIBUTE); //解析alias属性 String alias = ele.getAttribute(ALIAS_ATTRIBUTE); //默认校验成功 boolean valid = true; //name非空判断 if (!StringUtils.hasText(name)) { getReaderContext().error("Name must not be empty", ele); valid = false; } //alias空判断 if (!StringUtils.hasText(alias)) { getReaderContext().error("Alias must not be empty", ele); valid = false; } if (valid) { try { //如果校验成功,则开始alias的注册 getReaderContext().getRegistry().registerAlias(name, alias); } catch (Exception ex) { getReaderContext().error("Failed to register alias '" + alias + "' for bean with name '" + name + "'", ele, ex); } //发起事件 getReaderContext().fireAliasRegistered(name, alias, extractSource(ele)); } }
前面的逻辑比较简单,重点是别名的注册,getReaderContext().getRegistry(),就是从ReaderContext中获取注册中心,这里的Registry,就是最开始的DefaultListableBeanFactory。
如果是从此系列第一篇看到这里的,就应该明白ReaderContext上下文是一直贯穿始终的,而其中又引用了XmlBeanDefinitionReader对象和Resource对象。
而DefaultListableBeanFactory不仅是一个beanDefination注册中心,也是一个alias注册中心,其继承于SimpleAliasRegistry。
进入SimpleAliasRegistry.registerAlias(name, alias)。
public void registerAlias(String name, String alias) { Assert.hasText(name, "'name' must not be empty"); Assert.hasText(alias, "'alias' must not be empty"); //这里的map,以alias为key,name为value,保存别名与name的映射关系 synchronized (this.aliasMap) { //如果name和alias相等,就移除该别名与name的映射 if (alias.equals(name)) { this.aliasMap.remove(alias); if (logger.isDebugEnabled()) { logger.debug("Alias definition '" + alias + "' ignored since it points to same name"); } } else { //根据alias得到已注册的name String registeredName = this.aliasMap.get(alias); if (registeredName != null) { //如果name已经存在,就不需要注册了 if (registeredName.equals(name)) { // An existing alias - no need to re-register return; } //判断是否允许别名的覆盖,默认是允许的 if (!allowAliasOverriding()) { throw new IllegalStateException("Cannot define alias '" + alias + "' for name '" + name + "': It is already registered for name '" + registeredName + "'."); } if (logger.isDebugEnabled()) { logger.debug("Overriding alias '" + alias + "' definition for registered name '" + registeredName + "' with new target name '" + name + "'"); } } //检查循环引用 checkForAliasCircle(name, alias); //直接往map中放 this.aliasMap.put(alias, name); if (logger.isTraceEnabled()) { logger.trace("Alias definition '" + alias + "' registered for name '" + name + "'"); } } } }
别名的注册也不是很复杂,各种检查后,将别名与name的映射保存到map中,其中检查别名的循环引用要稍微复杂一点
protected void checkForAliasCircle(String name, String alias) { if (hasAlias(alias, name)) { throw new IllegalStateException("Cannot register alias '" + alias + "' for name '" + name + "': Circular reference - '" + name + "' is a direct or indirect alias for '" + alias + "' already"); } } public boolean hasAlias(String name, String alias) { for (Map.Entry<String, String> entry : this.aliasMap.entrySet()) { String registeredName = entry.getValue(); //判断你要注册的alias有没有作为name被注册过 if (registeredName.equals(name)) { //如果有,则判断这个被注册的name对应的alias是否和你要注册的name相同 String registeredAlias = entry.getKey(); //如果不相同,递归调用当前方法 if (registeredAlias.equals(alias) || hasAlias(registeredAlias, alias)) { return true; } } } return false; }
递归调用hasAlias方法,判断别名有没有被作为name注册过,同时已注册过的别名是否和你要注册的name相等。注意,这里实参name传到了形参alias,实参alias传到了形参name,这样做是别有一番用意的。
看起来有点绕。举个例子。
SimpleAliasRegistry registry = new SimpleAliasRegistry(); registry.registerAlias("a","b"); registry.registerAlias("b","a");
运行这段代码,会抛出这个异常
Cannot register alias 'a' for name 'b': Circular reference - 'b' is a direct or indirect alias for 'a' already
像这种b作为a的别名,而a又作为b的别名的情况,就叫做别名的循环引用。spring不允许这种情况出现,因为没有意义。
这种情况准确来说是直接的循环引用,所以这个报错信息,更明确一点的话是这样的:
'b' is a direct alias for 'a' already
而下面这种情况
registry.registerAlias("a","b"); registry.registerAlias("b","c"); registry.registerAlias("c","a");
在运行第三段代码时会报错,这属于别名的间接循环引用,因为c虽然没有直接注册为a的别名,但是经过前两段代码,c已经间接是a的别名。
这段代码会递归调用hasAlias方法,所以报错信息明确一点应该是
'c' is a indirect alias for 'a' already
3、内嵌beans标签的解析
//解析beans标签 else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // recurse 递归 doRegisterBeanDefinitions(ele); }
内嵌的beans标签可能用的不多,但我们在xml配置文件中,经常会用到import标签导入另一个xml文件,实际上也是导入一个beans标签,所以效果差不多。
类似解析import标签,因为还需要对另一个xml做XSD校验等操作,所以从loadBeanDefinitions开始执行,而解析内嵌beans标签,很简单,直接递归调用最开始的doRegisterBeanDefinitions方法就可以了
这里再贴一遍之前的代码。
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; doRegisterBeanDefinitions(doc.getDocumentElement()); } protected void doRegisterBeanDefinitions(Element root) { // Any nested <beans> elements will cause recursion in this method. In // order to propagate and preserve <beans> 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; //委托给delegate解析 this.delegate = createDelegate(getReaderContext(), root, parent); //判断当前Beans节点是否是默认命名空间 if (this.delegate.isDefaultNamespace(root)) { //获取beans节点的profile属性 String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); if (StringUtils.hasText(profileSpec)) { //可以使用逗号或分号将当前beans标签指定为多个profile类型 String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); // We cannot use Profiles.of(...) since profile expressions are not supported // in XML config. See SPR-12458 for details. //判断当前beans标签的profile是否被激活 if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { if (logger.isDebugEnabled()) { logger.debug("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; }
关于import、alias、beans标签的解析就到这里了,bean标签的解析有点复杂,而解析import、beans标签最终都会进入bean标签的解析,下篇会详细介绍。
走的太远,不要忘记为什么出发!
参考:spring源码深度解析