[Spring 系列] BeanFactory 与 ApplicationContext 详解

2,567 阅读7分钟

本文主题

上篇文章 [重温 IOC 设计理念] 后, 我想你们对 IOC 有了一定的了解。但是了解的同时,你肯定也有带着很多疑惑,例如说,
  1. Spring 中关于 IOC 的特定实现是什么?
  2. 它们是以什么方式来实现的?
  3. 如果 BeanFactory 和 ApplicationContext 都是容器的话,那么它们究竟谁才是底层 IOC 容器?还有它们面对真实的场景是什么?

我觉得,从 IOC 这个概念引出来的疑惑是非常多的。所以,这篇文章我决定对那篇文章进行一个“坑”的填充。所以这篇文章讲的主题是

IOC 在 Spring 的实现

或许很多人看过 Spring MVC 处理请求源码。既然你看过,相信你会看到过一个类 DispatherServlet,它就是负责处理请求的核心类,相当于一个中央调配器。而我们可以发现, DispatherServlet 有

一个构造器方法是注入一个 WebApplicationContext 的,代码如下:

public DispatcherServlet(WebApplicationContext webApplicationContext) {    
        super(webApplicationContext);    
        this.setDispatchOptionsRequest(true);
}
这个 WebApplicationContext 可以理解为 [ Web 环境的上下文 ]。学过计算机的同学都应该知道,所谓上下文其实就是存储一些运行时必要的数据。最为经典的就是一个正在执行的线程如果被调度的话,CPU 是要将其一些上下文的数据进行保存例如说“执行到哪一行”“全局变量”等等。

那你可能会说,这个 WebApplicationContext 有什么用呢?其实,WebApplicationContext 也是 IOC 的实现。如果你不相信,可以看一下官网的定义:
定义链接:docs.spring.io/spring-fram… 我们可以看到以下定义:

The BeanFactory interface provides an advanced configuration mechanism capable of managing any type of object.ApplicationContext is a sub-interface of BeanFactory.

它说 BeanFactory 和 ApplicationCont

ext 皆为 Spring IOC 容器。但是,前半句 Spring Doc 讲了,BeanFactory 这个接口提供了先进的配置管理机制来管理。而 ApplicationContext 则是其子类。那么,在讲 ApplicationContext 之前我们先讲一下 BeanFactory。

BeanFactory

BeanFactory 翻译过来就是 Bean 工厂。按照文档来说,它有以下的特点
  1. BeanFactory 的 API 为 Spring 的 IoC 功能提供了底层基础。
  2. BeanFactory 遵循了“开闭原则”,对外开放扩展,对内关闭修改。它通过 BeanFactoryAware / InitializingBean / DisposableBean 来扩展其他框架。
  3. BeanFactory 是核心 API,这也就意味着它功能非常简洁,关注度高。BeanFactory 的实现不会对配置格式或要使用的任何组件注释做任何假设。你可以理解为它不会要求你一定要遵守什么规则或规范才能使用 Spring,因为它的抽象性,可兼容不同的实现风格例如 XmlBeanDefinitionReader 和 AutowiredAnnotationBeanPostProcessor 等等。所以这就是Spring的容器如此灵活和可扩展的本质。[如果面试官问你你可以这么回答哦!]

现在我们来看看 BeanFactory 的一个基础 DefaultListableBeanFactory 的继承图,能看出它究竟如何通过继承的方式来实现以上特点。

API

看完了 BeanFactory,我们来看看它的 API


观察 API 可以看出,我补充说明:
  1. BeanFactory 接口仅仅提供了读功能却没有写功能?其实这些写功能会留给其子类进行实现的;
  2. 然后 BeanProvider 是在 Spring 中提供了延迟查找和注入的功能实现,以后有机会会讲解说明
  3. 我们能看到 BeanFactory 提供了 name 或者 requireType 来获取 Bean。那是不是说明仅仅提供了两种方法进行查找呢?这个就是我们接下来要讲的话题 - Bean 原元数据定义

Bean 原元数据定义

若想从 BeanFactory 获取 Bean,那么首先我们需要讲 Bean 的元数据搞进 Spring IOC 容器当中。怎么弄?Spring 提供了两种方法:
  1. 通过注解配置
  2. 通过 Java API 配置
  3. 通过 XML 配置

像注解配置,我们经常会看到 Spring Boot 使用了 @Autowire 注解,例如说

public class UserService {
    @Autowireprivate 
    UserDao userDao;
}

像 Java 配置,我们也可以使用 @Configuration / @Bean / @Import / @DependsOn,例如说

