Spring

Spring源码之bean标签的解析与注册

2019-08-23  本文已影响0人  九点半的马拉

前言

在上一篇中,最后讲到了parseDefaultElement方法和parseCustomElement方法,对标签进行解析。先讲一下对bean标签的解析。

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));
            }
    }
  1. 首先委托BeanDefinitionDelegate类的parseBeanDefinitionElement方法进行元素解析,返回BeanDefinitionHolder类型的实例bdHolder,此时,bdHolder就已经包含配置文件中的配置的各种属性了,例如:class、name、id
  2. 当返回的bdHolder不为空时若存在默认标签的子节点下再有自定义属性,还需要对自定义标签进行解析。
  3. 解析完成后,需要对解析后的bdHolder进行注册,同样,注册操作委托给了BeanDefinitionReaderUtils的registerDefinition方法。
  4. 最后发出响应事件,通知相关的监听器,这个bean已经加载完成了。
    下面依次解释每个步骤的含义。

解析BeanDefinition

先解释一个类BeanDefinitionHolder,

public class BeanDefinitionHolder implements BeanMetadataElement {

        private final BeanDefinition beanDefinition;

        private final String beanName;

        @Nullable
        private final String[] aliases;
        public BeanDefinitionHolder(BeanDefinition beanDefinition, String beanName, @Nullable String[] aliases) {
                Assert.notNull(beanDefinition, "BeanDefinition must not be null");
                Assert.notNull(beanName, "Bean name must not be null");
                this.beanDefinition = beanDefinition;
                this.beanName = beanName;
                this.aliases = aliases;
    }

从上面可以看出,BeanDefinition是BeanDefinition的一个持有者,并存储bean的姓名和别名。

@Nullable
    public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
                return parseBeanDefinitionElement(ele, null);
    }

@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
           //解析id属性
            String id = ele.getAttribute(ID_ATTRIBUTE);
         //解析name属性
            String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
         //分割name属性
            List<String> 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.isDebugEnabled()) {
                    logger.debug("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.isDebugEnabled()) {
                            logger.debug("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;
    }

主要步骤:

  1. 提取元素中的id以及name属性
  2. 进一步解析其他所有属性并统一封装至GenericBeanDefinition类型中的实例中。
  3. 如果监测到bean没有指定的beanName,那儿使用默认规则为此Bean生成beanName
  4. 将获取到的信息封装到BeanDefinitionHolder的实例中。

先简单介绍BeanDefinition这个接口。

/**
 * A BeanDefinition describes a bean instance, which has property values,
 * constructor argument values, and further information supplied by
 * concrete implementations.
 *

上面是源码文档解释,从上可以看出,它描述了一个bean实例,有属性值和构造函数参数值,具体的信息由具体的子类来实现。
在配置文件中<bean>元素拥有class、scope、lazy-init等配置属性,BeanDefinition则提供了相应的beanClass、Scope、lazyInit属性,BeanDefinition和<bean>中的元素一一对应。
具体的类RootBeanDefinition、GenericBeanDefinition和ChildBeanDefinition。
其中RootBeanDefinition是最常用的类,在配置文件中可以配置父<bean>和子<bean>,子<bean>用childBeanDefinition表示,没有子类时,直接用RootBeanDefinition表示。

Spring通过BeanDefinition将配置文件中的<bean>配置信息转化为容器的内部表示,并将这些BeanDefinition注册到BeanDefinitionRegistry中。Spring容器中的BeanDefinitionRegistry就像是Spring配置信息的内存数据库,主要是以map的形式保存,后续操作直接从BeanDefinitionRegistry中读取配置信息。

BeanDefinitionParserDelegate

* Stateful delegate class used to parse XML bean definitions.
 * Intended for use by both the main parser and any extension

从上面的定义中可以看出它是代表类,用于处理XML Bean定义的类,干的都是脏活累活。
import/alias/bean等element以及element的子节点以及属性都是它解析并且填充到BeanDefinition中然后使用ReaderContext中的Registry(实际就是DefaultListableBeanFactory)来将该BeanDefinition注册。

Bean的注册

Spring提供了BeanFactory对Bean进行获取,但Bean的注册和管理并不是在BeanFactory中进行,而是在BeanDefinitionRegistry中进行,这里BeanFactory只提供了查阅的功能。
Spring的Bean信息注册保存在一个个BeanDefinition中的。

我们以ClassPathXmlApplicationContext为例

  1. 在它的构造函数中主要的逻辑方法有两个。
    首先调用setConfigLocations()来设置配置文件路径并可以修改配置文件中的属性值。
    然后调用refresh()方法。它是代码的核心,用来对Bean进行注册和初始化。
  2. 在refresh()方法中,主要有下面几个步骤
  1. 对于bean的注册,我们需要关注refreshBeanFactory()方法,
@Override
protected final void refreshBeanFactory() throws BeansException {
    if (hasBeanFactory()) { // 如果BeanFactory已经创建则对其进行销毁
        destroyBeans();
        closeBeanFactory();
    }
    try {
        // 创建BeanFactory实例
        DefaultListableBeanFactory beanFactory = createBeanFactory();
        beanFactory.setSerializationId(getId()); // 为当前BeanFactory设置一个标识id
        customizeBeanFactory(beanFactory); // 设置BeanFacotry的定制化属性信息
        loadBeanDefinitions(beanFactory); // 加载xml文件信息
        synchronized (this.beanFactoryMonitor) {
            this.beanFactory = beanFactory;
        }
    }
    catch (IOException ex) {
        throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
    }
}

从中可以看出中间最重要的方法是loadBeanDefinitions
将加载XML文件中的bean信息交给XmlBeanDefinitionReader来处理。
它会依次读取每个xml配置文件中的bean信息,
将xml文件转化为一个InputStream,再转化为InputSource,进而转化为一个Document对象,该对象保存着各个XML文件中各个节点和子节点的相关信息,然后获取到Document的根节点信息,调用BeanDefinitionDocumentReader.registerBeanDefinitions(root)进行注册。
然后开始解析xml文件,封装成BeanDefinition并完成注册,该点在文章开头已经讲解。

上一篇 下一篇

猜你喜欢

热点阅读