spring_14-15 IOC进阶-两种容器

432 阅读14分钟

14、IOC容器详解-BeanFactory

14.1 BeanFactory 及其子接口

img

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 中有指定的 Bean 了,父 BeanFactory 中可能有吗?

    有可能!因为即便存在父子关系,但他们本质上是不同的容器,所以有可能找到多个相同的 Bean

    换句话说, @Scope 中声明的 Singleton 只是在一个容器中是单实例的,但有了层次性结构后,对于整体的多个容器来看,就不是单实例的了


ListableBeanFactory

  • 适用于实现了 “预加载其所有bean定义信息” 的 BeanFactory,这与 BeanDefinition有关。

  • 只列举出当前容器中的所有bean;若要获取包括父BeanFactory在内的所有bean,可通过 BeanFactoryUtils 工具类来实现。

  • 有选择性的列举,会忽略掉通过其他方式注册的任何单实例bean(例如 ConfigurableBeanFactoryregisterSingleton 方法);

    getBeanNamesForTypegetBeansOfType 除外,它们会检查这种手动注册的单实例 Bean,BeanFactory 的 getBean 也允许透明访问此类特殊 bean。

    • 选择性列举的 目的:

      registerSingleton 方法是在 ConfigurableBeanFactory 接口中被定义的。例如它在 AbstractApplicationContext 接口的 prepareBeanFactory 方法中被调用,来直接注册几个SpringFramework内部使用的组件。 SpringFramework 不希望开发者直接操控他们,于是就使用了这种方式来隐藏它们

  • 大部分方法不适合频繁调用,除了 getBeanDefinitionCountcontainsBeanDefinition 之外。

    • 因为一般情况下不会有业务需求会深入到 IOC 容器的底部,有的话可以读一遍就缓存起来,不需要频繁调用。

AutowireCapableBeanFactory-*

  • 本身可以支持自动装配,而且还可以使 现有的 Bean 也支持自动装配。“现有”,实际上指的是那些不被 SpringFramework 管理的 Bean

  • 使用场景:

    • AutowireCapableBeanFactory 这个子接口 不能在常规的应用程序代码中使用。一般情况下,请坚持使用 BeanFactoryListableBeanFactory

    • 其他框架与SpringFramework 集成时,其它框架的一些 Bean 实例无法让 SpringFramework 控制,但又需要注入一些由 SpringFramework 管理的对象,则其他框架的集成代码可以利用此接口

      • 典型场景如:你自己编写了一个 Servlet ,而这个 Servlet 里面需要引入 IOC 容器中的一个存在的 Service 。那么可以采用DL或DI的思路。其中 DI 的实现,就需要 AutowireCapableBeanFactory 帮忙注入了。
  • 这个接口一般用不到,没有在 ApplicationContext 中实现,但也可以通过 ApplicationContextgetAutowireCapableBeanFactory() 方法从应用程序上下文中获得。

  • 可以借助BeanFactoryAware注入


ConfigurableBeanFactory

具有了 “可配置” 的特性:

  • 可读并可写。 普通的 BeanFactory 只有 get 相关的操作,而 Configurable 开头的 BeanFactory 或者 ApplicationContext 就具有了 set 的操作。
  • 大多数 BeanFactory 的实现类都会实现这个带配置的接口,可以调用它里面定义的方法来对 BeanFactory 进行修改、扩展等。
  • ConfigurableBeanFactory 接口并不希望开发者在应用程序代码中使用,而是坚持使用 BeanFactoryListableBeanFactory 。因为 程序在运行期间按理不应该对 BeanFactory 再进行频繁的变动,此时只应该有读的动作,而不应该出现写的动作。

14.2 BeanFactory 的实现类

借助 IDEA ,可以将 BeanFactory 的实现类取出来,形成一张图:(当然,这里面不是所有的,只包含最核心的实现类)

img

