Spring Boot源码分析八:ApplicationContext初始化

前言

本文是作者写关于Spring源码的第一篇文章,作者水平有限,所有的源码文章仅限用作个人学习记录。文中如有错误欢迎各位留言指正。

之前分析到Spring Boot项目的run方法中的banner信息的打印的方法。下面接着阅读run方法的代码。接下来要看的方法是比较重要的一步,就是创建应用上下文了,即创建run方法最开始定义的ConfigurableApplicationContext对象的实例。

run

java 复制代码
public ConfigurableApplicationContext run(String... args) {
// 为了计时用的,老版本和新版本不一样
   long startTime = System.nanoTime();
   // 初始化一个引导器的上下文,这是属于Spring Boot的上下文。后边还有一个Spring的上下文。apach好喜欢context这个东西,证明写框架这个context是真的好用。
   DefaultBootstrapContext bootstrapContext = createBootstrapContext();
   // 这是Spring的上下文,在这里定义,在下面进行的初始化
   ConfigurableApplicationContext context = null;
   // 配置一个系统属性
   configureHeadlessProperty();
   // 获取配置文件的监听器 重点 也是扩展点,凡是读取配置文件的地方都是扩展点,因为配置在配置文件中的initializer、listener都会在某个阶段被调用
   SpringApplicationRunListeners listeners = getRunListeners(args);
   // 调用监听器发送启动事件,这里可以通过自定义监听器消费这个事件,处理自己的逻辑
   listeners.starting(bootstrapContext, this.mainApplicationClass);
   try {
   // 解析命令行参数 将其封装成一个ApplicationArguments,这个类的变量name被设置成commandLineArgs字符串,变量source是解析args封装的CommandLineArgs对象。
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      // 环境预处理
      ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
      // 配置忽略beanInfo
      configureIgnoreBeanInfo(environment);
      // 打印banner信息
      Banner printedBanner = printBanner(environment);
      context = createApplicationContext();
      context.setApplicationStartup(this.applicationStartup);
      prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
      refreshContext(context);
      afterRefresh(context, applicationArguments);
      Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
      }
      listeners.started(context, timeTakenToStartup);
      callRunners(context, applicationArguments);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, listeners);
      throw new IllegalStateException(ex);
   }
   try {
      Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
      listeners.ready(context, timeTakenToReady);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, null);
      throw new IllegalStateException(ex);
   }
   return context;
}

createApplicationContext()

此方法主要是用于创建一个应用程序上下文(ApplicationContext),它是一个用于管理Bean的容器。通过该容器,可以实现Bean的实例化、初始化、依赖注入以及销毁等操作。在Spring框架中,ApplicationContext是核心接口之一,它提供了许多有用的功能,例如:

  • 管理Bean的生命周期:自动实例化、初始化、销毁Bean;

  • 解析Bean之间的依赖关系:自动装配Bean的依赖;

  • 提供国际化支持:根据不同的语言环境加载相应的资源文件;

  • 支持AOP:可以实现面向切面编程;

  • 支持事件传播:可以实现事件的发布和订阅等。

因此,该函数在Spring框架中具有重要的作用,往往是在应用程序的初始化阶段被调用,用于创建和管理Bean的容器。

本次只看他的初始化过程,不对他的功能做过多的分析。

这里的代码体现出了一个设计模式:工厂设计模式。 通过SpringApplication类本身实例化的时候,初始化的一个ApplicationContextFactory对象创建了ApplicationContext对象实例。

java 复制代码
...
// 这里采用的是他默认的实现类 DefaultApplicationContextFactory
private ApplicationContextFactory applicationContextFactory = ApplicationContextFactory.DEFAULT;
...

protected ConfigurableApplicationContext createApplicationContext() {
    return this.applicationContextFactory.create(this.webApplicationType);
}

所以这里的applicationContextFactory.create方法就是DefaultApplicationContextFactory的create方法。这里create方法传入的参数当前应用的类型,根据前面分析,当前应用的类型是SERVLET。所以这里的this.webApplicationType的值就是SERVLET。

create

这里是调用了getFromSpringFactories方法,传入了两个函数对象,此方法没有过多的内容,进入下一个方法进行查看。

java 复制代码
public ConfigurableApplicationContext create(WebApplicationType webApplicationType) {
    try {
       return getFromSpringFactories(webApplicationType, ApplicationContextFactory::create,
             AnnotationConfigApplicationContext::new);
    }
    catch (Exception ex) {
       throw new IllegalStateException("Unable create a default ApplicationContext instance, "
             + "you may need a custom ApplicationContextFactory", ex);
    }
}

