资讯专栏INFORMATION COLUMN

Spring源码一(容器的基本实现2)

yagami / 2629人阅读

摘要:进一步解析其他所有属性并统一封装至类型的实例中。是一个接口,在中存在三种实现以及。通过将配置文件中配置信息转换为容器的内部表示,并将这些注册到中。容器的就像是配置信息的内存数据库,主要是以的形式保存。而代码的作用就是实现此功能。

前言:继续前一章。 一、porfile 属性的使用

如果你使用过SpringBoot, 你一定会知道porfile配置所带来的方便, 通过配置开发环境还是生产环境, 我们可以十分方便的切换开发环境,部署环境,更换不同的数据库。 可能为了让Java开发者转向SpringBoot开发, Spring在5.x之后停止了对这个属性的支持。所以本文也就不再继续描述这一属性。

二、bean标签的解析及注册
Spring中的标签分为默认标签和自定义标签两种,而这两种标签的用法及解析方式存在着很大的不同,默认标签是在parseDefaultElement中进行的,函数中的功能一目了然,分别对4种标签(import, alias、bean、beans)做了不同的处理。
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
        importBeanDefinitionResource(ele);
    }
    else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
        processAliasRegistration(ele);
    }
    else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
        processBeanDefinition(ele, delegate);
    }
    else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
        // recurse
        doRegisterBeanDefinitions(ele);
    }
}

我们不得不承认,Spring5.x提倡我们更少的使用xml文件,而是更多的使用注解进行配置,而且如果你经常使用Springboot的话,那么你肯定知道习惯优于约定,并且springboot中只需要一个配置文件,虽然这有时根本无法满足需求,这里不做关于springboot的更多的说明。不过这并不影响Spring内部的实现,现在主要还是从xml文件分析一下bean标签的解析及注册。

在4中标签的解析中,对bean标签的解析最为复杂也最为重要, 所以我们从这个标签进行深入的分析。不过在这之前我还是要将之前怎么加载这个文件的部分进行一下回忆

还记得上一部分,有一个这样的方法:

/**
 * This implementation parses bean definitions according to the "spring-beans" XSD
 * (or DTD, historically).
 * 

Opens a DOM Document; then initializes the default settings * specified at the {@code } level; then parses the contained bean definitions. */ @Override public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; doRegisterBeanDefinitions(doc.getDocumentElement()); }

如果确实对一步感兴趣可以追溯下去,这样就可以发现下面这段代码:

