• 【Spring源码分析系列】加载Bean


     1 /**
     2      * Create a new XmlBeanFactory with the given input stream,
     3      * which must be parsable using DOM.
     4      * @param resource XML resource to load bean definitions from
     5      * @param parentBeanFactory parent bean factory
     6      * @throws BeansException in case of loading or parsing errors
     7      */
     8     public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
     9         super(parentBeanFactory);
    10         this.reader.loadBeanDefinitions(resource);
    11     }

    整个处理过程:

    1)封装资源文件

    当进入XmlBeanDefinitionReader后首先对参数Resource使用EncodeResource类进行封装

    2)获取输入流

    从Resource中获取对应的InputSteam并构造InputSource

    3)通过构造的InputSource实例和Resource实例继续调用doLoadBeanDefinitions.

    loadBeanDefinition的实现:

     1 /**
     2      * Load bean definitions from the specified XML file.
     3      * @param resource the resource descriptor for the XML file
     4      * @return the number of bean definitions found
     5      * @throws BeanDefinitionStoreException in case of loading or parsing errors
     6      */
     7     @Override
     8     public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
     9         return loadBeanDefinitions(new EncodedResource(resource));
    10     }

    new EncodeResource(resource)实现:

    1 private EncodedResource(Resource resource, String encoding, Charset charset) {
    2         super();
    3         Assert.notNull(resource, "Resource must not be null");
    4         this.resource = resource;
    5         this.encoding = encoding;
    6         this.charset = charset;
    7     }
     1 /**
     2      * Open a {@code java.io.Reader} for the specified resource, using the specified
     3      * {@link #getCharset() Charset} or {@linkplain #getEncoding() encoding}
     4      * (if any).
     5      * @throws IOException if opening the Reader failed
     6      * @see #requiresReader()
     7      * @see #getInputStream()
     8      */
     9     public Reader getReader() throws IOException {
    10         if (this.charset != null) {
    11             return new InputStreamReader(this.resource.getInputStream(), this.charset);
    12         }
    13         else if (this.encoding != null) {
    14             return new InputStreamReader(this.resource.getInputStream(), this.encoding);
    15         }
    16         else {
    17             return new InputStreamReader(this.resource.getInputStream());
    18         }
    19     }

    真正的数据准备阶段:

    /**
         * Load bean definitions from the specified XML file.
         * @param encodedResource the resource descriptor for the XML file,
         * allowing to specify an encoding to use for parsing the file
         * @return the number of bean definitions found
         * @throws BeanDefinitionStoreException in case of loading or parsing errors
         */
        public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
            Assert.notNull(encodedResource, "EncodedResource must not be null");
            if (logger.isInfoEnabled()) {
                logger.info("Loading XML bean definitions from " + encodedResource.getResource());
            }
            //通过属性值来记录已经加载的资源
            Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
            if (currentResources == null) {
                currentResources = new HashSet<EncodedResource>(4);
                this.resourcesCurrentlyBeingLoaded.set(currentResources);
            }
            if (!currentResources.add(encodedResource)) {
                throw new BeanDefinitionStoreException(
                        "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
            }
            try {
    //从encodeResource中获取已经封装的Resource对象并再次从Resource中获取其中的inputStream InputStream inputStream
    = encodedResource.getResource().getInputStream(); try {
    //InputResource这个类不来自与Spring 全类路径是org.xml.sax.InputSource InputSource inputSource
    = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); }
    //真正的逻辑核心部分
    return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } finally { inputStream.close(); } } catch (IOException ex) { throw new BeanDefinitionStoreException( "IOException parsing XML document from " + encodedResource.getResource(), ex); } finally { currentResources.remove(encodedResource); if (currentResources.isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); } } }

    首先对传入的resource参数做封装,目的是考虑到Resource可能存在的编码要求的情况,其次,通过SAX读取XML文件的方式来准备InputSource对象,最后将准备的数据通过参数传入真正的核心处理部分doLoadBeanDefinitions(inputSource,encodeResource.getResource()).

     1 /**
     2      * Actually load bean definitions from the specified XML file.
     3      * @param inputSource the SAX InputSource to read from
     4      * @param resource the resource descriptor for the XML file
     5      * @return the number of bean definitions found
     6      * @throws BeanDefinitionStoreException in case of loading or parsing errors
     7      * @see #doLoadDocument
     8      * @see #registerBeanDefinitions
     9      */
    10     protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
    11             throws BeanDefinitionStoreException {
    12         try {
    //加载XML文件,并得到对应的Document
    13 Document doc = doLoadDocument(inputSource, resource);
    //根据返回的Document注册Bean信息
    14 return registerBeanDefinitions(doc, resource); 15 } 16 catch (BeanDefinitionStoreException ex) { 17 throw ex; 18 } 19 catch (SAXParseException ex) { 20 throw new XmlBeanDefinitionStoreException(resource.getDescription(), 21 "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex); 22 } 23 catch (SAXException ex) { 24 throw new XmlBeanDefinitionStoreException(resource.getDescription(), 25 "XML document from " + resource + " is invalid", ex); 26 } 27 catch (ParserConfigurationException ex) { 28 throw new BeanDefinitionStoreException(resource.getDescription(), 29 "Parser configuration exception parsing XML from " + resource, ex); 30 } 31 catch (IOException ex) { 32 throw new BeanDefinitionStoreException(resource.getDescription(), 33 "IOException parsing XML document from " + resource, ex); 34 } 35 catch (Throwable ex) { 36 throw new BeanDefinitionStoreException(resource.getDescription(), 37 "Unexpected exception parsing XML document from " + resource, ex); 38 } 39 }

    获取XML的验证模式

    DTD和XSD区别

    DTD即文档类型定义,是一种XML约束模式语言,是XML文件的验证机制,属于XML文件组成的一部分。DTD是一种保证XML文档格式正确的有效方法,可以通过比较XML和DTD文件来看文档是否符合规范,元素和标签使用是否正确。一个DTD文档包含:元素的定义规则,元素间关系的定义规则,元素可使用的属性,可使用的实体或符号规则。

    要使用DTD验证模式的时候需要在XML文件的头部声明,以下是在Spring中使用DTD声明方式的代码:

    1 <?xml version="1.0" encoding="UTF-8"?>
    2 <!DOCTYPE beans PUBLIC "-//Spring//DTD BEAN 2.0//EN" "http://www.Springframework.org/dtd/Spring-beans-2.0.dtd">

    获取Document

     1 /**
     2      * Load the {@link Document} at the supplied {@link InputSource} using the standard JAXP-configured
     3      * XML parser.
     4      */
     5     @Override
     6     public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
     7             ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
     8 
     9         DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
    10         if (logger.isDebugEnabled()) {
    11             logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
    12         }
    13         DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
    14         return builder.parse(inputSource);
    15     }

    首先创建DocumentBuilderFactory,再通过DocumentBuilderFactory创建DocumentBuilder,进而解析inputSource来返回Document对象。

    在上述方法中涉及一个参数EntityResolver何为EntityResolver?如果SAX应用程序需要实现自定义处理外部实体,则必须实现此接口并使用setEntityResolver方法向SAX驱动器注册一个实例,也就是说,对于解析一个XML,SAX首先读取该XML文档的声明,根据声明去寻找相应的DTD定义,以便对文档进行一个严重。默认的寻找规则,即通过网络来下载相应的DTD声明,并进行认证。

    EntityResolver的作用是项目本身就可以提供一个如何寻找DTD声明的方法,即由程序来实现寻找DTD声明的过程,比如我们将DTD文件放到项目中某处,在实现时直接将此文档读取并返回给SAX即可。

    解析及注册BeanDefinitions

    当把文件转换为Document后,接下来的提取及注册Bean就是我们的重头戏,当程序已经拥有XML文档文件的Document实例对象时,就会被引入如下的方法:

     1 /**
     2      * Register the bean definitions contained in the given DOM document.
     3      * Called by {@code loadBeanDefinitions}.
     4      * <p>Creates a new instance of the parser class and invokes
     5      * {@code registerBeanDefinitions} on it.
     6      * @param doc the DOM document
     7      * @param resource the resource descriptor (for context information)
     8      * @return the number of bean definitions found
     9      * @throws BeanDefinitionStoreException in case of parsing errors
    10      * @see #loadBeanDefinitions
    11      * @see #setDocumentReaderClass
    12      * @see BeanDefinitionDocumentReader#registerBeanDefinitions
    13      */
    14     public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    15        //使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    //记录统计前BeanDefinition的加载个数
    16 int countBefore = getRegistry().getBeanDefinitionCount();
    //加载及注册bean
    17 documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    //记录本次加载的BeanDefinition个数
    18 return getRegistry().getBeanDefinitionCount() - countBefore; 19 }
     1 /**
     2      * Register each bean definition within the given root {@code <beans/>} element.
     3      */
     4     protected void doRegisterBeanDefinitions(Element root) {
     5         // Any nested <beans> elements will cause recursion in this method. In
     6         // order to propagate and preserve <beans> default-* attributes correctly,
     7         // keep track of the current (parent) delegate, which may be null. Create
     8         // the new (child) delegate with a reference to the parent for fallback purposes,
     9         // then ultimately reset this.delegate back to its original (parent) reference.
    10         // this behavior emulates a stack of delegates without actually necessitating one.
    11         BeanDefinitionParserDelegate parent = this.delegate;
    12         this.delegate = createDelegate(getReaderContext(), root, parent);
    13 
    14         if (this.delegate.isDefaultNamespace(root)) {
    //处理profile属性
    15 String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); 16 if (StringUtils.hasText(profileSpec)) { 17 String[] specifiedProfiles = StringUtils.tokenizeToStringArray( 18 profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); 19 if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { 20 if (logger.isInfoEnabled()) { 21 logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec + 22 "] not matching: " + getReaderContext().getResource()); 23 } 24 return; 25 } 26 } 27 } 28 //解析前处理,留给子类实现 29 preProcessXml(root); 30 parseBeanDefinitions(root, this.delegate);
    //解析后处理,留给子类实现
    31 postProcessXml(root); 32 33 this.delegate = parent; 34 }

    preProcessXml和postProcessXml两个方法是空的,这是模板方法模式,在面向对象设计方法中常说:一个雷要么面向集成的设计,要么就用final修饰,这两个方法正是为子类设计的,如果继承自DefaultBeanDefinitionDocumentReader的子类需要在Bean解析前后做一些处理的话,那么只需要重写这两个方法就可以了。

    解析并注册BeanDefinition:

     1 /**
     2      * Parse the elements at the root level in the document:
     3      * "import", "alias", "bean".
     4      * @param root the DOM root element of the document
     5      */
     6     protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    //对beans的处理
    7 if (delegate.isDefaultNamespace(root)) { 8 NodeList nl = root.getChildNodes(); 9 for (int i = 0; i < nl.getLength(); i++) { 10 Node node = nl.item(i); 11 if (node instanceof Element) { 12 Element ele = (Element) node; 13 if (delegate.isDefaultNamespace(ele)) {
    //对bean的处理
    14 parseDefaultElement(ele, delegate); 15 } 16 else {
    //对bean的处理
    17 delegate.parseCustomElement(ele); 18 } 19 } 20 } 21 } 22 else { 23 delegate.parseCustomElement(root); 24 } 25 }

    在Spring的XML配置里面有两大类Bean声明,一个是默认的,如:

    1 <bean id="test" class="com.slp.Test"/>

    另一类就是自定义的,如:

    1 <tx:annotation-driven/>

    这两种方式的读取及解析差别是非常大的,如果采用Spring默认的配置,Spring当然知道该如何做,但是如果是自定义的,那么久需要用户实现一些接口及配置了。对于根节点或者子节点如果是默认命名空间的话则采用parseDefaultElement方法进行解析,否则使用delegate.parseCutomElement方法对自定义命名空间进行解析。而判断是否默认命名空间还是自定义命名空间的方法其实是使用node.getNamespaceURI()获取命名空间,并与Spring的命名空间进行对比,如果一致则是默认,否则是自定义。

  • 相关阅读:
    vue element 关于表单数组循环、对象数据校验
    vue 解耦双向数据绑定,new Set() 去重数组/对象
    Vue-cli3.0搭建和一些坑
    微信小程序入门学习小结--全局设置、数据请求、双向绑定、页面路由跳转、模板抽取
    Vuex简单入门实例
    关于wamp的HTML, PHP, mysql 三者的操作与联系
    关于wamp的HTML, PHP, mysql 三者的操作与联系
    浏览器知识小整理
    HTML DOM textContent 与 innerHTML的区别
    T-SQL查询进阶--SQL Server中的事务与锁
  • 原文地址:https://www.cnblogs.com/dream-to-pku/p/7591692.html
Copyright © 2020-2023  润新知