14、IOC容器详解-BeanFactory
14.1 BeanFactory 及其子接口
BeanFactory-***
-
BeanFactory是 SpringFramework 中管理 Bean 最基本的根容器,下面的扩展都是为了实现某些额外的特性(层次性、可搜索性、可配置性等)。 -
BeanFactory中定义有 Bean的作用域,并统一由@Scope注解显式声明。 -
BeanFactory本身是所有 Bean 的 注册中心,所有的 Bean 最终都在BeanFactory中创建和保存。另外
BeanFactory中还集成了应用程序组件的配置,但这个概念比较老了, SpringFramework 3.1 之后出现了一个新的概念叫Environment,它才是真正做环境和配置的保存地。 -
支持多种类型的配置源,例如 xml、注解驱动。详见
BeanDefinition -
BeanFactory本身可以支持父子结构。 -
BeanFactory中设有完整的生命周期控制机制。
HierarchicalBeanFactory
它是体现了层次性的 BeanFactory,由此,BeanFactory 就有了父子结构。
-
常用api接口:
- 要设置父 BeanFactory 对象,可以使用
ConfigurableBeanFactory接口中的setParentBeanFactory()方法;要获取到父 BeanFactory 对象,可以使用getParentBeanFactory()方法。 - 接口中还有一个方法是
containsLocalBean(String name),它是检查当前本地的容器中是否有指定名称的 Bean ,而不会往上找父 BeanFactory 。 getBean()方法会从当前 BeanFactory 开始查找是否存在指定的 Bean ,如果当前找不到就依次向上找父 BeanFactory ,直到找到为止返回,或者都找不到最终抛出NoSuchBeanDefinitionException
- 要设置父 BeanFactory 对象,可以使用
-
如果当前
BeanFactory中有指定的 Bean 了,父BeanFactory中可能有吗?有可能!因为即便存在父子关系,但他们本质上是不同的容器,所以有可能找到多个相同的 Bean 。
换句话说,
@Scope中声明的 Singleton 只是在一个容器中是单实例的,但有了层次性结构后,对于整体的多个容器来看,就不是单实例的了。
ListableBeanFactory
-
适用于实现了 “预加载其所有bean定义信息” 的 BeanFactory,这与 BeanDefinition有关。
-
只列举出当前容器中的所有bean;若要获取包括父BeanFactory在内的所有bean,可通过
BeanFactoryUtils工具类来实现。 -
有选择性的列举,会忽略掉通过其他方式注册的任何单实例bean(例如
ConfigurableBeanFactory的registerSingleton方法);但
getBeanNamesForType和getBeansOfType除外,它们会检查这种手动注册的单实例 Bean,BeanFactory 的getBean也允许透明访问此类特殊 bean。-
选择性列举的 目的:
registerSingleton方法是在ConfigurableBeanFactory接口中被定义的。例如它在AbstractApplicationContext接口的prepareBeanFactory方法中被调用,来直接注册几个SpringFramework内部使用的组件。 SpringFramework 不希望开发者直接操控他们,于是就使用了这种方式来隐藏它们。
-
-
大部分方法不适合频繁调用,除了
getBeanDefinitionCount和containsBeanDefinition之外。- 因为一般情况下不会有业务需求会深入到 IOC 容器的底部,有的话可以读一遍就缓存起来,不需要频繁调用。
AutowireCapableBeanFactory-*
-
本身可以支持自动装配,而且还可以使 现有的 Bean 也支持自动装配。“现有”,实际上指的是那些不被 SpringFramework 管理的 Bean。
-
使用场景:
-
AutowireCapableBeanFactory这个子接口 不能在常规的应用程序代码中使用。一般情况下,请坚持使用BeanFactory或ListableBeanFactory。 -
其他框架与SpringFramework 集成时,其它框架的一些 Bean 实例无法让 SpringFramework 控制,但又需要注入一些由 SpringFramework 管理的对象,则其他框架的集成代码可以利用此接口。
- 典型场景如:你自己编写了一个 Servlet ,而这个 Servlet 里面需要引入 IOC 容器中的一个存在的 Service 。那么可以采用DL或DI的思路。其中 DI 的实现,就需要
AutowireCapableBeanFactory帮忙注入了。
- 典型场景如:你自己编写了一个 Servlet ,而这个 Servlet 里面需要引入 IOC 容器中的一个存在的 Service 。那么可以采用DL或DI的思路。其中 DI 的实现,就需要
-
-
这个接口一般用不到,没有在
ApplicationContext中实现,但也可以通过ApplicationContext的getAutowireCapableBeanFactory()方法从应用程序上下文中获得。 -
可以借助BeanFactoryAware注入
ConfigurableBeanFactory
具有了 “可配置” 的特性:
- 可读并可写。 普通的
BeanFactory只有 get 相关的操作,而 Configurable 开头的BeanFactory或者ApplicationContext就具有了 set 的操作。 - 大多数
BeanFactory的实现类都会实现这个带配置的接口,可以调用它里面定义的方法来对BeanFactory进行修改、扩展等。 ConfigurableBeanFactory接口并不希望开发者在应用程序代码中使用,而是坚持使用BeanFactory或ListableBeanFactory。因为 程序在运行期间按理不应该对BeanFactory再进行频繁的变动,此时只应该有读的动作,而不应该出现写的动作。
14.2 BeanFactory 的实现类
借助 IDEA ,可以将 BeanFactory 的实现类取出来,形成一张图:(当然,这里面不是所有的,只包含最核心的实现类)
AbstractBeanFactory
-
它是
BeanFactory最基本的抽象实现它可以从配置源(之前看到的 xml 、LDAP 、RDBMS 等)获取 Bean 的定义信息,而这个 Bean 的定义信息就是
BeanDefinition。它提供
ConfigurableBeanFactorySPI 的全部功能。SPI,全称为 Service Provider Interface,是 jdk 内置的一种服务提供发现机制。即 它可以加载预先在特定位置下配置的一些类。详见 “模块装配高级” 部分。 -
对Bean的支持
此类可以提供单实例 Bean 的缓存(通过其父类
DefaultSingletonBeanRegistry)、单例/原型 Bean 的决定、别名的处理(来源于AliasRegistry接口)、Bean 定义的合并(涉及到 Bean 的继承,后续章节讲解)、Bean 的销毁动作支持(DisposableBean)等等。此外,它可以通过实现
HierarchicalBeanFactory接口来管理BeanFactory层次结构 -
它定义了模板方法
子类要实现的主要模板方法是
getBeanDefinition和createBean,功能分别为:为给定的 bean 名称检索 bean 定义信息、根据给定的 bean 定义信息创建 bean 的实例。这两个方法都会在 IOC 容器初始化阶段 起到重要作用。而
createBean是 SpringFramework 能管控的所有 Bean 的创建入口。
AbstractAutowireCapableBeanFactory-***
AbstractAutowireCapableBeanFactory 中最最核心功能:Bean 的创建、属性填充和依赖的自动注入、Bean 的初始化。
-
提供Bean的创建逻辑实现
- 实现了
AutowireCapableBeanFactory接口,代表它可以 实现 自动注入 / 组件的自动装配 功能 了。 - 继承了
AbstractBeanFactory抽象类,并实现了其中的的createBean方法,代表它具有 创建 Bean 的功能。 ( 但最终实现bean创建的是 AbstractAutowireCapableBeanFactory 中定义的 protected 方法doCreateBean。 )
- 实现了
-
实现了 属性赋值和依赖的自动注入
-
实现了部分模板方法
与AbstractBeanFactory不同,它没有实现全部的模板方法,主要实现了
resolveDependency()模板方法:可解析 Bean 的成员中定义的属性依赖关系,用于按类型自动装配。 -
不负责BeanDefinition的注册
它实现了对 Bean 的创建、赋值、注入、初始化的逻辑,但 对于 Bean 的定义是如何进入
BeanFactory的,它不负责。
DefaultListableBeanFactory-***
唯一一个目前使用的 BeanFactory 的落地实现。
-
是 BeanFactory 的最终默认实现,没有abstract标识。是
ConfigurableListableBeanFactory和BeanDefinitionRegistry接口的默认实现。 -
在
AbstractAutowireCapableBeanFactory的基础上,完成了注册 Bean 定义信息的动作- 注册bean定义信息,是通过上面的
BeanDefinitionRegistry来实现的; - 完整的 BeanFactory 对 Bean 的管理,应该是:先注册 Bean 的定义信息,再完成 Bean 的创建和初始化动作。
- 注册bean定义信息,是通过上面的
-
不负责解析bean定义文件等工作
BeanFactory作为一个统一管理 Bean 组件的容器,其核心工作是 控制 Bean 在创建阶段的生命周期;而对于 Bean 从哪里来、如何被创建、有哪些依赖要被注入,是由专门的组件来处理(就是包括上面提到的BeanDefinitionReader在内的一些其它组件)。
-
有简化版
StaticListableBeanFactory,但不常用
XmlBeanFactory-*
在 SpringFramework 3.1 之后,XmlBeanFactory 正式被标注为过时,代替的方案是使用 DefaultListableBeanFactory + XmlBeanDefinitionReader ,这种设计更符合组件的单一职责原则。
14.3 小结与思考
-
什么是
BeanFactory?BeanFactory都实现了哪些基础功能? -
高级
BeanFactory扩展都有哪些重要特性? -
BeanFactory的实现类为什么要定义模板方法?目的是什么?SpringFramework 中大量使用模板方法模式来设计核心组件,它的思路是:父类提供逻辑规范,子类提供具体步骤的实现。
-
最终的
BeanFactory实现是谁?它都具备哪些特性?
15、IOC容器详解-ApplicationContext
推荐使用 ApplicationContext 而不是 BeanFactory 。 ApplicationContext 相比较 BeanFactory 的扩展:
| Feature | BeanFactory | ApplicationContext |
|---|---|---|
| Bean的实例化和属性注入 | Yes | Yes |
| 生命周期管理 | No | Yes |
| Bean后置处理器的支持 | No | Yes |
| BeanFactory后置处理器的支持 | No | Yes |
| 消息转换服务(国际化) | No | Yes |
| 事件发布机制(事件驱动) | No | Yes |
15.1 ApplicationContext的父子接口
ApplicationContext
ApplicationContext 是为应用程序提供配置的 最核心的接口。在应用程序运行时,它是只读的,但是如果受支持的话,它可以 重新加载 。
-
ApplicationContext组合多个功能接口:- 用于访问应用程序组件的 Bean 工厂方法。继承自
ListableBeanFactory。 - 以通用方式加载文件资源的能力。继承自
ResourceLoader接口。 - 能够将事件发布给注册的监听器。继承自
ApplicationEventPublisher接口。 - 解析消息的能力,支持国际化。继承自
MessageSource接口。 - 支持
父子上下文描述的层级结构。上下文中包含容器,以及:动态增强、资源加载、事件监听机制等多方面扩展功能。
- 用于访问应用程序组件的 Bean 工厂方法。继承自
-
负责部分回调注入:
-
除了标准的
BeanFactory生命周期功能外,ApplicationContext的实现还会 检测并调用ApplicationContextAware bean、ResourceLoaderAware bean、ApplicationEventPublisherAware bean和MessageSourceAware bean。反过来说,Aware 中的bean 会注入到ApplicationContext。- 因为 ApplicationContext 继承的几个接口也分别继承自各自的Aware接口,例如
ResourceLoader→ResourceLoaderAware
- 因为 ApplicationContext 继承的几个接口也分别继承自各自的Aware接口,例如
-
ConfigurableApplicationContext
与 ConfigurableBeanFactory 类似,也给 ApplicationContext 提供了 “可写” 的功能。
配置 和 生命周期 相关的方法都封装在这个接口,以避免暴露给 ApplicationContext 的调用者。本接口的方法仅应该由 启动和关闭代码 使用。
实现了该接口的类可以被客户端代码修改内部的某些配置:ConfigurableApplicationContext 中扩展了 setParent 、setEnvironment 、addBeanFactoryPostProcessor 、addApplicationListener 等方法,都是可以改变 ApplicationContext 本身的方法。
EnvironmentCapable
在 SpringFramework 中,以 Capable 结尾的接口,通常意味着可以通过这个接口的某个特定的方法(通常是 getXXX() )拿到特定的组件。
按照这个概念说法,这个 EnvironmentCapable 接口中就应该通过一个 getEnvironment() 方法拿到 Environment ,事实上也确实如此。
-
所有 Spring 的 ApplicationContext 都具有 EnvironmentCapable 功能。
-
它是具有获取并公开
Environment引用的接口。主要用于在接受BeanFactory实例的框架方法中执行 instanceof 检查,以便可以与环境进行交互。Environment是 SpringFramework 中抽象出来的类似于运行环境的独立抽象,它内部存放着应用程序运行的一些配置。 -
ConfigurableApplicationContext 重新定义了
getEnvironment()并缩小了签名范围,获取的是 ConfigurableEnvironment
MessageSource
mvc-讲解
支持国际化的组件。国际化,是针对不同地区、不同国家的访问,可以提供对应的符合用户阅读习惯(语言)的页面和数据。
Spring 为生产提供了两种现有的实现:ResourceBundleMessageSource、ReloadableResourceBundleMessageSource。
ApplicationEventPublisher
IOC事件驱动机制-讲解
事件的发布器。ApplicationContext 容器中作为观察者模式中广播器的实现。
ResourcePatternResolver
根据特定的路径去解析资源文件。
-
它是
ResourceLoader的扩展,ResourceLoader实现最基本的解析,而ResourcePatternResolver可以支持 Ant 形式的带 * 的路径解析。这种模式可以有如下写法:/WEB-INF/*.xml:匹配/WEB-INF目录下的任意 xml 文件/WEB-INF/**/beans-*.xml:匹配/WEB-INF下面任意层级目录的beans-开头的 xml 文件/**/*.xml:匹配任意 xml 文件
-
ResourcePatternResolver不仅可以匹配 Web 工程中 webapps 的文件,也可以匹配 classpath 下的文件(类路径下的资源文件)了,方式是在资源路径中加一个classpath*:的前缀。
15.2 ApplicationContext的实现类
AbstractApplicationContext
-
AbstractApplicationContext是ApplicationContext的抽象实现,是最核心的实现类,定义和实现了 绝大部分应用上下文的特性和功能。这些抽象实现主要起规范功能(借助模板方法),实际的动作需要子类自行去实现。 -
可以处理特殊类型的bean(后置处理器、监听器)。
-
ApplicationContext接口实现了国际化的接口MessageSource、事件广播器的接口ApplicationEventMulticaster,那作为容器,为了支持不同类型的组件注入需要,可以把自己当做不同的bean(转换为多种类型):- 一个
MessageSource也可以在上下文中作为一个普通的 bean 提供,名称为"messageSource"; - 作为类型为
ApplicationEventMulticaster的"applicationEventMulticaster"bean 提供。
- 一个
-
默认情况下,
AbstractApplicationContext加载资源文件的策略是直接继承了DefaultResourceLoader的策略,从类路径下加载; 但在 Web 项目中,它可以从ServletContext中加载,需要覆写getResourceByPath() 方法。 -
定义了一个控制 ApplicationContext 生命周期的核心方法 ——
refresh
GenericApplicationContext
- 拥有一个内部的
DefaultListableBeanFactory实例;实现了BeanDefinitionRegistry接口,任何 bean 定义读取器都可以应用于该容器中。 - 借助
BeanDefinitionRegistry处理特殊的bean。 - GenericApplicationContext 只能刷新一次,因为其在构造方法中就初始化好了
DefaultListableBeanFactory,而初始化好的BeanFactory不允许在运行期间被重复刷新。—— 这一点与下文的 AbstractRefreshableApplicationContext 不同。
AbstractRefreshableApplicationContext
- 每次刷新都会创建一个新的内部的
BeanFactory实例(也就是DefaultListableBeanFactory),而初始化中不创建。 - 子类唯一需要实现的方法是
loadBeanDefinitions,它在每次刷新时都会被调用。一个具体的实现应该将 bean 的定义信息加载到给定的DefaultListableBeanFactory中,通常委托给一个或多个特定的 bean 定义读取器。 - 在web环境下,也有一个类似的父类
AbstractRefreshableWebApplicationContext。与不带web的相比,刷新策略一致,额外扩展的是与 Servlet 相关的部分。AbstractRefreshableWebApplicationContext内部就组合了一个ServletContext,并且支持给 Bean 注入ServletContext、ServletConfig等 Servlet 中的组件。
AbstractRefreshableConfigApplicationContext
用于添加对指定配置位置的通用处理。
AbstractXmlApplicationContext
-
子类(
ClassPathXmlApplicationContext和FileSystemXmlApplicationContext)只需要实现getConfigLocations和/或getConfigLocations方法,来调整配置文件的默认读取位置。 -
loadBeanDefiitions 的实现
@Override protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { // Create a new XmlBeanDefinitionReader for the given BeanFactory. // 借助XmlBeanDefinitionReader解析xml配置文件 XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // Configure the bean definition reader with this context's // resource loading environment. beanDefinitionReader.setEnvironment(this.getEnvironment()); beanDefinitionReader.setResourceLoader(this); beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // Allow a subclass to provide custom initialization of the reader, // then proceed with actually loading the bean definitions. // 初始化BeanDefinitionReader,后加载BeanDefinition initBeanDefinitionReader(beanDefinitionReader); loadBeanDefinitions(beanDefinitionReader); }可以看到,它解析 xml 配置文件不是自己干活,是组合了一个
XmlBeanDefinitionReader,让它去解析。而实际解析配置文件的动作 ,就是调用
getConfigResources和getConfigLocations方法,取到配置文件的路径 / 资源类,交给BeanDefinitionReader解析:protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { Resource[] configResources = getConfigResources(); if (configResources != null) { reader.loadBeanDefinitions(configResources); } String[] configLocations = getConfigLocations(); if (configLocations != null) { reader.loadBeanDefinitions(configLocations); } }
ClassPathXmlApplicationContext
-
加载配置文件的方式可以使用 Ant 模式匹配(比较经典的写法当属 web.xml 中声明的
application-*.xml)。 -
如果有多个配置位置,则较新的
BeanDefinition会覆盖较早加载的文件中的BeanDefinition,可以利用它来通过一个额外的 XML 文件有意覆盖某些BeanDefinition。通常情况下,如果在一个 jar 包的 xml 配置文件中声明了一个 Bean ,并且又在工程的 resources 目录下又声明了同样的 Bean ,则 jar 包中声明的 Bean 会被覆盖,这也就是配置文件加载优先级的设定。
-
由于
ClassPathXmlApplicationContext继承了AbstractXmlApplicationContext,而AbstractXmlApplicationContext实际上是内部组合了一个XmlBeanDefinitionReader,所以 可以有一种组合的使用方式:- 利用
GenericApplicationContext或者子类AnnotationConfigApplicationContext,配合XmlBeanDefinitionReader,可以注解驱动和 xml驱动通吃。
- 利用
AnnotationConfigApplicationContext
- 是注解驱动的 IOC 容器。本身继承了
GenericApplicationContext,自然也只能刷新一次。 - 除了
@Component及其衍生出来的几个注解,更重要的是@Configuration注解,一个被@Configuration标注的类相当于一个 xml 文件。 - 初始化的两种方式:要么使用
register(Class ...)一对一注册配置类,要么使用scan(String ...)直接进行包扫描。 - 与
ClassPathXmlApplicationContext类似,解析的配置类也有先后之分。 如果有多个@Configuration类,则在以后的类中定义的@Bean方法将覆盖在先前的类中定义的方法。
15.3 小结与思考
- 什么是
ApplicationContext?它与BeanFactory的关系是什么? ApplicationContext相比较BeanFactory都扩展了哪些特性?ApplicationContext与BeanFactory是如何联系起来的?- 最终落地实现的
ApplicationContext都应该具备哪些核心特性?