【从零构建Spring|第六节】 应用上下文, 自动识别, 资源加载, 扩展机制的实现

【从零构建Spring|第六节】 应用上下文, 自动识别, 资源加载, 扩展机制的实现

前言

提示

说到spring容器,有的同学可能知道指的是BeanFactory,有的可能说是ApplicationContext,其实这二者都是容器类。

BeanFactory是底层基础类,位于spring-beans模块中,而ApplicationContext位于spring-context模块,是对BeanFactory的装饰,即包含一个BeanFactory实例。

ApplicationContext 本质上还是调用了 BeanFactory 内部的系列方法实现功能,并拓展了许多别的功能

日志

在手写 Spring 框架过程中,需要不断扩展新的功能,如一个 Bean 的定义和实例化过程前后,是否可以做到支持自定义扩展,能够对 Bean 对象执行一些修改、增强、记录操作?要做到能随时扩展新功能,本身架构就必须设计好,不能把代码写死、耦合

本章节有所改动的内容:

  • 合并获取 BeanFactory、读取配置、注册 Bean 等操作,合并到 Spring 框架上下文中。让面向 Spring 的组件 DefaultListableBeanFactory 尽量不暴露给用户

  • 在 Bean 的定义和初始化过程中插入接口类,以便实现在 Bean 对象从注册到实例化的过程中执行用户的自定义操作

  • 新增两个对 Bean 对象扩展的两个接口,其实也是 Spring 框架中非常具有重量级的两个接口:

    BeanFactoryPostProcess

    BeanPostProcessor

    (本质上也是 Bean 的一种,通过

    getBeanOfType(Bean(...)Processor.class)

    获取

    • BeanFactoryPostProcessor,是由 Spring 框架组建提供的容器扩展机制,允许在 Bean 对象注册后但未实例化之前,对 Bean 的定义信息 BeanDefinition 执行修改操作。
    • BeanPostProcessor,也是 Spring 提供的扩展机制,不过 BeanPostProcessor 是在 Bean 对象实例化之后修改 Bean 对象(但注册要在实例化前),也可以替换 Bean 对象。这部分与后面要实现的 AOP 有着密切的关系。
  • 同时如果只是添加这两个接口,不做任何包装,那么对于使用者来说还是非常麻烦的。我们希望于开发 Spring 的上下文操作类,把相应的 XML 加载 、注册、实例化以及新增的修改和扩展都融合进去,让 Spring 可以自动扫描到我们的新增服务,便于用户使用。

Spring 应用上下文和对Bean对象扩展机制的类关系

image-20230208232154424

  • 在整个类图中主要体现出来的是关于 Spring 应用上下文以及对 Bean 对象扩展机制的实现。
  • 以继承了 ListableBeanFactory 接口的 ApplicationContext 接口开始,扩展出一系列应用上下文的抽象实现类,并最终完成 ClassPathXmlApplicationContext 类的实现。而这个类就是最后交给用户使用的类。
  • 同时在实现应用上下文的过程中,通过定义接口:BeanFactoryPostProcessorBeanPostProcessor 两个接口,把关于对 Bean 的扩展机制串联进去了。

设计

手写Spring-ApplicationContext

工程

BeanPostProcessor

如果我们想在 Spring 容器中完成 bean 实例化、配置以及其他初始化方法前后要添加一些自己逻辑处理。我们需要定义一个或多个 BeanPostProcessor 接口实现类,然后注册到 Spring IoC 容器中。它本身也是属于一种 Bean,可以注册到 Spring IoC ,通过 BeanFactory 的 getBeanOfType() 获取 bean 后置处理器集合

public interface BeanPostProcessor {
/**
* 实例化、依赖注入完毕,在调用显示的初始化之前完成一些定制的初始化任务
*/
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
/**
* 实例化、依赖注入、初始化完毕时执行
*/
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

由API可以看出:

  1. 后置处理器的postProcessorBeforeInitailization方法是在 bean 实例化,依赖注入之后及自定义初始化方法(例如:配置文件中bean标签添加init-method属性指定Java类中初始化方法、@PostConstruct注解指定初始化方法,Java类实现InitailztingBean接口)之前调用
  2. 后置处理器的 postProcessorAfterInitailization 方法是在bean实例化、依赖注入及自定义初始化方法之后调用

一个后置处理器的 xml 写法:

<?xml version="1.0" encoding="UTF-8"?>
<beans>

<!-- 定义一个bean -->
<bean id="narCodeService" class="com.test.service.impl.NarCodeServiceImpl">
</bean>
<bean id="beanLifecycle" class="com.test.spring.BeanLifecycle" init-method="init" destroy-method="close">
<property name="name" value="张三"></property>
<property name="sex" value="男"></property>
</bean>

<!-- Spring后置处理器 -->
<bean id="postProcessor" class="com.test.spring.PostProcessor"/>
</beans>

注意

BeanFactory 和 ApplicationContext 两个容器对待bean的后置处理器稍微有些不同。

ApplicationContext 容器会自动检测 Spring 配置文件中那些 bean (因为那些 bean 所对应的Java类实现了 BeanPostProcessor 接口),并自动把它们注册为后置处理器。在创建 bean 过程中调用它们,所以部署一个后置处理器跟普通的bean没有什么太大区别。

BeanFactory容器注册bean后置处理器时必须通过代码显示的注册,在IoC容器继承体系中的ConfigurableBeanFactory接口中定义了注册方法

ConfigurableBeanFactory 接口

/**
* beanFactory 扩展子接口
* 可获取 BeanPostProcessor 后处理器、BeanClassLoader 类加载器等的一个配置化接口
* @author BanTanger 半糖
* @Date 2023/2/7 21:59
*/
public interface ConfigurableBeanFactory extends SingletonBeanRegistry, HierarchicalBeanFactory {

/**
* 单例作用域标识
*/
String SCOPE_SINGLETON = "singleton";

/**
* 原型作用域标识
*/
String SCOPE_PROTOTYPE = "prototype";

/**
* 添加处理 bean 的后处理器
* BeanFactory 容器注册 bean 后处理器必须通过代码显式注册,此为注册方法。
* @param beanPostProcessor
*/
void addBeanPostProcessor(BeanPostProcessor beanPostProcessor);

}

Spring 调用多个 BeanPostProcessor

我们可以在 Spring 配置文件中添加多个 BeanPostProcessor(后置处理器) 接口实现类,在默认情况下Spring 容器会根据后置处理器的定义顺序来依次调用。

Spring配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="narCodeService" class="com.test.service.impl.NarCodeServiceImpl"/>
<bean id="postProcessor" class="com.test.spring.PostProcessor"/>
<bean id="postProcessorB" class="com.test.spring.PostProcessorB"/>
</beans>

postProcessor、postProcessorB 两个 BeanPostProcessor 的实现类(以 ApplicationContext 容器举例),按照 xml 的书写顺序依次注册执行,先 postProcessor 后 postProcessorB

当然 Spring 也支持通过 order 指定后置处理器调用顺序(需要 Order 接口,当前并没有书写此逻辑),通过让BeanPostProcessor接口实现类实现Ordered接口getOrder方法,该方法返回一整数,默认值为 0,优先级最高,值越大优先级越低

BeanFactoryPostProcessor

Spring 中 BeanFactoryPostProcessor 和 BeanPostProcessor 都是 Spring 初始化 bean 时对外暴露的扩展接口,两个接口名字听起来很像,但实际作用和使用场景却不同

Spring IoC 容器允许 BeanFactoryPostProcessor 在容器实例化之前读取 Bean 的定义(也称配置元数据),并可以修改它们。可定义多个 BeanFactoryPostProcessor ,通过设置 order 属性(需要 Order 接口,当前暂时没书写此逻辑)来确定各个BeanFactoryPostProcessor执行顺序。

注册一个 BeanFactoryPostProcessor 实例需要定义一个 Java 类来实现 BeanFactoryPostProcessor 接口,并重写该接口的 postProcessorBeanFactory 方法。通过 beanFactory 可以获取 bean 的定义信息,并可以修改 bean 的定义信息。这点是和 BeanPostProcessor 最大区别.

/**
* 允许自定义修改 BeanDefinition 属性信息
* 提供修改 BeanDefinition 属性的机制,生命周期:在所有的 BeanDefinition 加载完成后,实例化 Bean 对象之前
* @author BanTanger 半糖
* @Date 2023/2/7 23:38
*/
public interface BeanFactoryPostProcessor {

/**
* 在所有的 BeanDefinition 加载完成后,实例化 Bean 对象之前,提供修改 BeanDefinition 属性的机制
*
* @param beanFactory
* @throws BeansException
*/
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;

}

核心方法 refresh 实现

AbstractApplicationContext,只实现核心功能 refresh()

  • AbstractApplicationContext 继承 DefaultResourceLoader 是为了处理 spring.xml 配置资源的加载。
  • 之后是在 refresh() 定义实现过程
  • 另外把定义出来的抽象方法,refreshBeanFactory()、getBeanFactory() 由后面的继承此抽象类的其他抽象类实现。

抽象类 AbstractRefreshableApplicationContext 实现 refresh() 定义的模板方法:

:warning:

这里的 beanFactory 算是一个非常精妙的操作了,注意到他的创建在 AbstractRefreshableApplicationContext 而不是 AbstractApplicationContext 了吗?

按照我们一般人写逻辑,直接把工厂的创建放在和调用一个位置了。因为理解起来直观呀!但是代码整体不美观,且功能耦合。

Spring 将工厂的创建放在另一个抽象类中,且创建的是最基层的 beanFactroy —— DefaultListableBeanFactory。它拥有注册和获取 Bean 两个最基本的方法,也是 Bean 工厂最本质的功能。在这个抽象类里,读取配置文件,注册 Bean,并将这个注册好的 Factroy 放入内存中供 refresh 调用。而在 refresh 里,又通过向上转型的方式,将 beanFactroy 升级成 ConfigurableBeanFactory 可配置工厂,让这个最基础的工厂具有获取添加后置处理器的功能。

我们可以从 Spring 框架学会一点,最基础最核心最本质的功能在最顶级接口定义,实现类却位于最底层,中间都由接口不断的继承。这样的好处是实现类可以通过先上转型的方式获取到更多更全的内容。一方面让各个接口各司其职,不让代码耦合写死混乱;另一方面可维护性、可扩展性高,只要书写好文档说明,代码迭代的就会非常轻松。例如面向 Spring 所开发的 BeanFactory 系列组件、以及面向用户所设计的 ConfigurableBeanFactory 可配置工厂。

测试

@Test
public void test_BeanFactoryPostProcessorAndBeanPostProcessor_useContext() {
// 1. 初始化 BeanFactory
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:springPostProcessor.xml");

// 2. 获取 Bean 对象调用方法
UserService userService = applicationContext.getBean("userService", UserService.class);
String result = userService.queryUserInfo();
System.out.println("测试结果:" + result);
}

ApplicationContext 功能

  1. ApplicationEventPublisher:Ioc 事件派发器
  2. MessageSource:国际化解析器
  3. BeanFactory:Bean 工厂 –> 组合自动装配
  4. 资源解析

是用户能看见的 Bean 工厂,但 ApplicationContext 只是接口,我们都是调用其子类的方法来完成需求

流程分析

应用上下文容器的执行细节

  1. 启动项目,Spring 加载 applicationContext 应用上下文容器,进入 refresh() 核心步骤
  2. 创建 BeanFactory,读取 XML 配置文件(集成在 ApplicationContext)
    1. 通过 refresh() 中的 refreshBeanFactory() 将创建 Bean 工厂 (创建的是默认最基础工厂 DefaultListableBeanFactory 其具有 注册 bean 和 获取 bean 的功能) 和读取 xml 资源文件
    2. 指定资源路径供 Spring IoC 中的组件 XmlBeanDefinitionReader 读取信息将其加载至 BeanDefinition 。注册好的 beanFactory 放入内存里
  3. 提升 beanFactory ,通过自动转换的方式,由 DefaultListableBeanFactory 升级为 ConfigurableListableBeanFactory ,让工厂多了可配置工厂接口定义的方法:预先实例化单例 bean、增添后置处理器 addBeanPostProcessor
  4. 在 bean 实例化之前,Spring 注册 PostProcessor 后置处理器
    1. invokeBeanFactoryPostProcessors 通过 getBeanOfType 获取用户自定义的所有 MyBeanFactoryPostProcessors 集合(因为 BeanFactoryPostProcessor 本质也是 bean 的一种)从而修改 beanDefinition 配置元数据
    2. registerBeanPostProcessors 通过 getBeanOfType 获取用户自定义的所有 MyBeanPostProcessors 集合
  5. beanFactory.preInstantiateSingletons() 预处理实例化 XML 书写的所有 Bean

应用上下文容器完成了数据的读取,创建,bean对象的实例化

创建 Bean 的执行细节

  1. bean 通过 Cgilb(字节码增强) 或者 JDK 自带的手段(反射手段)和 BeanDefinition 中所定义的配置元数据实例化 Bean 对象
  2. 往实例化 Bean 对象填充属性,如果是引用,递归的获取引用的 Bean 实例
  3. 执行 BeanPostProcessor 后置处理器的逻辑,在初始化之前需要做操作 Before
  4. 初始化 Bean,目前未实现,为空逻辑
  5. 执行 BeanPostProcessor 后置处理器的逻辑,在初始化之后需要做操作 After
  6. 将注册好的 Bean 存入单例缓存