Spring 源码分析——应用上下文

Spring 源码分析——应用上下文

本篇文章讨论 Spring 源码中的应用上下文 ApplicationContext 对象,以及 ApplicationContext 应用上下文如何控制 Bean 容器。

对应 Github 源码完整文档:https://github.com/TyCoding/mini-spring/tree/main/docs/ioc/07-application-context

引言

到此为止,我们已经讲了如何用 XML 配置文件加载 Bean 并装配 Bean 容器,以及 Bean 的实例化和初始化过程,那么再整体看一下 Bean 创建阶段的源码设计:

Spring 源码分析——应用上下文
img

Bean 容器在哪维护

在之前我们知道getBean()是获取一个 Bean 的入口,而获取单例 Bean 的入口是SingletonBeanRegistry接口:

Spring 源码分析——应用上下文
img

其中DefaultSingletonBeanRegistrySingletonBeanRegistry接口的默认实现,此实现类也是三级缓存管理单例 Bean 的核心,Spring 的源码如下:

Spring 源码分析——应用上下文
image-20230425140033757

通过调用SingletonBeanRegistry接口的getSingleton()函数可以从 Bean 单例容器中拿到一个单例实例。

Bean 创建过程

在之前我们重复强调过:

  1. 获取一个 Bean 的入口getBean()函数定义在BeanFactory接口中;
  2. 而从容器中拿到一个单例 Bean 的入口getSingleton()函数定义在SingletonBeanRegistry接口;
  3. 将这两者逻辑融合在一起的函数定义在AbstractAutowireCapableBeanFactory类中,此类才是实例化、初始化 Bean 的核心:
Spring 源码分析——应用上下文
img

ApplicationContext

上面回顾了 Bean 创建实例化、初始化过程,到此为止我们已经能从容器中拿到一个单例 Bean 了;但是,其实整个过程需要我们手动调用getBean()函数才能拿到一个 Bean 实例对象。

但是实际上 Spring 在启动容器时就应该把需要的 Bean 加载到了容器中(这里涉及到 Bean 的立即加载和延迟加载问题,后面讨论),因此 Spring 需要一个全局的控制,去扫描所有需要加载到容器中的 Bean,以及控制这些 Bean 什么时候实例化、初始化,而这些逻辑就交由 ApplicationContext 对象完成。

ApplicationContext 的初始化过程可以概括为:

  1. 加载和解析 XML 配置文件和注册配置类
  2. 创建和装配 Bean 实例
  3. 注册 Bean 处理器(如 AOP、Processor)
  4. 发布容器事件(某些 Event 时间监听器)
  5. 完成初始化工作,容器准备就绪

ApplicationContext 接口设计如下:

Spring 源码分析——应用上下文
img

注: 上面仅仅是在项目https://github.com/TyCoding/mini-spring 中的设计,实际上 ApplicationContext 接口有很多实现类,我们这里仅仅是讲解最基础的从 XML 文件加载 Bean 并通过 ApplicationContext 对象加载到容器对象中。

  • ApplicationContext 接口是应用上下文的父类的接口(ListableBeanFactory、HierarchicalBeanFactory 接口是 Spring 的容器扩展父类接口)
  • ConfigurableApplicationContext 接口定义一些额外的配置接口,所有的 ApplicationContext 实现类都需要直接或间接的实现 ConfigurableApplicationContext 接口
  • AbstractApplicationContext 实现了 ConfigurableApplicationContext 和 ApplicationContext 接口
  • AbstractRefreshableApplicationContext 类用于刷新加载 ApplicationContext 上下文中的 Bean 定义(提供了refresh()函数)
  • AbstractXmlApplicationContext 用于从 XML 配置文件加载 ApplicationContext,提供加载 XML 文件的默认实现,并在 refresh()函数执行时加载 Bean 定义
  • ClassPathXmlApplicationContext 用于从classspath:类路径下的 XML 文件中加载 ApplicationContext 对象

核心伪代码:

public interface ApplicationContext extends ListableBeanFactoryHierarchicalBeanFactoryResourceLoader {}

public interface ConfigurableApplicationContext extends ApplicationContext {
    void refresh() throws BeansException;
}

public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
    @Override
    public void refresh() throws BeansException {
        // 创建BeanFactory并初始化BeanDefinition
        refreshBeanFactory();
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();

        // 实例化Bean之前先加载BeanFactoryPostProcessor处理器
        invokeBeanFactoryPostProcessors(beanFactory);

        // BeanPostProcessor要在其他Bean实例化之前注册
        registerBeanPostProcessors(beanFactory);

        // 提前实例化单例Bean
        beanFactory.preInstantiateSingletons();
    }
}

public interface ConfigurableListableBeanFactory extends ListableBeanFactoryAutowireCapableBeanFactoryConfigurableBeanFactory {
    void preInstantiateSingletons() throws BeansException;
}

public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements ConfigurableListableBeanFactoryBeanDefinitionRegistry {
   @Override
    public void preInstantiateSingletons() throws BeansException {
        beanDefinitionMap.forEach((beanName, beanDefinition) -> {
            if (beanDefinition.isSingleton()) {
                getBean(beanName);
            }
        });
    }
}

注: 这里我们仅是通过最简洁的方式实现 ApplicationContext 中的refresh()函数,实际源码中设计很复杂。

我们可以简单的理解为refresh()函数主要完成了以下几个工作:

  1. 提前加载 BeanDefinition 数据和 BeanFactory 等数据
  2. 在实例化 Bean 之前注册 PostProcessor 对象(定制化 Bean 实例)
  3. 提前实例化 Bean(根据 BeanDefinition 数据调用getBean实例化 Bean)

通常通过 ClassPathXmlApplicationContext(基于 XML 配置文件)和 AnnotationConfigApplicationContext(注解配置)来加载 ApplicationContext 对象,其中reefresh函数会在容器初始化时自动调用。

注: 虽然 Spring 容器启动时会自动调用refresh()函数以提前加载 Bean 实例(这里 bean 至少是保证容器运行的一些必要的实例对象),但并不是所有 bean 都会被加载,Spring 会判断哪些 bean 是需要被提前实例化的,有些 bean 只有在被注入或者被调用时才会进行实例化,称为延迟实例化。

SpringBoot 项目中经常使用的@Component@Service注解标记 bean,默认情况下这些 bean 都不会被立即实例化,可以通过配置@Lazy(false)标记一个 bean 需要在容器初始化时立即被加载。

本章节完整的源码示例请参考:https://github.com/TyCoding/mini-spring/tree/main/docs/ioc/07-application-context

Spring 源码专栏

此专栏将从 Spring 源码角度整体分析 Spring 设计思路以及常见的面试题

配套作者的手写 Spring 的项目:https://github.com/TyCoding/mini-spring 。该项目中包含各个阶段的开发文档,有关 Spring 源码更详细的分析测试文档请查阅:https://github.com/TyCoding/mini-spring/tree/main/docs

联系我

  • 个人博客:http://tycoding.cn/
  • GitHub:https://github.com/tycoding
  • 微信公众号:程序员涂陌
  • QQ 交流群:866685601


原文始发于微信公众号(程序员涂陌):Spring 源码分析——应用上下文

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/145591.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!