AbstractBeanFactory

  • 它是 BeanFactory 最基本的抽象实现

    它可以从配置源(之前看到的 xml 、LDAP 、RDBMS 等)获取 Bean 的定义信息,而这个 Bean 的定义信息就是 BeanDefinition

    它提供 ConfigurableBeanFactory SPI 的全部功能。SPI,全称为 Service Provider Interface,是 jdk 内置的一种服务提供发现机制。即 它可以加载预先在特定位置下配置的一些类。详见 “模块装配高级” 部分。

  • 对Bean的支持

    此类可以提供单实例 Bean 的缓存(通过其父类 DefaultSingletonBeanRegistry )、单例/原型 Bean 的决定、别名的处理(来源于 AliasRegistry 接口)、Bean 定义的合并(涉及到 Bean 的继承,后续章节讲解)、Bean 的销毁动作支持( DisposableBean )等等。

    此外,它可以通过实现 HierarchicalBeanFactory 接口来管理 BeanFactory 层次结构

  • 它定义了模板方法

    子类要实现的主要模板方法是 getBeanDefinitioncreateBean ,功能分别为:为给定的 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标识。是 ConfigurableListableBeanFactoryBeanDefinitionRegistry 接口的默认实现。

  • AbstractAutowireCapableBeanFactory 的基础上,完成了注册 Bean 定义信息的动作

    • 注册bean定义信息,是通过上面的 BeanDefinitionRegistry 来实现的;
    • 完整的 BeanFactory 对 Bean 的管理,应该是:先注册 Bean 的定义信息,再完成 Bean 的创建和初始化动作。
  • 不负责解析bean定义文件等工作

    • BeanFactory 作为一个统一管理 Bean 组件的容器,其核心工作是 控制 Bean 在创建阶段的生命周期;而对于 Bean 从哪里来、如何被创建、有哪些依赖要被注入,是由专门的组件来处理(就是包括上面提到的 BeanDefinitionReader 在内的一些其它组件)。
  • 有简化版StaticListableBeanFactory,但不常用


XmlBeanFactory-*

在 SpringFramework 3.1 之后,XmlBeanFactory 正式被标注为过时,代替的方案是使用 DefaultListableBeanFactory + XmlBeanDefinitionReader ,这种设计更符合组件的单一职责原则

14.3 小结与思考

  1. 什么是 BeanFactoryBeanFactory 都实现了哪些基础功能?

  2. 高级 BeanFactory 扩展都有哪些重要特性?

  3. BeanFactory 的实现类为什么要定义模板方法?目的是什么?

    SpringFramework 中大量使用模板方法模式来设计核心组件,它的思路是:父类提供逻辑规范,子类提供具体步骤的实现

  4. 最终的 BeanFactory 实现是谁?它都具备哪些特性?

15、IOC容器详解-ApplicationContext

推荐使用 ApplicationContext 而不是 BeanFactoryApplicationContext 相比较 BeanFactory 的扩展:

FeatureBeanFactoryApplicationContext
Bean的实例化和属性注入YesYes
生命周期管理NoYes
Bean后置处理器的支持NoYes
BeanFactory后置处理器的支持NoYes
消息转换服务(国际化)NoYes
事件发布机制(事件驱动)NoYes

15.1 ApplicationContext的父子接口

img

ApplicationContext

ApplicationContext 是为应用程序提供配置的 最核心的接口。在应用程序运行时,它是只读的,但是如果受支持的话,它可以 重新加载

  • ApplicationContext 组合多个功能接口:

    • 用于访问应用程序组件的 Bean 工厂方法。继承自 ListableBeanFactory
    • 以通用方式加载文件资源的能力。继承自 ResourceLoader 接口。
    • 能够将事件发布给注册的监听器。继承自 ApplicationEventPublisher 接口。
    • 解析消息的能力,支持国际化。继承自 MessageSource 接口。
    • 支持 父子上下文 描述的层级结构。上下文中包含容器,以及:动态增强、资源加载、事件监听机制等多方面扩展功能。
  • 负责部分回调注入:

    • 除了标准的 BeanFactory 生命周期功能外,ApplicationContext 的实现还会 检测并调用 ApplicationContextAware beanResourceLoaderAware beanApplicationEventPublisherAware beanMessageSourceAware bean。反过来说,Aware 中的bean 会注入到 ApplicationContext

      • 因为 ApplicationContext 继承的几个接口也分别继承自各自的Aware接口,例如 ResourceLoaderResourceLoaderAware

