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 :)

相关推荐
容若只如初见3 小时前
项目实战--Spring Boot + Minio文件切片上传下载
java·spring boot·后端
weixin_440401693 小时前
分布式锁——基于Redis分布式锁
java·数据库·spring boot·redis·分布式
码农爱java3 小时前
Spring Boot 中的监视器是什么?有什么作用?
java·spring boot·后端·面试·monitor·监视器
Apifox.4 小时前
什么是 HTTP POST 请求?初学者指南与示范
后端·http·学习方法·web
无名指的等待7124 小时前
SpringBoot实现图片添加水印(完整)
java·spring boot·后端
胡尚5 小时前
Ratf协议图解、Nacos CP集群源码分析
java·spring boot
三两肉6 小时前
如何使用缓存提升SpringBoot性能(EhCache和Redis方式)
spring boot·redis·缓存
甜甜圈的小饼干8 小时前
Spring Boot+Vue项目从零入手
vue.js·spring boot·后端
Mrceel8 小时前
java spring boot 单/多文件上传/下载
java·spring boot·vue
我曾遇到一束光9 小时前
Spring boot 更改启动LOGO
数据库·spring boot·后端