Spring容器——BeanFactory详解

Spring容器

什么是Spring容器

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

Spring中有两种容器:

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

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

 

BeanFactory容器

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

  

 

(BeanFactory接口实现类)

 

注:

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

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

 

核心类介绍

Ⅰ. DefaultListableBeanFactory

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

在上面讲到 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 容器的一个介绍。

作者:Dream saddle
原文链接:https://www.cnblogs.com/dream-saddle/p/9258258.html

请使用浏览器的分享功能分享到微信等