/**
     * Parse the elements at the root level in the document:
     * "import", "alias", "bean".
     * @param root the DOM root element of the document
     */
    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        if (delegate.isDefaultNamespace(root)) {
            NodeList nl = root.getChildNodes();
            for (int i = 0; i < nl.getLength(); i++) {
                Node node = nl.item(i);
                if (node instanceof Element) {
                    Element ele = (Element) node;
                    if (delegate.isDefaultNamespace(ele)) {
                        parseDefaultElement(ele, delegate);
                    }
                    else {
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        }
        else {
            delegate.parseCustomElement(root);
        }
    }

这段代码可能有点难以理解,不过当知道了if(delegate.isDefaultNamespace(ele)) 这个方法的作用就知道了,这其实就是在对标签进行一次处理而已, 是默认标签的就交给默认的处理方式,是自定义标签的话就换另一种处理方式。这就是这个方法中做的事了。

 public boolean isDefaultNamespace(Node node) {
        return isDefaultNamespace(getNamespaceURI(node));
}

这里的Node节点定义了所有的Spring提供的默认标签的解析结果。

parseDefaultElement(ele, delegate)这个方法又在做些什么呢?其实不过是对根级节点的标签进行解析分类而已,现在我们先分析一下bean标签, 所以现在只看针对于标签做了些什么。

else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
    processBeanDefinition(ele, delegate);
}
进入这个方法

/**
 * Process the given bean element, parsing the bean definition
 * and registering it with the registry.
 */
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    if (bdHolder != null) {
        bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
        try {
            // Register the final decorated instance.
            BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
        }
        catch (BeanDefinitionStoreException ex) {
            getReaderContext().error("Failed to register bean definition with name "" +
                    bdHolder.getBeanName() + """, ele, ex);
        }
        // Send registration event.
        getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
    }
}
这里使用Spring源码深入解析的一段:
三、解析BeanDefiniton

这个部分是Spring解析配置文件的最重要的部分, 根本就是解析标签并加载, 在使用Spring进行配置的时候不难发现, 我们有以下几个重要的根级标签,bean, imort, alias, nested-beans, 下面的内容就主要介绍一下bean标签的解析。上一个小结的结尾部分已经涉及了这个的处理,继续上面的内容, 我们会发现,实际上Spring首先是先通过“Bean定义解析委托”来获得了一个BeanDefinitionHolder, 在上面的分析中,我们似乎只注意了Element,而忘记了委托的是什么时候出现的,事实上这个委托是在DefaultBeanDefinitionDocumentReader在这个类中的时候就已经创建了这个委托, 并且一直通过参数的方式保存着这个委托, 知道们希望获得一个BeanDefinitionHolder的时候才真正的发挥作用,那么这个委托具体是什么呢?这个委托的作用是状态的保存, 早在DefaultBeanDefinitionDocumentReader 这个类中使用的时候就通过xml解析的上下文,保存了bean标签中的所有状态,这些状态包括,


....
等等等……
那么BeanDefinitionHolder的作用又是什么呢? 通过这个holder, 可是实现注册的功能这是一个十分重要的功能,后面会具体分析这个功能。现在首先要看的是怎么获得的这个holder呢:

/**
     * Parses the supplied {@code } element. May return {@code null}
     * if there were errors during parse. Errors are reported to the
     * {@link org.springframework.beans.factory.parsing.ProblemReporter}.
     */
    @Nullable
    public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
        String id = ele.getAttribute(ID_ATTRIBUTE);
        String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

        List aliases = new ArrayList<>();
        if (StringUtils.hasLength(nameAttr)) {
            String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
            aliases.addAll(Arrays.asList(nameArr));
        }

        String beanName = id;
        if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
            beanName = aliases.remove(0);
            if (logger.isTraceEnabled()) {
                logger.trace("No XML "id" specified - using "" + beanName +
                        "" as bean name and " + aliases + " as aliases");
            }
        }

        if (containingBean == null) {
            checkNameUniqueness(beanName, aliases, ele);
        }

        AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
        if (beanDefinition != null) {
            if (!StringUtils.hasText(beanName)) {
                try {
                    if (containingBean != null) {
                        beanName = BeanDefinitionReaderUtils.generateBeanName(
                                beanDefinition, this.readerContext.getRegistry(), true);
                    }
                    else {
                        beanName = this.readerContext.generateBeanName(beanDefinition);
                        // Register an alias for the plain bean class name, if still possible,
                        // if the generator returned the class name plus a suffix.
                        // This is expected for Spring 1.2/2.0 backwards compatibility.
                        String beanClassName = beanDefinition.getBeanClassName();
                        if (beanClassName != null &&
                                beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
                                !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
                            aliases.add(beanClassName);
                        }
                    }
                    if (logger.isTraceEnabled()) {
                        logger.trace("Neither XML "id" nor "name" specified - " +
                                "using generated bean name [" + beanName + "]");
                    }
                }
                catch (Exception ex) {
                    error(ex.getMessage(), ele);
                    return null;
                }
            }
            String[] aliasesArray = StringUtils.toStringArray(aliases);
            return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
        }

        return null;
    }


此处引用Spring源码解析中的一段内容:

以上便是对默认标签解析的全过程了。当然,对Spring的解析犹如洋葱剥皮一样,一层一层的进行,尽管现在只能看到对属性id以及name的解析,但是很庆幸,思路我们已经了解了。在开始对属性进行全面分析之前, Spring在最外层做了一个当前成的功能架构, 在当前成完成的主要工作包括以下的内容。
(1)提取元素中的id和name属性。
(2)进一步解析其他所有属性并统一封装至GenericBeanDefinition类型的实例中。
(3)如果检测到bean没有指定beanName,那么使用默认规则为此Bean生成beanName。
(4)将检测到的信息封装到BeanDefintionHolder的实例中。

继续跟进代码:

/**
 * Parse the bean definition itself, without regard to name or aliases. May return
 * {@code null} if problems occurred during the parsing of the bean definition.
 */
@Nullable
public AbstractBeanDefinition parseBeanDefinitionElement(
        Element ele, String beanName, @Nullable BeanDefinition containingBean) {

    this.parseState.push(new BeanEntry(beanName));

    String className = null;
    if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
        className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
    }
    String parent = null;
    if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
        parent = ele.getAttribute(PARENT_ATTRIBUTE);
    }

    try {
        AbstractBeanDefinition bd = createBeanDefinition(className, parent);

        parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
        bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

        parseMetaElements(ele, bd);
        parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
        parseReplacedMethodSubElements(ele, bd.getMethodOverrides());

        parseConstructorArgElements(ele, bd);
        parsePropertyElements(ele, bd);
        parseQualifierElements(ele, bd);

        bd.setResource(this.readerContext.getResource());
        bd.setSource(extractSource(ele));

        return bd;
    }
    catch (ClassNotFoundException ex) {
        error("Bean class [" + className + "] not found", ele, ex);
    }
    catch (NoClassDefFoundError err) {
        error("Class that bean class [" + className + "] depends on not found", ele, err);
    }
    catch (Throwable ex) {
        error("Unexpected failure during bean definition parsing", ele, ex);
    }
    finally {
        this.parseState.pop();
    }

    return null;
}

通过对代码的跟踪,事实上,我们很容易发现,这里的className就是从上一个方法中的通过解析别名得到beanName,在这里通过beanName又从已存的元素中获取得到的。同样的这个标签的父级元素parent也是这样获取得到。而接下来的操作也就是对各种属性的具体的解析操作了,诸如:me他, lookup-method, replace-method, property, qualifier子元素等。

BeanDefinition是一个接口,在Spring中存在三种实现:RootBeanDefinition、ChildBeanDefinition以及GenericBeanDefinition。三种实现均继承了AbstractBeanDefinition,其中BeanDefinition是配置文件元素标签在容器中的内部表示形式。元素标签拥有class、scope、lazy-init等配置属性,BeanDefinition则提供了相应的beanClass、scope、lazyInit属性,BeanDefinition和中的属性是一一对应的。其中RootBeanDefinition是最常用的实现类,它对应一般性的元素标签,GenericBeanDefinition是自2.5版本以后新加入的bean文件配置属性定义类,是一站式服务类。
在配置文件中可以定义父和子,父用RootBeanDefinition表示,而子用ChildBeanDefinition表示,而没有父就使用RootBeanDefinition表示。AbstractBeanDefinition对两者共同的类信息进行抽象。
Spring通过BeanDefinition将配置文件中配置信息转换为容器的内部表示,并将这些BeanDefinition注册到BeanDefinitionRegistry中。Spring容器的BeanDefinitionRestry就像是Spring配置信息的内存数据库,主要是以map的形式保存。后续操作直接从BeanDefinitionRegistry中读取配置信息。
BeanDefinition 及其实现类

由此可知,要解析属性首先要创建用于承载属性的实例,也就是创建GenericBeanDefinition类型的实例。而代码createBeanDefinition(className, parent)的作用就是实现此功能。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/72751.html

相关文章

  • Spring IOC 容器源码分析系列文章导读

    摘要:本文是容器源码分析系列文章的第一篇文章,将会着重介绍的一些使用方法和特性,为后续的源码分析文章做铺垫。我们可以通过这两个别名获取到这个实例,比如下面的测试代码测试结果如下本小节,我们来了解一下这个特性。 1. 简介 Spring 是一个轻量级的企业级应用开发框架,于 2004 年由 Rod Johnson 发布了 1.0 版本。经过十几年的迭代,现在的 Spring 框架已经非常成熟了...

    NSFish 评论0 收藏0
  • 起来读Spring源码吧(容器初始化

    摘要:对于开发者来说,无疑是最常用也是最基础的框架之一。概念上的东西还是要提一嘴的用容器来管理。和是容器的两种表现形式。定义了简单容器的基本功能。抽象出一个资源类来表示资源调用了忽略指定接口的自动装配功能委托解析资源。 对于Java开发者来说,Spring无疑是最常用也是最基础的框架之一。(此处省略1w字吹Spring)。相信很多同行跟我一样,只是停留在会用的阶段,比如用@Component...

    libxd 评论0 收藏0
  • Java深入-框架技巧

    摘要:从使用到原理学习线程池关于线程池的使用,及原理分析分析角度新颖面向切面编程的基本用法基于注解的实现在软件开发中,分散于应用中多出的功能被称为横切关注点如事务安全缓存等。 Java 程序媛手把手教你设计模式中的撩妹神技 -- 上篇 遇一人白首,择一城终老,是多么美好的人生境界,她和他历经风雨慢慢变老,回首走过的点点滴滴,依然清楚的记得当初爱情萌芽的模样…… Java 进阶面试问题列表 -...

    chengtao1633 评论0 收藏0
  • Java深入-框架技巧

    摘要:从使用到原理学习线程池关于线程池的使用,及原理分析分析角度新颖面向切面编程的基本用法基于注解的实现在软件开发中,分散于应用中多出的功能被称为横切关注点如事务安全缓存等。 Java 程序媛手把手教你设计模式中的撩妹神技 -- 上篇 遇一人白首,择一城终老,是多么美好的人生境界,她和他历经风雨慢慢变老,回首走过的点点滴滴,依然清楚的记得当初爱情萌芽的模样…… Java 进阶面试问题列表 -...

    dance 评论0 收藏0
  • Spring源码容器基本实现1)

    摘要:下面跟踪代码到这个实现中看看是怎么做的在实例化的过程中,在构造函数中调用了其超类的构造函数,而在超类中对其所处换环境进行的判断,所谓的环境呢,事实上指得就是是通过,还是通过加载的上下文,这也就意味着不同方式加载可能存在某些不同。 前言 本文基于《Spring源码深度解析》学习, 《Spring源码深度解析》讲解的Spring版本低于Spring3.1,当前阅读的版本为Spring5.x...

    awokezhou 评论0 收藏0

发表评论

0条评论

yagami

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<