getFromSpringFactories

该方法首先我们看到有一个for循环,在for循环的循环条件中读取了配置文件的信息,(这也是spring框架经常做的事情,在循环条件中做一些读取数据或者赋值的操作,个人觉得这样的写法还是有点...)。 SpringFactoriesLoader.loadFactories(ApplicationContextFactory.class, getClass().getClassLoader())这个方法就不再做过多的说明了,在前面也遇到很多次了,简单的说就是从项目的类路径下读取了spring.factories配置文件中的ApplicationContextFactory类型的配置类集合。 当前环境配置文件中配置的信息如下图:

java 复制代码
private <T> T getFromSpringFactories(WebApplicationType webApplicationType,
       BiFunction<ApplicationContextFactory, WebApplicationType, T> action, Supplier<T> defaultResult) {
    for (ApplicationContextFactory candidate : SpringFactoriesLoader.loadFactories(ApplicationContextFactory.class,
          getClass().getClassLoader())) {
       T result = action.apply(candidate, webApplicationType);
       if (result != null) {
          return result;
       }
    }
    return (defaultResult != null) ? defaultResult.get() : null;
}

for循环的循环体就是调用了action函数方法,将配置文件配置的AppliactionContextFactory对象作为参数进行传递。 接下来会走到遍历对象的方法中: 当前对象判断环境是SERVLET所以不会通过该对象进行创建。

看第二个对象

当前是SERVLET环境所以会通过该对象进行创建。

创建的ApplicationContext的类型是AnnotationConfigServletWebServerApplicationContext

看一下这个对象的够着方法:

  • 初始化AnnotatedBeanDefinitionReader:AnnotatedBeanDefinitionReader是Spring框架中的一个类,它的主要职责是读取和处理类上的注解(如@Service, @Component等),并将这些注解信息转换成Bean定义。这样,Spring容器就能根据这些定义来创建和管理Bean。

  • 初始化ClassPathBeanDefinitionScanner:ClassPathBeanDefinitionScanner也是一个Spring框架的组件,它的功能是在类路径(classpath)下扫描指定包及其子包,寻找带有特定注解(如@Component, @Service等)的类,并将这些类注册为Bean定义到Spring容器中。

csharp 复制代码
public AnnotationConfigServletWebServerApplicationContext() {
    this.reader = new AnnotatedBeanDefinitionReader(this);
    this.scanner = new ClassPathBeanDefinitionScanner(this);
}

这里有点模糊在做一个说明:

  • AnnotatedBeanDefinitionReader主要用于直接处理已经明确的配置类(通常是那些通过@Configuration注解标记的类)。当你直接知道哪些类包含了需要被Spring管理的Bean定义时,可以使用这个读取器来注册这些类。它提供了更细粒度的控制,比如你可以选择性地排除或包含某些Bean的定义。

  • ClassPathBeanDefinitionScanner则更加自动化,它通过扫描指定的包路径来查找带有特定组件注解(如@Component, @Service, @Repository, @Controller等)的类,并自动将这些类作为Bean定义注册到Spring容器中。这种方式适用于大型项目,可以快速地批量注册Bean,无需显式地列出每一个Bean。

至此ConfigurableApplicationContext的初始化过程已经分析完成。那么

OK 今天先到这里吧。

See you next time :)

相关推荐
智慧老师6 分钟前
Spring基础分析13-Spring Security框架
java·后端·spring
lxyzcm7 分钟前
C++23新特性解析:[[assume]]属性
java·c++·spring boot·c++23
搬码后生仔1 小时前
asp.net core webapi项目中 在生产环境中 进不去swagger
chrome·后端·asp.net
迷糊的『迷』1 小时前
vue-axios+springboot实现文件流下载
vue.js·spring boot
凡人的AI工具箱1 小时前
每天40分玩转Django:Django国际化
数据库·人工智能·后端·python·django·sqlite
Lx3522 小时前
Pandas数据重命名:列名与索引为标题
后端·python·pandas
小池先生2 小时前
springboot启动不了 因一个spring-boot-starter-web底下的tomcat-embed-core依赖丢失
java·spring boot·后端
百罹鸟2 小时前
【vue高频面试题—场景篇】:实现一个实时更新的倒计时组件,如何确保倒计时在页面切换时能够正常暂停和恢复?
vue.js·后端·面试
苹果醋33 小时前
2020重新出发,MySql基础,MySql表数据操作
java·运维·spring boot·mysql·nginx
小蜗牛慢慢爬行3 小时前
如何在 Spring Boot 微服务中设置和管理多个数据库
java·数据库·spring boot·后端·微服务·架构·hibernate