ConfigurableApplicationContext

ConfigurableBeanFactory 类似,也给 ApplicationContext 提供了 “可写” 的功能。

配置生命周期 相关的方法都封装在这个接口,以避免暴露给 ApplicationContext 的调用者。本接口的方法仅应该由 启动和关闭代码 使用。

实现了该接口的类可以被客户端代码修改内部的某些配置:ConfigurableApplicationContext 中扩展了 setParentsetEnvironmentaddBeanFactoryPostProcessoraddApplicationListener 等方法,都是可以改变 ApplicationContext 本身的方法。

EnvironmentCapable

在 SpringFramework 中,以 Capable 结尾的接口,通常意味着可以通过这个接口的某个特定的方法(通常是 getXXX() )拿到特定的组件。

按照这个概念说法,这个 EnvironmentCapable 接口中就应该通过一个 getEnvironment() 方法拿到 Environment ,事实上也确实如此。

  • 所有 Spring 的 ApplicationContext 都具有 EnvironmentCapable 功能。

  • 它是具有获取并公开 Environment 引用的接口。主要用于在接受 BeanFactory 实例的框架方法中执行 instanceof 检查,以便可以与环境进行交互。

    Environment 是 SpringFramework 中抽象出来的类似于运行环境独立抽象,它内部存放着应用程序运行的一些配置。

  • ConfigurableApplicationContext 重新定义了 getEnvironment() 并缩小了签名范围,获取的是 ConfigurableEnvironment

MessageSource

mvc-讲解

支持国际化的组件。国际化,是针对不同地区、不同国家的访问,可以提供对应的符合用户阅读习惯(语言)的页面和数据。

Spring 为生产提供了两种现有的实现:ResourceBundleMessageSourceReloadableResourceBundleMessageSource

ApplicationEventPublisher

IOC事件驱动机制-讲解

事件的发布器。ApplicationContext 容器中作为观察者模式中广播器的实现。

ResourcePatternResolver

根据特定的路径去解析资源文件。

  1. 它是 ResourceLoader 的扩展,ResourceLoader 实现最基本的解析,而ResourcePatternResolver 可以支持 Ant 形式的带 * 的路径解析。这种模式可以有如下写法:

    • /WEB-INF/*.xml :匹配 /WEB-INF 目录下的任意 xml 文件
    • /WEB-INF/**/beans-*.xml :匹配 /WEB-INF 下面任意层级目录的 beans- 开头的 xml 文件
    • /**/*.xml :匹配任意 xml 文件
  2. ResourcePatternResolver 不仅可以匹配 Web 工程中 webapps 的文件,也可以匹配 classpath 下的文件(类路径下的资源文件)了,方式是在资源路径中加一个 classpath*: 的前缀。

15.2 ApplicationContext的实现类

img

AbstractApplicationContext

  1. AbstractApplicationContextApplicationContext的抽象实现,是最核心的实现类,定义和实现了 绝大部分应用上下文的特性和功能。这些抽象实现主要起规范功能(借助模板方法),实际的动作需要子类自行去实现。

  2. 可以处理特殊类型的bean(后置处理器、监听器)。

  3. ApplicationContext 接口实现了国际化的接口 MessageSource 、事件广播器的接口 ApplicationEventMulticaster ,那作为容器,为了支持不同类型的组件注入需要,可以把自己当做不同的bean(转换为多种类型):

    • 一个 MessageSource 也可以在上下文中作为一个普通的 bean 提供,名称为 "messageSource"
    • 作为类型为 ApplicationEventMulticaster"applicationEventMulticaster" bean 提供。
  4. 默认情况下,AbstractApplicationContext 加载资源文件的策略是直接继承了 DefaultResourceLoader 的策略,从类路径下加载; 但在 Web 项目中,它可以从 ServletContext 中加载,需要覆写getResourceByPath() 方法。

  5. 定义了一个控制 ApplicationContext 生命周期的核心方法 —— refresh

