zoukankan      html  css  js  c++  java
  • spring源码学习之容器的基本实现

      最近想拿出一部分时间来学习一下spring的源码,还特意买了一本书结合来看,当然主要是学习并跟着作者的思路来踏上学习spring的源码的道路,特意在此记录一下,《spring源码深度解析》

    一、spring的结构组成

      从简单的例子入手,从实际的开发中去解析、学习源码,结合工作中的内容,这样才能更好的、更加深入的学习,go go go!!!

    1、容器的基本用法与功能分析

    容器基本用法,代码如下:

     1 public class MyTestBean {
     2     private String testStr = "testStr" ;
     3 
     4     public String getTestStr() {
     5         return testStr;
     6     }
     7 
     8     public void setTestStr(String testStr) {
     9         this.testStr = testStr;
    10     }
    11 }
    12 
    13 -----------------------------------------------------------------------------
    14 
    15 <bean id="myTestBean" class="com.ssc.springStudy.beans.MyTestBean"></bean>
    16 
    17 -------------------------------------------------------------------------
    18 
    19 @Test
    20 public void testSimpleLoad(){
    21     BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("beanFactory.xml"));
    22     MyTestBean bean = (MyTestBean) beanFactory.getBean("myTestBean");
    23     System.out.println(bean.getTestStr());
    24 }

    功能解析:

    (1)读取配置文件beanFactoryTest.xml

    (2)根据beanFactoryTest.xml中的配置找到对应的类的配置,并实例化

    (3)调用实例化后的实例

    如果完成以上的功能,至少需要3个类:

    ConfigReader:用于读取及验证配置文件,我们要用配置文件里面的东西,当然首先就是读取,然后放置到内存中

    ReflectionUtil:用于根据配置文件中的配置进行反射实例化。

    App:用于完成整个逻辑的串联

    二、spring的结构组成

    1、beans包的层级结构以及核心类

    (1)DefaultListableBeanFactory

    XmlBeanFactory继承自DefaultListableBeanFactory,而DefaultListableBeanFactory是整个bean加载的核心部分,是spring注册及加载bean的默认实现方式,而对XmlBeanFactory与DefaultListableBeanFactory不同的地方其实是在XmlBeanFactory中使用了自定义的XML读取器XmlBeanDefinitionReader,实现了个性化的BeanDefinitionReader读取,DefaultListableBeanFactory继承 了AbstractAutowireCapableBeanFactory并实现了ConfigurableListableBeanFactory以及BeanDefinitionRegistry接口。

    全局角度了解DefaultListableBeanFactory:

    A:AliasRegistry:定义对alias的简单的增删改查等操作

    B:SimpleAliasRegistry:主要使用map作为alias的缓存,并对接口AlisaRegistry进行实现

    C:SingletonBeanRegistry:定义对单例的注册和获取

    D:BeanFactroy:定义获取bean以及bean的各种属性

    E:DefaultSingletonBeanRegistry:对接口SingletonBeanRegistry各函数的实现

    F:HierarchicalBeanFactory:继承BeanFactory,也就是在BeanFactory定义的功能的基础上增加了对ParentFactory的支持

    G:BeanDefinitionRegistry:定义对BeanDefinition的各种增删改查操作

    H:FactoryBeanRegistrySupport:在DefaultSingletonBeanRegistry基础上增加了对FactoryBean的特殊处理功能

    I:ConfigurableBeanFactory:提供配置Factory的各种方法

    J:ListableBeanFactory:根据各种条件获取bean的配置清单

    K:AbstractBeanFactory:综合FactoryBeanRegistrySupport和ConfigurableBeanFactory的功能

    L:AutowireCapableBeanFactory:提供创建bean、自动注入、初始化以及应用bean的后处理器

    M:AbstractAutowireCapableBeanFactory:综合AbstractBeanFactory并对接口AutoWireCapableBeanFactory进行实现

    N:ConfigurableListableBeanFactory:BeanFactory的配置清单,指定忽略类型及接口等

    O:DefaultListableBeanFactory:综合上面所有的功能。主要是对bean注册后的处理

    注意:

    XmlBeanFactory对DefaultListableBeanFactory类进行了扩展,主要用于从xml文档中读取BeanDefinition,对于注册及获取bean都是使用从父类DefaultListableBeanFactory继承的方法去实现,而唯独与父类不同的个性化实现就是增加了XmlBeanDefinitionReader类型的reader属性。在XmlBeanFactory中主要使用reader属性对资源文件进行读取和注册。

    (2)XmlBeanDefinitionReader

    XML配置文件的读取是spring中重要的功能,因为spring的大部分功能是以配置作为切入点的,那么我们可以从XmlBeanDefinitionReader中梳理一下资源文件读取、解析及注册的大致脉络,首先看看各个类的功能。

    A:ResourceLoader:定义资源加载器,主要应用于根据给定的资源文件地址返回对应的Resource

    B:BeanDefinitionReader:主要定义资源文件读取并装换为BeanDefinition的各个功能

    C:EnvironmentCapable:定义获取Environment方法

    D:DocumentLoader:定义从资源文件加载到转换为Document的功能

    E:AbstractBeanDefinitionReader:对EnvironmentCapable、BeanDefinitionReader类定义的功能进行实现

    F:BeanDefinitionDocumentReader:定义读取Document并注册BeanDefinition功能

    G:BeanDefinitionParserDelegate:定义解析Element的各种方法

    注意:

    在XmlBeanDefinitionReader中主要包含以下几步的处理:

    A:通过继承自AbstractBeanDefinitionReader中的方法,来使用ReaderLoader将资源文件路径转换为对应的Resource文件

    B:通过DocumentLoader对Resource文件进行装换,将Resource文件转换为Document文件

    C:通过实现接口BeanDefinitionDocumentReader的DefaultBeanDefinitionDocumentReader类对Document进行解析,并使用BeanDefinitionParserDelegate对Element进行解析

    2、容器的基础XmlBeanFactory

    这部分从下面的代码开始深入研究:

    1 BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("beanFactory.xml"));

    (1)配置文件封装(xml文件如何封装?)

    spring对其内部使用到的资源文件实现了自己的抽象结构:Resource接口封装底层资源。

      1 package org.springframework.core.io;
      2 
      3 import java.io.IOException;
      4 import java.io.InputStream;
      5 
      6 public interface InputStreamSource {
      7 
      8     InputStream getInputStream() throws IOException;
      9 
     10 }
     11 -------------------------------------------------------------------------------------------------------------------------------------
     12 package org.springframework.core.io;
     13 
     14 import java.io.File;
     15 import java.io.IOException;
     16 import java.net.URI;
     17 import java.net.URL;
     18 
     19 public interface Resource extends InputStreamSource {
     20 
     21     /**
     22      * Return whether this resource actually exists in physical form.
     23      * <p>This method performs a definitive existence check, whereas the
     24      * existence of a {@code Resource} handle only guarantees a
     25      * valid descriptor handle.
     26      */
     27     boolean exists();
     28 
     29     /**
     30      * Return whether the contents of this resource can be read,
     31      * e.g. via {@link #getInputStream()} or {@link #getFile()}.
     32      * <p>Will be {@code true} for typical resource descriptors;
     33      * note that actual content reading may still fail when attempted.
     34      * However, a value of {@code false} is a definitive indication
     35      * that the resource content cannot be read.
     36      * @see #getInputStream()
     37      */
     38     boolean isReadable();
     39 
     40     /**
     41      * Return whether this resource represents a handle with an open
     42      * stream. If true, the InputStream cannot be read multiple times,
     43      * and must be read and closed to avoid resource leaks.
     44      * <p>Will be {@code false} for typical resource descriptors.
     45      */
     46     boolean isOpen();
     47 
     48     /**
     49      * Return a URL handle for this resource.
     50      * @throws IOException if the resource cannot be resolved as URL,
     51      * i.e. if the resource is not available as descriptor
     52      */
     53     URL getURL() throws IOException;
     54 
     55     /**
     56      * Return a URI handle for this resource.
     57      * @throws IOException if the resource cannot be resolved as URI,
     58      * i.e. if the resource is not available as descriptor
     59      */
     60     URI getURI() throws IOException;
     61 
     62     /**
     63      * Return a File handle for this resource.
     64      * @throws IOException if the resource cannot be resolved as absolute
     65      * file path, i.e. if the resource is not available in a file system
     66      */
     67     File getFile() throws IOException;
     68 
     69     /**
     70      * Determine the content length for this resource.
     71      * @throws IOException if the resource cannot be resolved
     72      * (in the file system or as some other known physical resource type)
     73      */
     74     long contentLength() throws IOException;
     75 
     76     /**
     77      * Determine the last-modified timestamp for this resource.
     78      * @throws IOException if the resource cannot be resolved
     79      * (in the file system or as some other known physical resource type)
     80      */
     81     long lastModified() throws IOException;
     82 
     83     /**
     84      * Create a resource relative to this resource.
     85      * @param relativePath the relative path (relative to this resource)
     86      * @return the resource handle for the relative resource
     87      * @throws IOException if the relative resource cannot be determined
     88      */
     89     Resource createRelative(String relativePath) throws IOException;
     90 
     91     /**
     92      * Determine a filename for this resource, i.e. typically the last
     93      * part of the path: for example, "myfile.txt".
     94      * <p>Returns {@code null} if this type of resource does not
     95      * have a filename.
     96      */
     97     String getFilename();
     98 
     99     /**
    100      * Return a description for this resource,
    101      * to be used for error output when working with the resource.
    102      * <p>Implementations are also encouraged to return this value
    103      * from their {@code toString} method.
    104      * @see Object#toString()
    105      */
    106     String getDescription();
    107 
    108 }

    解析:

      InputStreamSource封装了任何能返回InputStream的类,比如File、ClassPath下的资源和ByteArray等。它只有一个方法定义:getInputStream()。该方法返回一个新的InputStream对象。

      Resource接口抽象了所有spring内部使用到的底层资源:File、URL、ClassPath等。首先,它定义了3个判断当前资源状态的方法:存在性(exists)、可读性(isReadable)、是否处于打开状态(isOpen)。另外、Resource接口还提供了不同资源URL、URI、File类型的装换,以及获取lastModified属性、文件名(不带路径信息的文件名,getFilename())的方法。为了便于操作,Resource还提供了基于当前资源创建一个相对资源的方法:createRelative()。在错误处理中需要详细地打印出错的资源文件。因而Resource还提供了getDescription()方法用来在错误处理中打印信息。

    (2)加载bean

    之前提到的再XmlBeanFactory构造函数中调用了XmlBeanDefinitionReader类型的reader属性提供的方法this.reader.loadBeanDefinitions(resource),而这句代码则是整个资源加载的切入点。

    加载XML文件和解析注册Bean整个处理过程:

    A:封装资源文件。当进入XmlBeanDefinitionReader后首先对参数Resource使用EncodeResource类进行封装。

    B:获取输入流。从Resource中获取对应的InputStream并构造InputSource。

    C:通过构造的InputSource实例和Resource实例继续调用doLoadBeanDefinitions

    loadBeanDefinitions函数具体的实现过程:

     1   package org.springframework.beans.factory.xml;
     2     /**
     3      * Load bean definitions from the specified XML file.
     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      */
     8     @Override
     9     public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    10         return loadBeanDefinitions(new EncodedResource(resource));
    11     }

    这个类用于对资源文件的编码进行处理。其中主要逻辑体现在getReader()方法中,当设置了编码属性的时候Spring会使用相应的编码作为输入流的编码

    注意在EncodedResource类中:

      package org.springframework.core.io.support;
    
        /**
         * Open a {@code java.io.Reader} for the specified resource, using the specified
         * {@link #getCharset() Charset} or {@linkplain #getEncoding() encoding}
         * (if any).
         * @throws IOException if opening the Reader failed
         * @see #requiresReader()
         * @see #getInputStream()
         */
        public Reader getReader() throws IOException {
            if (this.charset != null) {
                return new InputStreamReader(this.resource.getInputStream(), this.charset);
            }
            else if (this.encoding != null) {
                return new InputStreamReader(this.resource.getInputStream(), this.encoding);
            }
            else {
                return new InputStreamReader(this.resource.getInputStream());
            }
        }

    上面代码构造了一个有编码的InputStreamReader。当构造好encodedResource对象后,再次转入可复用方法loadBeanDefinitions(new EncodeResource(resource))。

    这个方法内部才是真正的数据准备阶段:

     1   package org.springframework.beans.factory.xml;
     2 
     3     /**
     4      * Load bean definitions from the specified XML file.
     5      * @param encodedResource the resource descriptor for the XML file,
     6      * allowing to specify an encoding to use for parsing the file
     7      * @return the number of bean definitions found
     8      * @throws BeanDefinitionStoreException in case of loading or parsing errors
     9      */
    10     public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
    11         Assert.notNull(encodedResource, "EncodedResource must not be null");
    12         if (logger.isTraceEnabled()) {
    13             logger.trace("Loading XML bean definitions from " + encodedResource);
    14         }
    15         // 通过属性来记录已经加载的资源    
    16         Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
    17         if (currentResources == null) {
    18             currentResources = new HashSet<>(4);
    19             this.resourcesCurrentlyBeingLoaded.set(currentResources);
    20         }
    21         if (!currentResources.add(encodedResource)) {
    22             throw new BeanDefinitionStoreException(
    23                     "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
    24         }
    25         try {
    26             InputStream inputStream = encodedResource.getResource().getInputStream();
    27             try {
    28                 InputSource inputSource = new InputSource(inputStream);
    29                 if (encodedResource.getEncoding() != null) {
    30                     inputSource.setEncoding(encodedResource.getEncoding());
    31                 }
    32                 return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
    33             }
    34             finally {
    35                 inputStream.close();
    36             }
    37         }
    38         catch (IOException ex) {
    39             throw new BeanDefinitionStoreException(
    40                     "IOException parsing XML document from " + encodedResource.getResource(), ex);
    41         }
    42         finally {
    43             currentResources.remove(encodedResource);
    44             if (currentResources.isEmpty()) {
    45                 this.resourcesCurrentlyBeingLoaded.remove();
    46             }
    47         }
    48     }

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

      1 package org.springframework.beans.factory.xml;
      2 
      3 /**
      4      * Actually load bean definitions from the specified XML file.
      5      * @param inputSource the SAX InputSource to read from
      6      * @param resource the resource descriptor for the XML file
      7      * @return the number of bean definitions found
      8      * @throws BeanDefinitionStoreException in case of loading or parsing errors
      9      * @see #doLoadDocument
     10      * @see #registerBeanDefinitions
     11      */
     12     protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
     13             throws BeanDefinitionStoreException {
     14 
     15         try {
     16             Document doc = doLoadDocument(inputSource, resource);
     17             int count = registerBeanDefinitions(doc, resource);
     18             if (logger.isDebugEnabled()) {
     19                 logger.debug("Loaded " + count + " bean definitions from " + resource);
     20             }
     21             return count;
     22         }
     23         catch (BeanDefinitionStoreException ex) {
     24             throw ex;
     25         }
     26         catch (SAXParseException ex) {
     27             throw new XmlBeanDefinitionStoreException(resource.getDescription(),
     28                     "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
     29         }
     30         catch (SAXException ex) {
     31             throw new XmlBeanDefinitionStoreException(resource.getDescription(),
     32                     "XML document from " + resource + " is invalid", ex);
     33         }
     34         catch (ParserConfigurationException ex) {
     35             throw new BeanDefinitionStoreException(resource.getDescription(),
     36                     "Parser configuration exception parsing XML from " + resource, ex);
     37         }
     38         catch (IOException ex) {
     39             throw new BeanDefinitionStoreException(resource.getDescription(),
     40                     "IOException parsing XML document from " + resource, ex);
     41         }
     42         catch (Throwable ex) {
     43             throw new BeanDefinitionStoreException(resource.getDescription(),
     44                     "Unexpected exception parsing XML document from " + resource, ex);
     45         }
     46     }
     47 
     48     /**
     49      * Actually load the specified document using the configured DocumentLoader.
     50      * @param inputSource the SAX InputSource to read from
     51      * @param resource the resource descriptor for the XML file
     52      * @return the DOM Document
     53      * @throws Exception when thrown from the DocumentLoader
     54      * @see #setDocumentLoader
     55      * @see DocumentLoader#loadDocument
     56      */
     57     protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
     58         return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
     59                 getValidationModeForResource(resource), isNamespaceAware());
     60     }
     61 
     62     /**
     63      * Determine the validation mode for the specified {@link Resource}.
     64      * If no explicit validation mode has been configured, then the validation
     65      * mode gets {@link #detectValidationMode detected} from the given resource.
     66      * <p>Override this method if you would like full control over the validation
     67      * mode, even when something other than {@link #VALIDATION_AUTO} was set.
     68      * @see #detectValidationMode
     69      */
     70     protected int getValidationModeForResource(Resource resource) {
     71         int validationModeToUse = getValidationMode();
     72         if (validationModeToUse != VALIDATION_AUTO) {
     73             return validationModeToUse;
     74         }
     75         int detectedMode = detectValidationMode(resource);
     76         if (detectedMode != VALIDATION_AUTO) {
     77             return detectedMode;
     78         }
     79         // Hmm, we didn't get a clear indication... Let's assume XSD,
     80         // since apparently no DTD declaration has been found up until
     81         // detection stopped (before finding the document's root tag).
     82         return VALIDATION_XSD;
     83     }
     84 
     85     /**
     86      * Register the bean definitions contained in the given DOM document.
     87      * Called by {@code loadBeanDefinitions}.
     88      * <p>Creates a new instance of the parser class and invokes
     89      * {@code registerBeanDefinitions} on it.
     90      * @param doc the DOM document
     91      * @param resource the resource descriptor (for context information)
     92      * @return the number of bean definitions found
     93      * @throws BeanDefinitionStoreException in case of parsing errors
     94      * @see #loadBeanDefinitions
     95      * @see #setDocumentReaderClass
     96      * @see BeanDefinitionDocumentReader#registerBeanDefinitions
     97      */
     98     public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
     99         BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    100         int countBefore = getRegistry().getBeanDefinitionCount();
    101         documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    102         return getRegistry().getBeanDefinitionCount() - countBefore;
    103     }

    其实这段代码,这个方法大部分代码都是在处理异常情况,可以看出异常情况咋一个框架中是很重要的一个事情,其实只有三个事情:

    A:获取对XML文件的验证模式

    B:加载XML文件,并得到对应的Document

    C:根据返回的Document注册Bean信息

    这3个步骤支撑着真个spring容器的实现,尤其是第三步对配置文件的解析,逻辑非常的复杂,我们先从获取XML文件的验证模式说起

    3、获取XML 的验证模式

     (1)DTD和XSD区别

    XML文件为保证其正确性,会有验证模式,比较常用的是两种验证模式:DTD和XSD

    DTD: Document Type Definition 即文档类型定义,是一种XML约束模式语言,是XML文件的验证机制,属于XML文件组成的一部分。

    一个DTD文档包含:元素定义规则,元素间关系的定义规则,元素可使用属性、可使用的实体或符号规则

    XSD:XML Schemas Definition XML Schema描述了XML文档的结构。可以用一个指定的XML Schema来验证某个XML文档,以检查该XML文档是否符合其要求。

    (2)验证模式的读取

    spring通过getValidationModeForResource()来获取对应资源的验证模式,代码如下:

     1   package org.springframework.beans.factory.xml;
     2 
     3     /**
     4      * Determine the validation mode for the specified {@link Resource}.
     5      * If no explicit validation mode has been configured, then the validation
     6      * mode gets {@link #detectValidationMode detected} from the given resource.
     7      * <p>Override this method if you would like full control over the validation
     8      * mode, even when something other than {@link #VALIDATION_AUTO} was set.
     9      * @see #detectValidationMode
    10      */
    11     protected int getValidationModeForResource(Resource resource) {
    12         int validationModeToUse = getValidationMode();
    13         // 如果手动指定了验证模式 则使用指定的验证模式
    14         if (validationModeToUse != VALIDATION_AUTO) {
    15             return validationModeToUse;
    16         }
    17         // 如果未指定 则使用自动检测
    18         int detectedMode = detectValidationMode(resource);
    19         if (detectedMode != VALIDATION_AUTO) {
    20             return detectedMode;
    21         }
    22         // Hmm, we didn't get a clear indication... Let's assume XSD,
    23         // since apparently no DTD declaration has been found up until
    24         // detection stopped (before finding the document's root tag).
    25         return VALIDATION_XSD;
    26     }

    方法实现还是挺简单的,如果设定了验证模式则使用设定的验证模式(可以通过对调用XmlBeanDefinitionReader中的setValidationMode方法进行设定),否则使用自动检测的方式。而自动检测验证模式的功能是在函数deterValidationMode方法中实现的,在deterValidationMode函数中又将自动检测验证模式的工作委托费了专门处理类XmlValidationModelDetector,调用XmlValidationModelDetector的validationModeDetector方法,具体代码:

     1   package org.springframework.beans.factory.xml;
     2 
     3   /**
     4      * Detect which kind of validation to perform on the XML file identified
     5      * by the supplied {@link Resource}. If the file has a {@code DOCTYPE}
     6      * definition then DTD validation is used otherwise XSD validation is assumed.
     7      * <p>Override this method if you would like to customize resolution
     8      * of the {@link #VALIDATION_AUTO} mode.
     9      */
    10     protected int detectValidationMode(Resource resource) {
    11         if (resource.isOpen()) {
    12             throw new BeanDefinitionStoreException(
    13                     "Passed-in Resource [" + resource + "] contains an open stream: " +
    14                     "cannot determine validation mode automatically. Either pass in a Resource " +
    15                     "that is able to create fresh streams, or explicitly specify the validationMode " +
    16                     "on your XmlBeanDefinitionReader instance.");
    17         }
    18 
    19         InputStream inputStream;
    20         try {
    21             inputStream = resource.getInputStream();
    22         }
    23         catch (IOException ex) {
    24             throw new BeanDefinitionStoreException(
    25                     "Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " +
    26                     "Did you attempt to load directly from a SAX InputSource without specifying the " +
    27                     "validationMode on your XmlBeanDefinitionReader instance?", ex);
    28         }
    29 
    30         try {
    31             return this.validationModeDetector.detectValidationMode(inputStream);
    32         }
    33         catch (IOException ex) {
    34             throw new BeanDefinitionStoreException("Unable to determine validation mode for [" +
    35                     resource + "]: an error occurred whilst reading from the InputStream.", ex);
    36         }
    37     }
    38 
    39   // 这个方法在其他的包中,如下:
    40   package org.springframework.util.xml;
    41 
    42     /**
    43      * Detect the validation mode for the XML document in the supplied {@link InputStream}.
    44      * Note that the supplied {@link InputStream} is closed by this method before returning.
    45      * @param inputStream the InputStream to parse
    46      * @throws IOException in case of I/O failure
    47      * @see #VALIDATION_DTD
    48      * @see #VALIDATION_XSD
    49      */
    50     public int detectValidationMode(InputStream inputStream) throws IOException {
    51         // Peek into the file to look for DOCTYPE.
    52         BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
    53         try {
    54             boolean isDtdValidated = false;
    55             String content;
    56             while ((content = reader.readLine()) != null) {
    57                 content = consumeCommentTokens(content);
    58            // 如果读取的行是空或是注释则略过
    59                 if (this.inComment || !StringUtils.hasText(content)) {
    60                     continue;
    61                 }
    62                 if (hasDoctype(content)) {
    63                     isDtdValidated = true;
    64                     break;
    65                 }
    66            // 读取到<开始符号,验证模式一定会在开始符号之前
    67                 if (hasOpeningTag(content)) {
    68                     // End of meaningful data...
    69                     break;
    70                 }
    71             }
    72             return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
    73         }
    74         catch (CharConversionException ex) {
    75             // Choked on some character encoding...
    76             // Leave the decision up to the caller.
    77             return VALIDATION_AUTO;
    78         }
    79         finally {
    80             reader.close();
    81         }
    82     }

    4、获取Document

    经过了验证模式准备的步骤就可以进行Document加载了,同样XmlBeanFactoryReader类对于文档读取并没有亲力亲为,而是委托给了DocumentLoader去执行,这里的DocumentLoader是个接口,而真正调用的是DefaultDocumentLoader,解析代码如下:

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

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

    对于参数entityResolver,传入的是通过getEntityResolver()函数获取的返回值,代码如下:

     1   package org.springframework.beans.factory.xml;
     2 
     3     /**
     4      * Return the EntityResolver to use, building a default resolver
     5      * if none specified.
     6      */
     7     protected EntityResolver getEntityResolver() {
     8         if (this.entityResolver == null) {
     9             // Determine default EntityResolver to use.
    10             ResourceLoader resourceLoader = getResourceLoader();
    11             if (resourceLoader != null) {
    12                 this.entityResolver = new ResourceEntityResolver(resourceLoader);
    13             }
    14             else {
    15                 this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
    16             }
    17         }
    18         return this.entityResolver;
    19     }

    说明一下EntityResolver的用法:

    官方解释:如果SAX应用程序需要实现自定义处理外部实体,则必须使用此接口并使用setEntiryResolver方法向SAX驱动器注册一个实例。也就是说,对于解析一个XML,SAX首先读取该XML文档上的声明,根据声明去寻找相应的DTD定义,以便对文档进行一个验证。默认的寻找规则,即通过网络(实际上就是声明的DTD的URL地址)来下载相应的DTD声明,并进行验证。

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

    直接看EntityResolver接口的实现类DelegatingEntityResolver中解析XSD或者DTD格式的声明

    注意:至于publicId、systemId两个参数 对应着xml文件中命名空间中URL路径

      // spring中对EntityResolver类进行了实现,就是DelegatingEntityResolver EntityResolver类是jdk中的方法,包:org.xml.sax.EntityResolver;
      package org.springframework.beans.factory.xml;
    
        /**
         * Create a new DelegatingEntityResolver that delegates to
         * a default {@link BeansDtdResolver} and a default {@link PluggableSchemaResolver}.
         * <p>Configures the {@link PluggableSchemaResolver} with the supplied
         * {@link ClassLoader}.
         * @param classLoader the ClassLoader to use for loading
         * (can be {@code null}) to use the default ClassLoader)
         */
        public DelegatingEntityResolver(ClassLoader classLoader) {
          // 分别解析XSD 与 DTD格式
            this.dtdResolver = new BeansDtdResolver();
            this.schemaResolver = new PluggableSchemaResolver(classLoader);
        }
    
        @Override
        public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
            if (systemId != null) {
                if (systemId.endsWith(DTD_SUFFIX)) {
              // 如果是DTD 从这里解析
                    return this.dtdResolver.resolveEntity(publicId, systemId);
                }
                else if (systemId.endsWith(XSD_SUFFIX)) {
              // 通过调用META-INF/Spring.schema 解析
                    return this.schemaResolver.resolveEntity(publicId, systemId);
                }
            }
            return null;
        }

    注意:

    可以看到,对不同的验证模式,spring使用了不同的解析器解析。这里简单描述一下原理,加载DTD类型的BeanDtdResolver的resolverEntiry是直接截取systemId最后的xx.dtd然后去当前路径下寻找,而加载XSD类型的PluggableSchemaResolver类的resolverEntity是默认到META-INF/Spring.schemas文件中找到systemid所对应的XSD文件加载。

    5、解析及注册BeanDefinitions

    (1)当文件转换为Document后,接下来的提取及注册bean就是我们的重头戏。当程序已经拥有了XML文件文档文件的Document实例对象时,就会引入下面这个方法:

     1 package org.springframework.beans.factory.xml; 
     2 //XmlBeanDefinitionReader类
     3 
     4     /**
     5      * Register the bean definitions contained in the given DOM document.
     6      * Called by {@code loadBeanDefinitions}.
     7      * <p>Creates a new instance of the parser class and invokes
     8      * {@code registerBeanDefinitions} on it.
     9      * @param doc the DOM document
    10      * @param resource the resource descriptor (for context information)
    11      * @return the number of bean definitions found
    12      * @throws BeanDefinitionStoreException in case of parsing errors
    13      * @see #loadBeanDefinitions
    14      * @see #setDocumentReaderClass
    15      * @see BeanDefinitionDocumentReader#registerBeanDefinitions
    16      */
    17     public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    18       // 使用DefinitionDocumentReader实例化BeanDefinitionDocumentReader
    19         BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    20       // 记录统计前BeanDefinnition的加载个数
    21         int countBefore = getRegistry().getBeanDefinitionCount();
    22       // 加载及注册bean
    23         documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    24       // 记录本次加载到的BeanDefinition个数
    25         return getRegistry().getBeanDefinitionCount() - countBefore;
    26     }

    其中参数doc是通过上一节loadDocument加载转换出来的。在这个方法中很好地应用了面向对象中单一职责的原则,将逻辑处理委托给单一的类进行处理,而这个逻辑处理类就是BeanDefinitionDocumentReader。BeanDefinitionDocumentReader是一个接口,而实例化工作就是在createBeanDefinitionDocumentReader()方法中完成的,而通过此方法,BeanDefinitionDocumentReader真正的类型其实已经是DefaultBeanDefinitionDocumentReader了,进入DefaultBeanDefinitionDocumentReader后,发现这个方法的重要目的之一就是提取root,以便于再次将root作为参数继续BeanDefinition的注册。

     1   package org.springframework.beans.factory.xml;
     2   // DefaultBeanDefinitionDocumentReader.java
     3     /**
     4      * This implementation parses bean definitions according to the "spring-beans" XSD
     5      * (or DTD, historically).
     6      * <p>Opens a DOM Document; then initializes the default settings
     7      * specified at the {@code <beans/>} level; then parses the contained bean definitions.
     8      */
     9     @Override
    10     public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    11         this.readerContext = readerContext;
    12       // doc.getDocumentElement() 这个方法其实是得到Element root节点
    13         doRegisterBeanDefinitions(doc.getDocumentElement());
    14     }

    终于到了doRegisterBeanDefinitions(doc.getDocumentElement())方法了,这个方法是真正进行解析了,核心方法:

    // 与上面方法位于同一类中    
        /**
         * Register each bean definition within the given root {@code <beans/>} element.
         */
        @SuppressWarnings("deprecation")  // for Environment.acceptsProfiles(String...)
        protected void doRegisterBeanDefinitions(Element root) {
            // Any nested <beans> elements will cause recursion in this method. In
            // order to propagate and preserve <beans> 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.
        // 专门处理解析
            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);
                    // 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);
            parseBeanDefinitions(root, this.delegate);
        // 解析后处理。留给子类实现
            postProcessXml(root);
    
            this.delegate = parent;
        }

    首先是对profile的处理,然后开始进行解析,可是当我们跟进preProcessXml(root)和postProcessXml(root)发现代码是空的,这里是面向继承而设计的,如果继承自DefaultBeanDefinitionDocumentReader,只需要重写这两个方法就好了。

    (2)profile属性的使用

    profile 属性我是真的没用过,书中写的是大概就是用于生产环境与开发环境进行切换开发,最常用的是更换不同的数据库

    (3)解析并注册BeanDefinition

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

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

    上面代码,解析bean的配置,spring的XML配置中有两大类Bean的声明,一个是默认的

    <bean id="test" class=""  />

    另一个是自定义的:

    <tx:annotation-driven/> 这个。。。

    而这两种方式的读取及解析差别是非常大的,如果采用spring默认的配置,spring当然知道怎么做,但如果是自定义的,那么就需要用户实现一些接口与配置了。对于根节点或者子节点如果是默认命名空间的话则采用parseDefaultElement方法进行解析,否则采用delegate.parseCustomElement方法对自定义命名空间进行解析。而判断是否默认命名空间还是自定义命名空间的办法其实使用node.getNamespaceURI()获取命名空间,并与spring中固定命名空间http://www.springframework.org/schema/beans 进行对比。如果一直则是默认,否则就是自定义的。

    这写的,花费了挺长时间,然而我觉得并不是特别清楚,不过大概的思路还是能够捋顺清楚的。。。

  • 相关阅读:
    c#和unity引擎的所有笔记
    C#笔记(十九)——委托和事件
    委托
    c#笔记(十七)——队列
    js正则表达式
    mysql分页
    springMVC
    hibernate与spring整合实现transaction
    spring aop
    about hibernate lazy load and solution
  • 原文地址:https://www.cnblogs.com/ssh-html/p/10924552.html
Copyright © 2011-2022 走看看