spring容器之注册BeanDefinitions

前面我们完成了xml文件转换成Document对象,我们可以发现的是并没有完成最终的任务,我们是要获取我们定义的bean元素的,来到xmlBeanDefinitionReader#registerBeanDefinitions用来注册我们的beanDefinition的,关于beanDefinition之前提过,这里就不多说了,直接看代码:

  /***
 * 用来注册beandefinition的方法
 * @param doc
 * @param resource
 * @return
 * @throws BeanDefinitionStoreException
 */
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    //通过DefaultBeanDefinitionDocumentReader来构建一个BeanDefinitionDocumentReader对象
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    //获取已经注册的beanDefinition的数量
    int countBefore = getRegistry().getBeanDefinitionCount();
    //1.首先是通过createReaderContext(resource)创建XmlReaderContext实例
    //2.然后调用registerBeanDefinitions方法注册beanDefinition
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    //统计最新注册的beanDefinition的数量
    return getRegistry().getBeanDefinitionCount() - countBefore;
}

上述就是注册beanDefinition的过程,实质上核心不在这里我们在最后调用了BeanDefinitionDocumentReader#registerBeanDefinitions的方法,该方法是注册beanDefinition的核心方法,首先来看一下是如何构建BeanDefinitionDocumentReader实例的过程:

private Class documentReaderClass =
        DefaultBeanDefinitionDocumentReader.class;

protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
    return BeanUtils.instantiateClass(this.documentReaderClass);
}

这就是为什么说是通过DefaultBeanDefinitionDocumentReader来构建的原因,代码简单,不必多说啥了,接着看是获取之前已经注册的beanDefinition的数量:

  • 首先是调用getBeanDefinitionCount()方法统计个数.
  • 其次是调用getRegistry()来表明是已经注册过的.
/** bean定义对象的映射,其中key为bean的name*/
private final Map beanDefinitionMap = new ConcurrentHashMap<>(256);

public int getBeanDefinitionCount() {
    return this.beanDefinitionMap.size();
}

实际上就是缓存map的大小,上述是beanDefinitionRegistry接口的一个实现类DefaultListableBeanFactory#getBeanDefinitionCount,接着看:

private final BeanDefinitionRegistry registry;

public final BeanDefinitionRegistry getRegistry() {
    return this.registry;
}

这就是那两个分步骤,接下来看真正的核心注册方法BeanDefinitionDocumentReader#registerBeanDefinitions(),在该方法中我们看到有两个参数,一个是我们上节通过解析拿到的document对象,一个是通过调用createReaderContext(resource)方法来拿的,来看代码实现;

/**
 * Create the {@link XmlReaderContext} to pass over to the document reader.
 */
public XmlReaderContext createReaderContext(Resource resource) {
    return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
            this.sourceExtractor, this, getNamespaceHandlerResolver());
}

可以看到,上述方法主要是为了获取XmlReaderContext的实例,在方法的实现过程中直接new了一个XmlReaderContext,里面的参数我们简单的了解下:

public class XmlReaderContext extends ReaderContext {

private final XmlBeanDefinitionReader reader;

private final NamespaceHandlerResolver namespaceHandlerResolver;

/**
 *
 * @param resource  定义bean的资源文件
 * @param problemReporter 是一个接口(ProblemReporter),在解析beanDefinition的过程中出现错误用它来处理
 * @param eventListener 是一个接口(ReaderEventListener)在解析beanDefinition的过程中使用的事件监听器
 * @param sourceExtractor 是一个接口(SourceExtractor)提取bean的定义的元数据Docu * @param reader  是一个XmlBeanDefinitionReader 实例
 * @param namespaceHandlerResolver 是一个接口(NamespaceHandlerResolver)主要是处理beanDefinition的命名空间协议
 */
public XmlReaderContext(
        Resource resource, ProblemReporter problemReporter,
        ReaderEventListener eventListener, SourceExtractor sourceExtractor,
        XmlBeanDefinitionReader reader, NamespaceHandlerResolver namespaceHandlerResolver) {

    super(resource, problemReporter, eventListener, sourceExtractor);
    this.reader = reader;
    this.namespaceHandlerResolver = namespaceHandlerResolver;
}

这就是该方法的一些参数,如此一来我们拿到了注册方法的第二个参数,接着看注册过程,跟踪代码来到了BeanDefinitionDocumentReader接口中:

/**
 * Read bean definitions from the given DOM document and
 * register them with the registry in the given reader context.
 * @param doc the DOM document
 * @param readerContext the current context of the reader
 * (includes the target registry and the resource being parsed)
 * @throws BeanDefinitionStoreException in case of parsing errors
 */
void registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
        throws BeanDefinitionStoreException;

}

接着来看它的直接实现了类:

