zoukankan      html  css  js  c++  java
  • Spring5源码分析(007)——IoC篇之加载BeanDefinition总览

    注:《Spring5源码分析》汇总可参考:Spring5源码分析(002)——博客汇总 


      本文主要讲述加载 BeanDefinition 的大致流程,并没有涉及太多的细节,概括来说就是:XML Resource --> EncodedResource --> org.xml.sax.InputSource --> XML Document --> BeanDefinition。目录结构如下:

    1、回顾创建 IoC 容器的大概过程

      回到上一篇文章提到的创建 IoC 容器的大概过程(这里重新复制一下,也可以回头看看笔者的上一篇介绍:Spring5源码分析(006)——IoC篇之核心类DefaultListableBeanFactory和XmlBeanDefinitionReader):

    Resource resource = new ClassPathResource("beans.xml"); // (1.1)
    // ClassPathResource resource = new ClassPathResource("beans.xml"); // (1.2)
    // Resource[] resources = PathMatchingResourcePatternResolver.getResources(locationPattern); // (1.3),需要遍历获取 BeanDefinition
    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); // (2)
    XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory); // (3)
    reader.loadBeanDefinitions(resource); // (4)
    • 1、获取资源:资源的定位,可以是直接创建并提供资源,或者通过 PathMatchingResourcePatternResolver 对指定的资源路径进行解析定位然后获取对应的(多个)资源;
    • 2、获取 BeanFactory:这里使用默认的 DefaultListableBeanFactory;
    • 3、根据 BeanFactory 创建一个 BeanDefinitionReader 实例对象,即资源解析器;
    • 4、装载资源:也即是 bean 的加载过程,解析并装配成 BeanDefinition,然后就可以根据 BeanDefinition 做各种事情了。

    注:如果 (4) 中直接使用的是 BeanDefinitionReader.loadBeanDefinitions(String location) ,则最终是通过 (1.3) 定位获取到资源,即本文开头的那个方法中的如下这一行(参考:Spring5源码分析(005)——IoC篇之统一资源加载Resource和ResourceLoader),然后再对 Resource[] resources 遍历调用 reader.loadBeanDefinitions(resource),一样的效果。

      整个处理流程总结起来的话,其实分为这几个步骤:先是资源的定位(获取资源),之后就是对资源进行解析并装配成 BeanDefinition,然后就是将 BeanDefinition 注册好(Map 存储),最后便可以根据注册的 BeanDefinition 来进行各种操作了。

    • 资源的定位和获取:这里所谓的资源,很大程度上就是指 bean 的配置元数据了,包括我们举例的 bean 的 XML 配置信息。容器的初始化的第一步,就是先找到这些配置资源,这部分讲解参考:Spring5源码分析(005)——IoC篇之统一资源加载Resource和ResourceLoader
    • 资源的装载:也即是对资源进行解析并装配成 BeanDefinition。这部分时通过 BeanDefinitionReader 读取、解析 Resource 配置资源,将用户定义的所有的 <bean /> 转换成 IoC 容器内部的数据结构 BeanDefinition。
    • 注册:将解析好的 BeanDefinition 通过接口 BeanDefinitionRegistry (实际上是实现类 DefaultListableBeanFactory )进行注册,其内部是使用一个 BeanDefinition Map 来实现,进行 BeanDefinition 的维护。
      • 默认情况下,这里仅仅是 BeanDefinition 的维护,并不涉及到依赖注入和 bean 的直接创建,bean 实例化发生在应用第一次调用 getBean 时。
      • 如果 bean 定义中设置了 lazyinit = false,那么这个 bean 的依赖注入会在容器初始化的时候完成。
    /** Map of bean definition objects, keyed by bean name. */
    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

    2、DefaultListableBeanFactory 初始化

      在进入正戏前,先看看 new DefaultListableBeanFactory() 里面处理了什么事情,它调用了父类的构造器,如下:

    /**
     * Create a new DefaultListableBeanFactory.
     */
    public DefaultListableBeanFactory() {
        super();
    }

      看下父类 AbstractAutowireCapableBeanFactory 的构造器:

    /**
     * Create a new AbstractAutowireCapableBeanFactory.
     */
    public AbstractAutowireCapableBeanFactory() {
        super();
        ignoreDependencyInterface(BeanNameAware.class);
        ignoreDependencyInterface(BeanFactoryAware.class);
        ignoreDependencyInterface(BeanClassLoaderAware.class);
    }

      这里看看 ignoreDependencyInterface 方法是什么情况:

    /**
     * Ignore the given dependency interface for autowiring.
     * <p>This will typically be used by application contexts to register
     * dependencies that are resolved in other ways, like BeanFactory through
     * BeanFactoryAware or ApplicationContext through ApplicationContextAware.
     * <p>By default, only the BeanFactoryAware interface is ignored.
     * For further types to ignore, invoke this method for each type.
     * <p>忽略给定依赖接口的自动装配。
     * <p>这通常会被应用程序上下文用于注册通过其他方式解析的依赖项,如通过 BeanFactoryAware 注入的 BeanFactory,
     * 或通过 ApplicationContextAware 注入的 ApplicationContext。
     * <p>默认情况下,只有 BeanFactoryAware 接口被忽略。如果还要忽略其他类型,请为每个类型调用此方法。
     * @see org.springframework.beans.factory.BeanFactoryAware
     * @see org.springframework.context.ApplicationContextAware
     */
    public void ignoreDependencyInterface(Class<?> ifc) {
        this.ignoredDependencyInterfaces.add(ifc);
    }
    
    /**
     * Dependency interfaces to ignore on dependency check and autowire, as Set of
     * Class objects. By default, only the BeanFactory interface is ignored.
     * <p>忽略依赖检查和自动装配的依赖接口集合。默认情况下,只忽略 BeanFactory 接口。
     */
    private final Set<Class<?>> ignoredDependencyInterfaces = new HashSet<>();

      可以看到,ignoreDependencyInterface 方法的主要功能是忽略给定接口的依赖检查和自动装配功能,这么做的目的是什么呢,会起到什么样的效果呢?

      举例来说,当 A 中有属性 B ,那么当 Spring 在获取 A 的 Bean 的时候如果其属性 B 还没有初始化,那么 Spring 会自动初始化 B ,这也是 Spring 中提供的一个重要特性。但是,某些情况下,B 不会被初始化,其中的一种情况就是 B 实现了 BeanNameAware 接口。Spring 的 api doc 中是这样介绍的:自动装配时忽略给定的依赖接口,典型应用是通过其他方式解析 Application 上下文注册依赖,类似于 BeanFactory 通过 BeanFactoryAware 进行注入或者 ApplicationContext 通过 ApplicationContextAware 进行注入 。

    3、loadBeanDefinitions(Resource resource)

      回到重头戏 XmlBeanDefinitionReader.loadBeanDefinitions(resource),也即是 bean 的加载,看下 loadBeanDefinitions(resource) 的实际处理过程:

    /**
     * Load bean definitions from the specified XML file.
     * <p>从指定的 XML 文件加载 bean definitions
     * @param resource the resource descriptor for the XML file
     * @return the number of bean definitions found
     * @throws BeanDefinitionStoreException in case of loading or parsing errors
     */
    @Override
    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        return loadBeanDefinitions(new EncodedResource(resource));
    }

      EncodedResource 的作用:通过类名称,可以大致推断出这个类主要是用于对资源文件的编码(字符集)进行处理的。其中一个比较重要的方法是 Reader getReader(),用于读取(指定编码集的)字符流。EncodedResource 的源码也比较简单;

    /**
     * Holder that combines a {@link Resource} descriptor with a specific encoding
     * or {@code Charset} to be used for reading from the resource.
     * <p>结合了指定用于资源读取的编码或者字符集和 Resource 描述符的 Holder
     * <p>Used as an argument for operations that support reading content with
     * a specific encoding, typically via a {@code java.io.Reader}.
     * <p>用作支持使用特定编码读取内容的操作(通常是通过java.io.Reader)的参数。
     * @author Juergen Hoeller
     * @author Sam Brannen
     * @since 1.2.6
     * @see Resource#getInputStream()
     * @see java.io.Reader
     * @see java.nio.charset.Charset
     */
    public class EncodedResource implements InputStreamSource {
    
        private final Resource resource;
    
        @Nullable
        private final String encoding;
    
        @Nullable
        private final Charset charset;
    
        // constructor here...
    
        // getter here...
    
        /**
         * Determine whether a {@link Reader} is required as opposed to an {@link InputStream},
         * i.e. whether an {@linkplain #getEncoding() encoding} or a {@link #getCharset() Charset}
         * has been specified.
         * @see #getReader()
         * @see #getInputStream()
         */
        public boolean requiresReader() {
            return (this.encoding != null || this.charset != null);
        }
    
        /**
         * Open a {@code java.io.Reader} for the specified resource, using the specified
         * {@link #getCharset() Charset} or {@linkplain #getEncoding() encoding}
         * (if any).
         * <p>使用指定的字符集或编码(如果有的话)为指定的资源打开一个 java.io.Reader
         * @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());
            }
        }
    
        /**
         * Open an {@code InputStream} for the specified resource, ignoring any specified
         * {@link #getCharset() Charset} or {@linkplain #getEncoding() encoding}.
         * <p>为指定的 resource 打开 InputStream,忽略任何字符编码(Charset 和 encoding)
         * @throws IOException if opening the InputStream failed
         * @see #requiresReader()
         * @see #getReader()
         */
        @Override
        public InputStream getInputStream() throws IOException {
            return this.resource.getInputStream();
        }
      // ...
    
    }

      loadBeanDefinitions(Resource resource) 只是做了封装 Resource 的操作,然后又委托给 loadBeanDefinitions(EncodedResource encodedResource) 进行实际的处理:

    private final ThreadLocal<Set<EncodedResource>> resourcesCurrentlyBeingLoaded =
            new NamedThreadLocal<>("XML bean definition resources currently being loaded");
    /**
     * Load bean definitions from the specified XML file.
     * <p>从指定的XML文件加载 bean definitions
     * @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.isTraceEnabled()) {
            logger.trace("Loading XML bean definitions from " + encodedResource);
        }
       // 1、记录已经加载的资源
        Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
        if (currentResources == null) {
            currentResources = new HashSet<>(4);
            this.resourcesCurrentlyBeingLoaded.set(currentResources);
        }
        if (!currentResources.add(encodedResource)) {
            throw new BeanDefinitionStoreException(
                    "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
        }
        try {
               // 2、从 EncodedResource 中封装的 Resource 中获取对应的 InputStream
            InputStream inputStream = encodedResource.getResource().getInputStream();
            try {
                       // 封装成 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);// 3、移除已记录的资源
            if (currentResources.isEmpty()) {
                this.resourcesCurrentlyBeingLoaded.remove();
            }
        }
    }

      这里说明下处理流程:

    • 1、记录已经加载的资源:这里通过一个 ThreadLocal 属性 resourcesCurrentlyBeingLoaded 来获取当前线程在加载地资源,如果没有,则把当前资源添加进去,然后进行下一步地加载;如果资源已存在,则抛出异常 BeanDefinitionStoreException,从这个异常地描述说明 “Detected cyclic loading” 来看,其实时为了避免循环加载,出现死循环。因此最后加载成功后就把资源给移除了
    • 2、从 EncodedResource 中封装的 Resource 中获取对应的 InputStream 并构造 org.xml.sax.InputSource,通过 SAX 读取 XML 文件的方式来准备 InputSource 对象,最后调用真正的核心处理逻辑部分 doLoadBeanDefinitions(inputSource, encodedResource.getResource())

    4、doLoadBeanDefinitions(InputSource inputSource, Resource resource)

      这个是加载 bean 的核心逻辑,源码如下:

    /**
     * Actually load bean definitions from the specified XML file.
     * <p>实际处理从指定的 XML 文件加载 bean definitions。
     * @param inputSource the SAX InputSource to read from
     * @param resource the resource descriptor for the XML file
     * @return the number of bean definitions found
     * @throws BeanDefinitionStoreException in case of loading or parsing errors
     * @see #doLoadDocument
     * @see #registerBeanDefinitions
     */
    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
    
        try {
            // 1、加载 XML 文件,并得到对应的Document(内部会先获取对 XML 文件的验证模式)
            Document doc = doLoadDocument(inputSource, resource);
            // 2、根据 Document 解析和注册 BeanDefinition
            int count = registerBeanDefinitions(doc, resource);
            if (logger.isDebugEnabled()) {
                logger.debug("Loaded " + count + " bean definitions from " + resource);
            }
            return count;
        }
        catch (BeanDefinitionStoreException ex) {
            throw ex;
        }
        catch (SAXParseException ex) {
            throw new XmlBeanDefinitionStoreException(resource.getDescription(),
                    "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
        }
        catch (SAXException ex) {
            throw new XmlBeanDefinitionStoreException(resource.getDescription(),
                    "XML document from " + resource + " is invalid", ex);
        }
        catch (ParserConfigurationException ex) {
            throw new BeanDefinitionStoreException(resource.getDescription(),
                    "Parser configuration exception parsing XML from " + resource, ex);
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException(resource.getDescription(),
                    "IOException parsing XML document from " + resource, ex);
        }
        catch (Throwable ex) {
            throw new BeanDefinitionStoreException(resource.getDescription(),
                    "Unexpected exception parsing XML document from " + resource, ex);
        }
    }
    /**
     * Actually load the specified document using the configured DocumentLoader.
     * <p>使用配置的 DocumentLoader 对指定 document 的进行实际的加载处理
     * @param inputSource the SAX InputSource to read from
     * @param resource the resource descriptor for the XML file
     * @return the DOM Document
     * @throws Exception when thrown from the DocumentLoader
     * @see #setDocumentLoader
     * @see DocumentLoader#loadDocument
     */
    protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
        return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
                getValidationModeForResource(resource), isNamespaceAware());
    }
    
    /**
     * Determine the validation mode for the specified {@link Resource}.
     * If no explicit validation mode has been configured, then the validation
     * mode gets {@link #detectValidationMode detected} from the given resource.
     * <p>Override this method if you would like full control over the validation
     * mode, even when something other than {@link #VALIDATION_AUTO} was set.
     * <p>确定指定资源的验证模式。如果没有配置显式的验证模式,则从给定资源检测获取验证模式。
     * <p>如果需要完全控制验证模式,请覆盖此方法,即使在设置了VALIDATION_AUTO以外的内容时也是如此。
     * @see #detectValidationMode
     */
    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;
    }
    • 1、调用 doLoadDocument(InputSource inputSource, Resource resource),加载 XML 文件,并得到对应的Document(内部会先获取对 XML 文件的验证模式)。
      • 获取 Document 又分为2个步骤:
      • 1.1、通过调用 getValidationModeForResource(Resource resource) 来获取指定 XML 资源的验证模式,也即是 xml 开头常见到的各种 DTD 和 XSD 了。
      • 1.2、通过调用 DocumentLoader.loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) 来获取实际的 Document 实例。
    • 2、调用 registerBeanDefinitions(Document doc, Resource resource),根据 Document 解析和注册 BeanDefinition。

    总的来说,在上面冗长的代码其实只做了三件事,这三件事的每一件都必不可少:

    • 1、获取对 XML 文件的验证模式。
    • 2、加载 XML 文件,并得到对应的 Document。
    • 3、解析返回的 Document 并注册 Bean 信息。

    这 3 个步骤支撑着整个 Spring 容器部分的实现,尤其是第 3 步对配置文件的解析,逻辑非常的复杂,后面我们会继续对这三3个步骤进行分析讲解。下一篇将从获取 XML 文件的验证模式讲起。
    (未完待续 ing。。。)

    5、总结:加载 BeanDefinition 的大致流程

      本篇其实只是初步讲述了加载 BeanDefinition 的大致流程,并没有深入讲解具体的加载过程,这里重新汇总如下:

    概括下就是:XML Resource --> EncodedResource --> org.xml.sax.InputSource --> XML Document --> BeanDefinition

    6、参考

  • 相关阅读:
    廖雪峰的多线程 1
    保持良好的心态 戒骄戒躁
    Break camelCase
    int32 to IPv4 (int32到IPv4地址转换)
    Stop gninnipS My sdroW!
    Find The Parity Outlier 找到奇偶校验异常值
    今日新闻整理 2020-7-31
    改造rabbitmq demo 到 jpa
    Flink实战(110):FLINK-SQL应用场景(11)connector(十九)Flink 与 hive 结合使用(七) Flink Hive Connector 使用
    Hadoop基础(六十):面试题 Hadoop数据切片(二)切片机制源码
  • 原文地址:https://www.cnblogs.com/wpbxin/p/13174370.html
Copyright © 2011-2022 走看看