BeanFactory
Spring Ioc
是一个管理Bean的容器,在Spring的定义中,他要求所有的Ioc容器都需要实现接口BeanFactory
。==BeanFactory
是一个顶级容器接口。==
public interface BeanFactory {
// 前缀
String FACTORY_BEAN_PREFIX = "&";
// 根据名称获取bean
Object getBean(String name) throws BeansException;
// 根据名称获取bean,返回指定类型
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
// 根据名称获取bean,使用指定的参数初始化
Object getBean(String name, Object... args) throws BeansException;
// 根据类型获取bean
<T> T getBean(Class<T> requiredType) throws BeansException;
// 根据类型获取bean,使用指定的参数初始化
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
// 根据类型获取bean提供者
<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
<T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);
// 是否存在指定名称的bean
boolean containsBean(String name);
// 指定的bean是否是单例
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
// 指定的bean是否是原型
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
// 是否类型匹配
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
// 获取指定bean的类型
@Nullable
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
@Nullable
Class<?> getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException;
// 获取bean的别名
String[] getAliases(String name);
}
在BeanFactory
中含有多个getBean方法,这是Ioc容器最重要的方法之一,主要是从Ioc容器中获取Bean。这些getBean的方法中有按类型、按名称获取bean,这也是说,在Spring Ioc容器中,允许按照名称或者类型获取bean.
由于BeanFacttory
的功能还不够强大,因此Spring在BeanFactory
的基础上,还设计了更为高级的接口:ApplicationContext
,这是BeanFactory
的子接口之一,在Spring的体系中BeanFactory
和ApplicationContext
是最为重要的接口设计,实际使用的大部分Spring Ioc容器是ApplicationContext
接口的实现类。
ApplicationContext
接口通过集成上级接口,进而集成BeanFactory
接口。在BeanFactory
的基础上,扩展了消息国际化接口(MessageSource)和资源模式解析接口(ResourcePatternResolver)。
容器的基本使用
我们在idea中创建一个springboot的工程,只需要lombok和web的starter即可。(gradle)
我们这里创建了spring boot的工程,但是只是使用gradle自动下载spring的依赖即可。
还记得一个spring项目是如何创建的吗?
复习一下:
spring--hello~!(如何搭建一个spring项目)
省去依赖包,我们创建一个spring的项目
新增bean
新增beans.xml
然后注释掉spring boot中原来的代码,添加我们的代码
运行
可以看到,最终,我们打印出了people的name.也就是说,通过XmlBeanFactory
加载了我们制定的配置文件后,使用指定的配置文件,创建了我们需要的Ioc容器。最后我们可以通过名字获取bean。
XmlBeanFactory源码
虽然XmlBeanFactory
基本上没有人在使用,而且也被标记为废弃了,但是,作为我spring入门的一个方法,我还是想看看其具体的实现。
这是XmlBeanFactory
的UML图:
我们可以看到,XmlBeanFactory
直接继承DefaultListableBeanFactory
,而DefaultListableBeanFactory
上面又有非常多的继承关系。
-
AliasRegistry
:定义对alias的简单增删改等操作。 -
SimpleAliasRegistry
:主要使用Map作为alias的缓存,并对接口AliasRegistry
进行实现。 -
SingletonBeanRegistry
:定义对单例的注册及获取。 -
BeanFactry
:定义获取bean及bean的各种特性。 -
DefaultSingletonBeanRegistry
:对接口SingletonBeanRegistry
的实现。 -
HierarchicalBeanFactory
:继承BeanFactory
,也就是在BeanFactory
定义的功能的基础上增加了对parentFactory
的支持。 -
BeanDefinitionRegistry
:定义对BeanDefinition
的各种增删改操作。 -
FactoryBeanRegistrySupport
:在DefaultSingletonBeanRegistry
基础上增加了对BeanFactory
的特殊处理。 -
ConfigurableBeanFactory
:提供配置Factory的各种方法。 -
ListableBeanFactory
:根据各种条件获取bean的配置清单。 -
AbstractBeanFactory
:综合FactoryBeanRegistrySupport
和ConfigurableBeanFactory
的功能。 -
AutowireCapableBeanFactory
:提供创建bean、自动注入、初始化以及应用bean的后处理器。 -
AbstractAutowireCapableBeanFactory
A:综合AbstractBeanFactory
并对接口AutowireCapableBeanFactory
进行实现。 -
ConfigurableListableBeanFactory
:BeanFactory
配置清单,指定忽略类型及接口等。 -
DefaultListableBeanFactory
:综合上述全部功能,主要是对Bean进行注册后的处理。
XmlBeanFactory
对DefaultListableBeanFactory
进行了扩展,从Xml文档中读取BeanDefinition
,对于注册和获取都是使用的父类继承的方法。
XmlBeanDefinitionReader
这是XmlBeanDefinitionReader
的UML图
这些类主要的操作:
-
ResourceLoader
:定义资源加载器,主要应用于根据给定的资源文件地址返回对应的Resource。 -
BeanDefinitionReader
:主要定义自语言文件读取并转换为BeanDefinition
的各个功能。 -
EnvironmentCapable
:定义获取Environment方法。 -
DockumentLoader
:定义从资源文件加载到转换为Document的功能。 -
AbstractBeanDefinitionReader
:对EnvironmentCapable,BeanDefinitionReader
类定义的功能进行实现。 -
BeanDefinitionDocumentReader
定义读取Document并注册BeanDefinition功能。 -
BeanDefinitionParserDelegate
:定义解析Element的各种方法。
XmlBeanFactory
我们看下XmlBeanFactory
的时序图:
在main方法中首先调用ClassPathResource
的构造方法来构造Resource资源文件的实例对象,这样后续的资源处理就可以用Resource提供的服务进行操作,有了Resource后,就可以进行XmlBeanFactory
的初始化了。
配置文件的封装
Spring的配置文件读取是通过ClassPathResource
进行封装的,比如new ClassPathResource("beans.xml")
。
在java中,将不同来源的资源抽象成URL,通过注册不同的handler(URLStreamHandler)
来处理不同来源的资源的读取逻辑,一般handler的类型使用不同的前缀来识别,比如:file:,http:,jar:
等。但是URL没有默认定义相对ClassPath或者ServletContext等资源的handler,虽然可以注册自己的URLStreamHandler来解析特定的URL前缀(比如cclasspath:
),这样需要了解URL的实现机制,而且URL也没有提供基本方法检查资源是否存在等。所以Spring对内部使用到的资源资源实现了自己的抽象结构:Resource
这是Resource的方法
Resource接口抽象了所有Spring内部使用到的底层资源:File,URL,Classpath等。
定义了3个判断当前资源状态的方法:存在性(exists
)、可读性(isReadable
)、是否处于打开状态(isOpen
)。另外,Resource接口还提供了不同资源到URL、URI、File类型的转换,以及获取lastModified
属性、文件名(不带路径信息的文件名,getFilename()
)的方法。为了便于操作,Resource 还提供了基于当前资源创建一个相对资源的方法:createRelativeO
。在错误处理中需要详细地打印出错的资源文件,因而 Resource
还提供了 getDescription
()方法用于在错误处理中的打印信息。
不同来源的资源文件都有相应的Resource实现:文件(FileSystemResource
),ClassPath资源(ClassPathResource
),URL资源(UrlResource
),InputStream资源(InputStreamResource
),Byte数组(ByteArrayResource
)等。
具体实现也很简单,ClassPathResource是通过ClassLoader进行读取文件的:
得到了配置流后,就交给了XmlBeanDefinitionReader
进行解析。
在初始化父类的时候,忽略装配一些类
加载Bean
- 封装资源文件。当进入
XmlBeanDefinitionReader
后首先对参数Resource使用EncodeResource
类进行封装。 - 获取输入流。从Resource中获取对应的
InputSttream
并构造InputSource
。 - 通过构造的
InputSource
实例和Resource实例继续调用方法doLoadBeanDefinitions
。
EncodeedResource
主要用于对资源文件的编码进行相应的编码处理。
如果设置了编码属性,会使用相应的编码作为输入流的编码。
这个方法主要是将重新编码的输入流转换为SAX的InputSource对象,同时如果指定了编码,需要设置相关的属性。
这个方法处理了两件事情:
- 加载XML文件,得到对应的Document.
- 根据返回的Document注册bean
XML的验证模式
XML文件的验证模式保证了XML文件的正确性,而比较常用的验证模式有两种:DTD和XSD。
DTD
DTD(Document Type Dedfinition
)即文档类型定义,是一种XML约束模式语言,是XML文件的验证机制,属于XML文件组成的一部分。DTD是一种保证XML文档格式正确的有效方法,可以通过比较XML文档和DTD文件来看文档是否符合规范,元素和标签使用是否正确。一个DTD文档包含:元素的定义规则,元素间关系的定义规则,元素可以使用的属性,可以使用的实体或符号规则。
要使用DTD验证模式的时候需要在XML文件的头部声明:
DTD文件里面是一些ENTITY节点
XSD
XML Schema语言就是XSD(XML Schema Definition
)。Xml Schema
描述了XML文档的结构。可以用一个指定的XML Schema
来验证某个XML文档,已检查改XML文档是否符合其要求。文档设计者可以通过过XML Schema
指定一个XML文档所允许的结构和内容,并可以据此检查一个XML文档是否是以有效的。XML Schema
本身是一个XML文档,他符合XML语法结构,可以使用通用的XML解析器解析它。
在使用XML Schema
文档对XML实例文档进行检验,除了要声明空间外(xmlns=....
),还必须指定该名称空间锁对应的XML Schema
文档的存储位置。通过schemaLocation
属性来指定名称空间所对一样的XML Schema
文档的存储位置。它包含两个部分,一部分是名称空间的URL,另一部分就是该名称空间所表示的XML Schema
文件位置或者URL地址。
获取Document
调用了DocumentLoader
接口的方法:
这个接口只有一个实现类DefaultDocumentLoader
BeanDefinitions
将配置文件转换为Document
后,就会根据Document
对象进行注册Bean.
注册bean:
首先通过反射获取BeanDefinitionDocumentReader
的对象。
然后记录下本次注册前,已经有多少个bean被注册了。
然后调用documentReader
对象进行注册。
使用的还是默认实现
在使用documentReader进行读取时,首先读取的是root节点。
接下来就是解析的核心逻辑了:
首先处理profile
解析bean就是这里了