public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {


private XmlReaderContext readerContext;

@Nullable
private BeanDefinitionParserDelegate delegate;

/**
 * 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()); }

代码跟踪我们来到DefaultBeanDefinitionDocumentReader#registerBeanDefinitions()方法中,可以看到的是这里将注册的逻辑交给了doRegisterBeanDefinitions()方法,在看该方法前我们先来看它所需要的参数即doc.getDocumentElement():

 /**
 * This is a convenience attribute that allows direct access to the child
 * node that is the document element of the document.
 */
public Element getDocumentElement();

该方法是在Document接口中的一个方法其主要的目的是获取当前doc的根元素,有了这个参数就可以执行真正的注册操作了doRegisterBeanDefinitions(ele):

protected void doRegisterBeanDefinitions(Element root) {
    // Any nested  elements will cause recursion in this method. In
    // order to propagate and preserve  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.
    //将当前解析beanDefinition委托给父类BeanDefinitionParserDelegate,实质上为了下面实例化BeanDefinitionParserDelegate对象做准备
    BeanDefinitionParserDelegate parent = this.delegate;
    //通过createDelegate()实例化一个BeanDefinitionParserDelegate并设置给当前委托类delegate
    this.delegate = createDelegate(getReaderContext(), root, parent);
    //1首先获取到命名空间的URL:http://www.springframework.org/schema/beans
    //2.然后进行简单判null还有是否是默认的指定的URL:http://www.springframework.org/schema/beans
    if (this.delegate.isDefaultNamespace(root)) {
        //针对于profile属性的处理过程
        String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
        if (StringUtils.hasText(profileSpec)) {
            //通过;来切割,这里考虑到了我们的xml中有多个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.
            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);
    //核心方法,解析beanDefinition
    parseBeanDefinitions(root, this.delegate);
    //解析后置处理,也是由子类实现
    postProcessXml(root);
    //将之前委托给父类BeanDefinitionParserDelegate的权利收回
    this.delegate = parent;
}

上述是注册beanDefinition的过程,到最后我们发现有这样一个 parseBeanDefinitions()去解析beanDefinition,在整个方法中显示对profile属性做处理的,关于profile属性等下提一下,简单的说一下在上述代码中流程:

  • 首先委托解析beanDefinition给BeanDefinitionParserDelegate类,这里我们用到了如下的两个属性:
public class BeanDefinitionParserDelegate {

public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans";

public static final String MULTI_VALUE_ATTRIBUTE_DELIMITERS = ",; ";

该类中有很多属性的定义,感兴趣的可以自己去看看喽,这里不扯了,接着分析

  • 通过createDelegate()去创建BeanDefinitionParserDelegate实例并赋值给当前的环境中的委托类delegate
protected BeanDefinitionParserDelegate createDelegate(
        XmlReaderContext readerContext, Element root, @Nullable BeanDefinitionParserDelegate parentDelegate) {

    BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);
    delegate.initDefaults(root, parentDelegate);
    return delegate;
}

接着看判断是否是默认的指定的URL命名空间:

//获取命名空间的URL: http://www.springframework.org/schema/beans
@Nullable
public String getNamespaceURI(Node node) {
    return node.getNamespaceURI();
}

//判null,还有是否是这个http://www.springframework.org/schema/beans
public boolean isDefaultNamespace(@Nullable String namespaceUri) {
    return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));
}

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

判断完之后接下来主要是针对于profile属性的一些处理

  • 首先通过root.getAttribute(PROFILE_ATTRIBUTE)获取到属性
  public static final String PROFILE_ATTRIBUTE = "profile";
  • 然后是一些简单的判null校验,进行分割操作
  • 以上简单的校验完成之后,首先是进行解析xml的前后处理:
protected void preProcessXml(Element root) {
}
protected void postProcessXml(Element root) {
}

我们看到是空实现,这里是留给程序员自由发挥的空间

  • 最后看真正的解析方法parseBeanDefinitions

在这之前我们遗留了一个问题profile属性的使用,该属性是spring3提出的,直接看代码:













  

  


上述配置了不同的三种环境,通过不同场景可以切换环境,实际上没必要这么麻烦因为我们可以通过注解的方法来切换,这里只是让大家看看profile的属型在xml中的配置,接下来我们来看真正的解析方法

/**
 * 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;
                //如果bean是默认的命名空间,采用默认的解析过程
                if (delegate.isDefaultNamespace(ele)) {
                    parseDefaultElement(ele, delegate);
                }
                //采用自定义的解析过程
                else {
                    delegate.parseCustomElement(ele);
                }
            }
        }
    }
    //解析自定义的根元素的过程
    else {
        delegate.parseCustomElement(root);
    }
}

上述代码无非就是对两类xml的解析过程,一类是默认的如:,对于默认的spring采用的是parseDefaultElement去做相关的解析工作,还有一类是自定义的,如:spring 通过parseCustomElement()去做相应的解析工作.关于两者是如何解析的过程将在后续的文章中进行讨论.......

你可能感兴趣的:(spring容器之注册BeanDefinitions)