通俗理解spring源码(六)—— 默认标签(import、alias、beans)的解析

通俗理解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源码深度解析

上一篇:新版本kali root用户下文件文件夹配色


下一篇:nginx的alias和root的区别