zoukankan      html  css  js  c++  java
  • 通俗理解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源码深度解析

  • 相关阅读:
    bzoj 1176 cdq分治套树状数组
    Codeforces 669E cdq分治
    Codeforces 1101D 点分治
    Codeforces 1100E 拓扑排序
    Codeforces 1188D Make Equal DP
    Codeforces 1188A 构造
    Codeforces 1188B 式子转化
    Codeforces 1188C DP 鸽巢原理
    Codeforces 1179D 树形DP 斜率优化
    git commit -m "XX"报错 pre -commit hook failed (add --no-verify to bypass)问题
  • 原文地址:https://www.cnblogs.com/xiaohang123/p/13059744.html
Copyright © 2011-2022 走看看