Spring IOC BeanDefinition解析

Posted 零秒思考

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring IOC BeanDefinition解析相关的知识,希望对你有一定的参考价值。

Spring IOC BeanDefinition解析

IOC(Inversion of Control)即控制反转,是说创建对象的控制权进行了转移,以前创建对象的主动权和创建时机是由自己把控的,而现在这种权利转移到Spring IOC容器。许多非凡的应用,都是由两个或者多个类通过彼此的合作依赖来实现业务逻辑的,在Spring中,这些依赖关系可以通过把对象的依赖注入交给IOC容器来管理,这样在解耦代码的同时提高了代码的可测试性。

1.    加载bean

加载bean的流程:

(1)     封装资源文件。当进入XmlBeanDefinitionReader后首先对参数Resource使用EncodedResource类进行封装。

(2)     获取输入流。从Resource中获取对应的InputStream并构造InputSource。

(3)     通过构造的InputSource实例和Resource实例继续调用函数doLoadBeanDefinitions。

我们来看一下doLoadBeanDefinitions函数的具体的实现过程(中间省略了loadBeanDefinitions具体方法的一步):

    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        return loadBeanDefinitions(new EncodedResource(resource));
}

继续跟进代码,进入真正的核心处理部分doLoadBeanDefinitions(inputSource, encodedResource.getResource())

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
           throws BeanDefinitionStoreException {
        try {
           Document doc = doLoadDocument(inputSource, resource);
           return registerBeanDefinitions(doc, resource);
        }
        catch (BeanDefinitionStoreException ex) {
           throw ex;
        }
        // ……省略异常处理部分
    }

 

在上面冗长的代码中,假如不考虑异常类的代码,其实只做了三件事,这三件事的每一件事都必不可少。

(1)     获取对XML文件的验证模式。

(2)     加载XML文件,并得到对应的Document。

(3)     根据返回的Document注册bean信息。

2.    获取XML的验证模式

  protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
          return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
                        getValidationModeForResource(resource), isNamespaceAware());
   }

 2.1验证模式的读取

了解XML文件的读者都应该知道XML文件的验证模式保证了XML文件的正确性,而比较常用的验证模式有两种:DTD和XSD。

对于验证模式,读者可以自行查阅数据了解。

验证模式的读取方法如下:

  protected int getValidationModeForResource(Resource resource) {
        int validationModeToUse = getValidationMode();
        if (validationModeToUse != VALIDATION_AUTO) {
           return validationModeToUse;
        }
        int detectedMode = detectValidationMode(resource);
        if (detectedMode != VALIDATION_AUTO) {
           return detectedMode;
        }
        // Hmm, we didn‘t get a clear indication... Let‘s assume XSD,
        // since apparently no DTD declaration has been found up until
        // detection stopped (before finding the document‘s root tag).
        return VALIDATION_XSD;
    }

 方法的实现其实还是很简单的,无非是如果设定了验证模式则使用设定的验证模式,否则使用自动的验证模式,自检测验证模式的功能相对来说比较简单,这里就不再多说了。

3.    获取Document

经过了验证模式准备的步骤就可以进行Document加载了,同样XmlBeanDefinitionReader对于文档的读取并没有亲力亲为,而是委托给了DocumentLoader去执行,解析代码如下(DefaultDocumentLoader中)

   public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
           ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
 
        DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
        if (logger.isDebugEnabled()) {
           logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
        }
        DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
        return builder.parse(inputSource);
    }

 对于这部分代码其实并没有太多可以描述的,因为通过SAX解析XML文档的套路大致都差不多,Spring在这里并没有什么特殊的地方,同样首先创建DocumentBuilderFactory再通过DocumentBuilderFactory创建DocumentBuilder,进而解析inputSource来返回Document对象。

4.    解析及注册BeanDefinitions

  把文件转换成Document之后,接下来就可以提取及注册bean了。

  public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        //使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
// 在实例化BeanDefinitionReader时候会将BeanDefinitionRegistry传入,默认使用继承自DefaultListableBeanFactory的子类
        // 记录统计前BeanDefinition的加载个数
        int countBefore = getRegistry().getBeanDefinitionCount();
        // 加载及注册bean
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        // 记录本次加载的BeanDefinition个数
        return getRegistry().getBeanDefinitionCount() - countBefore;
    }

   进入DefaultBeanDefinitionDocumentReader的registerBeanDefinitions方法

   public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
        this.readerContext = readerContext;
        logger.debug("Loading bean definitions");
        Element root = doc.getDocumentElement();
        doRegisterBeanDefinitions(root);
    }

 下面进入核心逻辑的底部doRegisterBeanDefinitions(root)方法

  protected void doRegisterBeanDefinitions(Element root) {
        // 专门处理解析
        BeanDefinitionParserDelegate parent = this.delegate;
        this.delegate = createDelegate(getReaderContext(), root, parent);
 
        if (this.delegate.isDefaultNamespace(root)) {
            // 处理profile属性
           String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
           if (StringUtils.hasText(profileSpec)) {
               String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                       profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
               if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                   if (logger.isInfoEnabled()) {
                       logger.info("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;
    }

 

4.1profile属性的使用

这个特性可以同时在配置文件中部署两套配置来适用于生产环境和开发环境,这样可以方便的进行切换开发、部署环境,最常用的是更换不同的数据库。

4.2解析并注册BeanDefinitions

处理了profile后就可以进行XML的读取了,跟踪代码进入parseBeanDefinitions(root, this.delegate)

   protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
         // 对beans的处理
        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)) {
                         // 对bean的处理
                       parseDefaultElement(ele, delegate);
                   }
                   else {
                         // 对bean的处理
                       delegate.parseCustomElement(ele);
                   }
               }
           }
        }
        else {
           delegate.parseCustomElement(root);
        }
    }

 

上面的代码看起来逻辑还是挺清晰的,因为在spring的xml配置里面有两大类bean申明,一个是默认的,一个是自定义的,两种方式的读取及解析差别还是非常大的,如果采用Spring默认的配置,spring当然知道该怎么做,但是如果是自定义的,那么久需要用户实现一些接口及配置了。

对于标签解析,请看我下一篇文章。

以上是关于Spring IOC BeanDefinition解析的主要内容,如果未能解决你的问题,请参考以下文章

[死磕 Spring 38/43] --- IOC 之 BeanDefinition 注册机:BeanDefinitionRegistry

[死磕 Spring 38/43] --- IOC 之 BeanDefinition 注册机:BeanDefinitionRegistry

深入分析Spring之IOC之加载BeanDefinition案例详解

Spring-IOC源码解读2.3-BeanDefinition的注册

[死磕 Spring 9/43] --- IOC 之解析 bean 标签:BeanDefinition

[死磕 Spring 5/43] --- IOC 之 注册 BeanDefinition