Spring——BeanFactory

Spring容器

什么是Spring容器

  Spring容器是Spring的核心,它能够建立对象,把他们关联在一块儿,配置各个对象,并管理每一个对象的整个生命周期。Spring容器使用依赖注入(DI)来管理组成一个应用程序的组件。这些对象被称为Spring Beans (一个对象就是一个Bean)。spring

  Spring中有两种容器:测试

    ① BeanFactory 一个最简单的Spring容器,给依赖注入(DI)提供了基础的支持。ui

    ② ApplicationContext  此容器添加以一些企业须要用到的东西,更加全面。它包含了BeanFactory容器中的东西。this

 

BeanFactory容器

  在Spring中,有大量BeanFactory接口的实现类(见下图),可是,最经常使用的也就是XmlBeanFactory类(在Eclipse中,查看其源码能够看见已是一个过期的类了,但咱们也须要了解。),它能够从一个 XML 文件中读取配置元数据,由这些元数据来生成一个被配置化的系统或者应用。编码

  

 

                                    (BeanFactory接口实现类)spa

 

注:

  本文主要是针对下面一行代码执行所发生的事情的一些深刻探究。3d

  BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("配置文件"));code

 

  核心类介绍

    Ⅰ. DefaultListableBeanFactoryxml

       XmlBeanFactory类继承自DefaultListableBeanFacotry类,而DefaultListableBeanFactory类是Bean加载的核心部分,是Spring注册及加载Bean的默认实现。对象

       XmlBeanFactory类与DefaultListableBeanFactory类之间不一样的地方就在于XmlBeanFactory类中使用了自定义的XML读取器XmlBeanDefinitionReader,

      

                            (XmlBeanDefinitionReader对象)

      实现了个性化的BeanDefinitionReader读取,DefaultListableBeaFactory类继承了AbstractAutoWireCapableBeanFactory类,并实现了ConfigurableListableBeanFactory以及BeanDefinitionRegistry接口。

      

                            (DefaultListableBeanFactory类)

    DefaultListableBeanFactory类的基类,实现接口的一些相关类图:

    

 

                                    (容器加载部分相关类图)

    XmlBeanFactory类就继承自DefaultListableBeanFactory类。XmlBeanFactory类对DefaultListableBeanFactory类进行了扩展,在XMLBeanFactory中主要使用reader属性对资源文件进行读取和注册。

    XmlBeanFactory类的构造方法以下图:

    

                                    (XmlBeanFactory构造方法)

    Ⅱ. XmlBeanDefinitionReader

      上面咱们已经知道了XmlBeanFactory类和DefaultListableBeanFactory类的区别了,XmlBeanFactory类中定义了一个XmlBeanDefinitionReader对象,用于对资源文件进行处理。

      XML配置文件的读取对于Spring而言很是重要,由于Spring绝大部分功能都是以配置文件做为切入点的,那么,咱们就要从XmlBeanDefinitionReader类中梳理一下资源文件读取、解析和注册的大体流程。

      

                                            (配置文件读取相关类)

    经过上面配置文件读取相关类图能够获得读取大体流程以下:

      ① 经过继承自 AbstratcBeanDefinitionReader 中的方法,来使用 ResourceLoader 将资源文件路径转换为对应的 Resource 文件。

      ② 经过 DocumentLoader 对 Resource 文件进行转换,将 Resource 文件转换为 Document 文件。

      ③ 经过 DefaultBeanDefinitionDocumentReader 类对Document进行解析,并使用 BeanDefinitionParserDelegate 对 Element 进行解析。

    上述三步只是读取配置文件的一个大体流程,接下来将进行更加详细的解析。

    

    XmlBeanFactory

      上面咱们已经知道了 XmlBeanFacotry 和 DefaultListableBeanFactory 的区别了。XmlBeanFactory 结构以下图:

                        

                                (XmlBeanFactory类结构)

      在Spring中,咱们建立了一个配置文件,而且在配置文件中配置了一个Bean后,那么,咱们就要获取这个Bean。在测试代码中咱们都写过这样一句代码:

       XmlBeanFactory xmlBeanFactory = new XmlBeanFacotry(new ClassPathResource("配置文件"));

       也能够是:

       BeanFactory beanFactory = new XmlBeanFacotry(new ClassPathResource("配置文件"));

      总之,以上两句行代码就是读取配置文件建立容器。

      那么,new XmlBeanFacotry(new ClassPathResource("配置文件")) 这句代码究竟是怎么回事呢?先看看下面的 XmlBeanFactory 初始化时序图吧!(画得很差,将就看吧)

      

                                  (XmlBeanFactory初始化时序图)

      时序图解析:

        ◆ 首先,时序图从一个测试类开始,这个测试类就是上面建立 XmlBeanFactory 那里。

        ◆ 建立 XmlBeanFactory 须要一个 Resource 对象,因为 Resource 是接口,因此咱们使用其实现类 ClassPathResource 来构造 Resource 资源文件的实例对象。

        ◆ 有了 Resource 对象就能够进行 XmlBeanFactory 的初始化了,最后获得一个 BeanFactory。

      那么,问题来了,什么又是 Resource呢?它是怎么对资源进行封装处理的呢?

        ● Resource 是什么

        在说 Resource 以前,咱们要知道一个接口:InputStreamSource该接口封装任何能返回 InputStream 的类,好比File、Classpath下的资源、Byte、Array等。它只定义了一个方法:InputStream getInputStream() throws IOException; 该方法返回一个 InputStream 对象。

        Resource 接口抽象了全部 Spring 内部所使用到的底层资源: File、URL、Classpath等。Resource 接口中的方法及大体做用以下图:

       

                            (Resource接口)

        对于不一样来源的资源文件都有对象的 Resource 实现:

          文件(FileSystemResource)、Classpath资源(ClassPathResource)、URL资源(UrlResource)等。

 

      

                            (资源文件处理部分相关类)

        资源文件的加载在平常开发中也常常被使用,能够直接使用 Spring 提供的类,如在加载文件时使用以下代码:

          Resource resource = new ClassPathResource("资源文件");

          InputStream inputStream = resource.getInputStream();

        获得 inputStream 后,咱们就能够按照以往的开发方式进行开发了,并且还可使用 Resource 及其实现类的一些东西。

    当经过 Resource 相关类完成了对配置文件的封装后,配置文件的读取就是 XmlBeanDefinitionReader 来完成了。

    如今咱们已经知道了 Spring 中将配置文件封装为 Resource 对象,下面继续了解 XmlBeanFactory 的初始化过程。从上面 XmlBeanFactory类结构图中能够看出 XMLBeanFactory 类共有两个构造方法,以下图:

    

                              (XmlBeanFactory类构造方法)

    从上图咱们看出,第一个构造方法内部调用了该类内部的另外一个构造方法。因此,咱们也就了解第二个构造方法了。

    首先,在第一行出现了 super(parentBeanFactory) 这样一句代码,调用了父类(DefaultListableBeanFactory)的一个构造方法。

      

                              (DefaultListableBeanFactory有参构造方法)

    来到父类构造方法,咱们发现又继续调用了父类(AbstractAutowireCapableBeanFactory)的构造方法,见下图:

      

                                 (AbstractAutowireCapableBeanFactory类构造方法)

    从图中能够看出,有参构造方法(咱们使用的就是有参构造)先调用了本类中的一个无参构造方法,无参构造方法首先执行了父类(AbstractBeanFactory)的构造方法(一个空方法),这里了解一下 ignoreDependencyInterface 方法,该方法的主要功能就是 忽略自动链接给定的依赖接口(忽略给定接口的自动装配功能)。那么,该方法有什么用?

      如:当 A类 中有属性 B,当 Spring 在获取 A 的 Bean 的时候若是属性 B 尚未被初始化,Spring 就会自动初始化 B,(这也是Spring的一个重要特性)。可是,某些状况下,B 不会被初始化,好比 B 实现了 BeanNameAware 接口。Spring API介绍:应用程序上下文一般使用它来注册以其余方式解析的依赖项,如经过BeanFactoryAware实现的BeanFactory或经过ApplicationContextAware实现的ApplicationContext。默认状况下,只忽略BeanFactoryAware接口。若要忽略其余类型,请为每一个类型调用此方法。

    最后调用 setParentBeanFactory 方法设置 BeanFactory对象。

      

                                (setParentBeanFactory方法)

      在 setParentBeanFactory 方法中有一个 if 判断,用于判断是否已经关联了 BeanFactory,若是已经关联就抛出异常。

      

      • 加载Bean

        使用 ClassPathXmlApplicationContext 类加载容器,在读取配置文件加载 Bean 的定义也就和下面的同样的了。只是前面作了些不一样的处理。

        在上面讲到 Resource 时,咱们知道了 XmlBeanFactory 的构造方法,咱们也知道了其中一个构造方法首先调用了父类的构造方法,那么,在super()语句下面就是 this.reader.loadBeanDefinitions(resource) 方法的调用。这个方法才是整个资源加载的切入点,下面是该方法调用的时序图:

    

                                  (loadBeanDefinitions方法执行时序图)

        从上图能够看到,这个方法的调用引发了很大一串的工做。然而这些工做也只是在作准备工做,下面说说这里究竟在准备什么工做:

          (1)封装资源文件。当调用 loadBeanDefinitions 方法时,就会跳转到该方法中,该方法就调用了本类的一个重载方法,同时根据 Resource 对象建立一个EncodedResource 对象做为参数传递,使用 EncodedResource 的做用就是把 Resource 使用 EncodedResource 类进行封装

            

                                (loadBeanDefinitions(Resource resource)方法)

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

            

                       (loadBeanDefinitions(EncodedResource encodedResource)方法中 获取 InputStream 并 建立 InputSource)

          (3)经过刚刚建立的 InputSource 对象和 Resource继续调用 doLoadBeanDefinitions()方法

              

                                (doLoadBeanDefinitions(InputSource, Resource)方法调用)

 

    上面,咱们屡次看到 EncodedResource ,那么,它究竟是什么?

    • EncodedResource

      经过名字能够猜想该类和编码相关。该类主要就是对资源文件的编码进行处理的。其中一个很重要的方法 getReader() 方法 ,当设置了编码属性时,Spring 就会使用相应的编码做为输入流的编码

      首先看看它的一个构造方法:(该类一共有四个构造方法,但其他三个构造方法均调用了下面这个构造方法)

      

                              (EncodedResource类的一个构造方法)

      该构造方法主要就是对类中的属性进行初始化。

      再来看看 getReader() 方法:

          

                               (getReader() 方法)

      getReader() 方法构造了一个含编码的 InputStreamReader。将 Resource 封装为 EncodedResource 对象后,就来到了 XmlBeanDefinitionReader 类中的 loadBeanDefinitions() 方法(另外一个重载后的方法),也就是下图中的方法。

      

                                (loadBeanDefinitions(EncodedResource ..)方法部分重要代码)

      上图方法才算是真正的数据准备,也就是往上第7张 时序图中所描述的部分。

      再次回顾以上数据准备部份内容,首先将传入的 Resource 对象封装为 EncodedResource,为何须要封装?目的是考虑到 Resource 可能存在编码要求的状况,其次,经过SAX读取XML文件的方式来准备 InputSource对象,最后将准备的数据经过参数传递给核心处理方法 doLoadBeanDefinitions(InputSource inputSource, Resource resource)。下面就来看看 doLoadBeanDefinitions() 方法作了什么事情:

      

                            (doLoadBeanDefinitions() 方法部分代码(除catch部分))

      doLoadBeanDefinitions() 方法 try 后面有多个 catch ,除开这些 catch,那么,这段代码作了如下三件事情:

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

        (2)加载 XML 文件,获得对应的 Document 对象

        (3)根据返回的 Document 对象注册 Bean 信息

      以上三个操做支撑着整个Spring容器的实现基础,下面就将从这三个步骤讲起。

 

    • 获取 XML的验证模式

      XML 验证模式的做用:用于保证 XML 文件的正确性(貌似就是所说的约束),经常使用的验证模式有两种:DTD(Document Type Definition) 和 XSD(XML Schemas Definition)。详细了解验证模式请自行上网搜索。

      在上一张(doLoadBeanDefinitions方法代码)图中,咱们看见 try 块中第一行代码是: Document document = doLoadDocument(inputSource, resource); 调用了本类中的 doLoadDocument() 方法,可是,这句代码主要是用来获取 Document对象的,也就是上面第二件事情;可是,在其中会先完成对 XML 文件验证模式的获取

      

                          (doLoadDocument() 方法)

      咱们能够从上图看到,在 loadDocument() 方法执行时,先执行了 getValidationModeForResource(resource) 方法,该方法返回一个 int 类型的值。(在 Spring3.2 中,并不存在 doLoadDocument() 方法,是直接在 doLoadBeanDefintions() 方法中调用 getValidationModeForResource(resource) 方法 和 loadDocument() 方法),下面咱们看看 getValidationModeForResource(resource) 方法作了什么事情:

      

                              (getValidationModeForResource(resource) 方法)

      在 上面方法中,使用了几个常量,下图是几个常量所表示的值:

      

           (org.springframework.util.xml.XmlValidationModeDetector类中的几个常量)

      注意:XmlBeanDefinitionsReader 类中也有上图中除了 DOCTYPE 常量之外的几个 int 类型的同名的常量,其值就是上面的值也就是 Spring 把不一样的验证模式使用了不一样数值表示了而已

      在往上第二张图中得知 getValidationModeForResource(resource) 方法首先判断是否手动指定(经过 setValidationMode() 方法设置验证模式)了验证模式,判断方式就是 获取 validationMode 属性进行判断。不然使用自动检测的方式,自动检测调用了 org.springframework.beans.factory.xml.XmlBeanDefinitionReader.detectValidationMode(resource) 方法(本类中的方法)实现

      

                      (XmlBeanDefinitionReader.detectValidationMode(resource) 方法 省略了catch处理部分代码)

      在上图最后一个 try 块调用了 XmlValidationModeDetector 类中的 detectValidationMode(inputStream) 方法作进一步处理,下面就看看这个方法(这里若是要全面了解,建议本身查看一遍源码,毕竟在该方法中还调用了其余方法,也使用了几个常量,这里并无列出)。

      

                           (XmlValidationModeDetector 类中的 detectValidationMode(inputStream) 方法)

      上面获取验证模式部分须要根据 DTD 和 XSD来进行理解,由于获取验证模式就是根据两种验证模式使用方法来的。Spring 检测验证模式的方法就是判断是否包含 DOCTYPE,若是包含就是 DTD,不然就是 XSD(这一点从上面一张图的第一行注释就是能够看出:查看文件以查找 DOCTYPE),这一点从上图方法中能够很容易的看出。

      到这里,获取验证模式就讲解完了。

 

    • 获取 Document

      上面咱们知道了在获取 Document 以前要先获取 XML 验证模式。下面咱们就来看看 Spring 中是怎么获取 Document 的。在上面 (doLoadDocument() 方法)图中咱们看见 doLoadDocument 方法调用了本类 documentLoader 的 loadDocuemnt() 方法。documentLoader 定义以下:

      private DocumentLoader documentLoader = new DefaultDocumentLoader();

      DocumentLoader 是一个接口,因此使用其实现类 DefaultDocumentLoader;

      先来看看 DefaultDocumentLoader 类中的 loadDocument() 方法吧。

      

                            (loadDocument() 方法)

      上面这段代码就是基本的 经过 SAX 解析 XML,这里算是基本步骤了。首先建立 DocumentBuilderFacotry 对象,再经过 DocumentBuilderFactory 建立 DocumentBuilder,而后解析 inputSource 来返回 Document 对象。这里涉及到了 XML 解析相关知识,可自行上网深刻了解。

 

    • 解析及注册 BeanDefinitions

      在上面 (doLoadBeanDefinitions() 方法) 图中咱们知道 获取到了 Document 后,就执行下面这行代码:

      return registerBeanDefinitions(doc, resource);

      也就是 继续调用 registerBeanDefinitions(doc, resource) 方法。

                                  (registerBeanDefinitions(doc, resource) 方法)

      上图中第一行代码就是建立 BeanDefinitionDocumentReader,BeanDefinitionDocumentReader 是接口,而实例化是在 createBeanDefinitionDocumentReader() 方法中完成的,而经过执行此方法后,BeanDefinitionDocumentReader 真正的类型就是 DefaultBeanDefinitionDocumentReader (它的实现类)了。

      上图中第三行就是加载、注册 Bean了,因为 BeanDefinitionDocumentReader 是接口,因此咱们来到 DefaultBeanDefinitionDocumentReader 类中的 registerBeanDefinitions() 方法。

      

                             (registerBeanDefinitions(Document, XmlReaderContext) 方法)

      上图方法的重要目的之一就是提取 root,再将 root  做为参数继续 BeanDefinition 的注册。

      

                            (doRegisterBeanDefinitions(Element) 方法)

      上图代码中涉及到 profile 属性,该属性详情还请自行上网了解。上图程序第二部分,程序会先获取 beans 节点是否认义了 profile 属性,若是定义了则须要到环境变量中区寻找,每定义就不解析。

    处理了 profile 就能够开始进行 XML的读取了,下面看看上图框住的方法 parseBeanDefinitions(root, this.delegate)。

    

                            (parseBeanDefinitions(Element, BeanDefinitionParserDelegate) 方法)

      在 Spring 的 XML文件中可使用默认的 Bean声明,也能够自定义。因此 Spring 针对不一样的 Bean 声明作了不一样的处理。

      关于 bean 标签的解析将在后面介绍。直到这里,以上内容也只是 Spring 在加载配置文件,尚未真正的开始解析 bean标签。只是 Spring 容器的一个介绍。

 

PS: 本文存在诸多不足之处,望指出!

相关文章
相关标签/搜索