GenericApplicationContext

  1. 拥有一个内部的 DefaultListableBeanFactory 实例;实现了 BeanDefinitionRegistry 接口,任何 bean 定义读取器都可以应用于该容器中。
  2. 借助 BeanDefinitionRegistry 处理特殊的bean。
  3. GenericApplicationContext 只能刷新一次,因为其在构造方法中就初始化好了 DefaultListableBeanFactory,而初始化好的 BeanFactory 不允许在运行期间被重复刷新。—— 这一点与下文的 AbstractRefreshableApplicationContext 不同。

AbstractRefreshableApplicationContext

  1. 每次刷新都会创建一个新的内部的 BeanFactory 实例(也就是 DefaultListableBeanFactory ),而初始化中不创建。
  2. 子类唯一需要实现的方法是 loadBeanDefinitions ,它在每次刷新时都会被调用。一个具体的实现应该将 bean 的定义信息加载到给定的 DefaultListableBeanFactory 中,通常委托给一个或多个特定的 bean 定义读取器。
  3. 在web环境下,也有一个类似的父类 AbstractRefreshableWebApplicationContext 。与不带web的相比,刷新策略一致,额外扩展的是与 Servlet 相关的部分。AbstractRefreshableWebApplicationContext 内部就组合了一个 ServletContext ,并且支持给 Bean 注入 ServletContextServletConfig 等 Servlet 中的组件。

AbstractRefreshableConfigApplicationContext

用于添加对指定配置位置的通用处理。

AbstractXmlApplicationContext

  1. 子类( ClassPathXmlApplicationContextFileSystemXmlApplicationContext)只需要实现 getConfigLocations 和/或 getConfigLocations 方法,来调整配置文件的默认读取位置。

  2. 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 ,让它去解析。

    而实际解析配置文件的动作 ,就是调用 getConfigResourcesgetConfigLocations 方法,取到配置文件的路径 / 资源类,交给 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

  1. 加载配置文件的方式可以使用 Ant 模式匹配(比较经典的写法当属 web.xml 中声明的 application-*.xml )。

  2. 如果有多个配置位置,则较新的 BeanDefinition 会覆盖较早加载的文件中的 BeanDefinition ,可以利用它来通过一个额外的 XML 文件有意覆盖某些 BeanDefinition

    通常情况下,如果在一个 jar 包的 xml 配置文件中声明了一个 Bean ,并且又在工程的 resources 目录下又声明了同样的 Bean ,则 jar 包中声明的 Bean 会被覆盖,这也就是配置文件加载优先级的设定。

  3. 由于 ClassPathXmlApplicationContext 继承了 AbstractXmlApplicationContext ,而 AbstractXmlApplicationContext 实际上是内部组合了一个 XmlBeanDefinitionReader ,所以 可以有一种组合的使用方式:

    • 利用 GenericApplicationContext 或者子类 AnnotationConfigApplicationContext ,配合 XmlBeanDefinitionReader ,可以注解驱动和 xml驱动通吃。

AnnotationConfigApplicationContext

  1. 是注解驱动的 IOC 容器。本身继承了 GenericApplicationContext ,自然也只能刷新一次。
  2. 除了 @Component 及其衍生出来的几个注解,更重要的是 @Configuration 注解,一个被 @Configuration 标注的类相当于一个 xml 文件。
  3. 初始化的两种方式:要么使用 register(Class ...) 一对一注册配置类,要么使用 scan(String ...) 直接进行包扫描。
  4. ClassPathXmlApplicationContext 类似,解析的配置类也有先后之分。 如果有多个 @Configuration 类,则在以后的类中定义的 @Bean 方法将覆盖在先前的类中定义的方法。

15.3 小结与思考

  1. 什么是 ApplicationContext ?它与 BeanFactory 的关系是什么?
  2. ApplicationContext 相比较 BeanFactory 都扩展了哪些特性?
  3. ApplicationContextBeanFactory 是如何联系起来的?
  4. 最终落地实现的 ApplicationContext 都应该具备哪些核心特性?