@Configuration
public class SecurityConfig{
    @Bean public UserDao userDao() {
        return new UserDao();
    }
}
像 XML 配置就不用说了,那是 Spring / Hibernate / Structs 2 时代进行做的事情,例如说

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="..." class="...">  
        <!-- collaborators and configuration for this bean go here -->
    </bean>
</beans>


上面是我总结了一下其 Bean 元数据初始化的方法。或许你会问我究竟这几种元数据初始化方法,谁胜谁劣?目前按照我的认知来说如果单从便利性来比较是不可取的,我只能说各种方式都 not bad。各有个的应用场景,像 Spring Boot 这种约定大于配置的框架来说,更倾向于注解的方式,但是不意味着 XML 被淘汰了。因为 XML 的方式在某些特定场景还是会被用上,又或者是为了兼容老项目,Spring Boot 还特意保留着。所以,即使是多种选择,在业务架构上可能多种选择结合才是最佳方案。

看完了容器元数据的初始化,BeanFactory 作为 Spring IOC 最基础的容器,大部分时候我们不会直接从 BeanFactory 获取 Bean 的,那怎么使用它获取 Bean 呢?那接下来就要引出了其优秀的子类 - ApplicationContext 了!

ApplicationContext

上面已经说了 ApplicationContext 是 BeanFactory 的子类。现在我开始完善一下信息。ApplicationContext 是位于 org.springframework.context 包下。它属于 BeanFactory 的扩展增强。ApplicationContext 扩展都是一些面向框架的功能,例如说
  1. i18n 国际化
  2. 有强大的资源抽象类 Resource 和加载起 ResourceLoader,可以读取 URLS 和 文件
  3. 事件监听机制
  4. 可以加载多个具有层级关系的上下文

从上面的选项其实我们都可以看到,Spring 框架考虑都是一些很常用的,围绕面向企业级别应用的特性。你想想,一个拥有诸多特性,而且又拥有 IOC 容器的功能类,那肯定就像是一个框架内部的控制器,方便别的组件进行调用。所以这也是为什么我们一般不直接通过 BeanFactory 进行 Bean 的获取,这也算是 “外开内聚”
的体现吧。那接下来我们看一下怎么通过 ApplicationContext 获取 Bean

假设我们在 resources/META-INF 目录下有个配置文件 servicees.xml

<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> 

    <bean id="userDao" class="com.jc.test01.dao.UserDao"> 
    </bean> 

</beans>
然后我们使用 Java API 形式进行获取

// create and configure beans	创建和配置 BeansApplicationContext context = new ClassPathXmlApplicationContext("services.xml");

// retrieve configured instance  通过 名称+类型进行查找
UserDao userDao = context.getBean("userDao", UserDao.class);

// use configured instance	调用方法
List<String> userList = userDao.getUsernameList();

上面的代码我要解释一下。ClassPathXmlApplicationContext 是 ApplicationContext 的扩展,就好像上面说的,Spring 多数情况下都是通过其丰富的子类来实现多种不同的应用风格。例如 ClassPathXmlApplicationContext 从名字看来我们可以知道它的意思是 “从 classPath 加载 XML 文件而来的上下文”[说明这是一个面向 XML 风格的实现类]。类似的还有注解风格的 AnnotationConfigApplicationContext ,还有文件系统风格的 FileSystemXmlApplicationContext 的等等。有兴趣可以通过 IDEA 来调试其继承图,你会大有收获。

继承图



在图上的粉红色圈圈,我们可以看到 ApplicationContext 的扩展特性实现的接口。总的来说,Spring 更喜欢的是通过组合继承的方法来实现特性,这样使各个类具备更高的专注度,提高封装和利用率。

结语

到了文章末尾,我要回答这几个问题。

Spring 中关于 IOC 的特定实现是什么?
特定的实现是 BeanFactory 和 ApplicationContext。

它们是以什么方式来实现的?
其实从它们的类图就可以发现,它们其实都会通过组合和继承。如上面所说的,这样的好处在于提高各个类的职能,使其更加专注于其技能,也便于外部进行扩展。

如果 BeanFactory 和 ApplicationContext 都是容器的话,那么它们究竟谁才是底层 IOC 容器?还有它们面对真实的场景是什么?
上面说了 BeanFactory 和 ApplicationContext 皆为 IOC 的实现。但是 BeanFactory 是专注于提供最核心最基础的 IOC 功能,而 ApplicationContext 是面向企业级别应用而集成更多便于开发的特性的 IOC 实现。它们两个专注方向不同,非多余实现,互不影响。