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

  • 相关阅读:
    会计科目不能使用
    SAP提示为创建科目作为控制范围中成本要素
    创建成本要素
    拓端tecdat:R语言集成模型:提升树boosting、随机森林、约束最小二乘法加权平均模型融合分析时间序列数据
    拓端tecdat:R语言用贝叶斯线性回归、贝叶斯模型平均 (BMA)来预测工人工资
    拓端tecdat:数据评估三方科技公司开发人员能力
    拓端tecdat:R语言因子实验设计nlme拟合非线性混合模型分析有机农业施氮水平
    拓端tecdat:R语言主成分回归(PCR)、 多元线性回归特征降维分析光谱数据和汽车油耗、性能数据
    Go的异常处理 defer, panic, recover
    Android项目架构设计深入浅出
  • 原文地址:https://www.cnblogs.com/xiaohang123/p/13059744.html
Copyright © 2011-2022 走看看