Spring中Bean的加载与SpringBoot的初始化流程详解

前言

一直对它们之间的关系感到好奇,SpringBoot既然是Spring的封装,那么SpringBoot在初始化时应该也会有Bean的加载,那么是在何时进行加载的呢?

第一章 Spring中Bean的一些简单概念

1.1 SpingIOC简介

Spring启动时去读取应用程序提供的Bean配置信息,并在Spring容器中生成相应的Bean定义注册表,然后根据注册表去实例化Bean,装配好Bean之间的依赖关系,为上层提供准备就绪的运行环境.

Spring提供一个配置文件描述Bean与Bean之间的依赖关系,利用java语言的反射功能实例化Bean,并建立Bean之间的依赖关系.

Spring中Bean的加载与SpringBoot的初始化流程详解

1.2 BeanFactory

BeanFactory是接口,提供了IOC容器最基本的形式,给具体的IOC容器的实现提供了规范。

1.2.1 BeanDefinition

主要用来描述Bean的定义,Spring在启动时会将Xml或者注解里Bean的定义解析成Spring内部的BeanDefinition.

beanClass保存bean的class属性,scop保存bean是否单例,abstractFlag保存该bean是否抽象,lazyInit保存是否延迟初始化,autowireMode保存是否自动装配,等等等

1 public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor
 2          implements BeanDefinition, Cloneable {
 3      private volatile Object beanClass;
 4      private String scope = SCOPE_DEFAULT;
 5      private boolean abstractFlag = false;
 6      private boolean lazyInit = false;
 7      private int autowireMode = AUTOWIRE_NO;
 8      private int dependencyCheck = DEPENDENCY_CHECK_NONE;
 9      private String[] dependsOn;
10      private ConstructorArgumentValues constructorArgumentValues;
11      private MutablePropertyValues propertyValues;
12      private String factoryBeanName;
13      private String factoryMethodName;
14      private String initMethodName;
15      private String destroyMethodName;
16 }

1.2.2 BeanDefinitionRegistry

registerBeanDefinition方法主要是将BeanDefinition注册到BeanFactory接口的实现类DefaultListableBeanFacory中的beanDefinitionMap中。

1 private final Map beanDefinitionMap = new ConcurrentHashMap<>(256);

1.2.3 BeanFactory结构图

ListableBeanFactory该接口定义了访问容器中Bean的若干方法,如查看Bean的个数,获取某一类型Bean的配置名,查看容器中是否包括某一Bean等方法.

HierarchicalBeanFactory是父子级联的IOC容器接口,子容器可以通过接口方法访问父容器,通过HierarchicalBeanFactory接口SpringIOC可以建立父子层级关联的IOC层级体系,子容器可以访问父容器的Bean,父容器不能访问子容器的Bean,比如展现层的Bean位于子容器中而业务层和持久层的Bean位于父容器的Bean.

  • ConfigurableBeanFactory:增强了IOC接口的可定制性,定义了设置类装载器,属性遍历器,以及属性初始化后置处理器等方法.

  • AutowireCapableBeanFactory:定义了将容器中的Bean按某种规则,按名字匹配,按类型匹配等.

  • SingletonBeanRegistry:允许在运行期间向容器注册SingletonBean实例的方法.

通过这些接口也证明了BeanFactory的体系也确实提供了IOC的基础及依赖注入和Bean的装载等功能.

Spring中Bean的加载与SpringBoot的初始化流程详解

1.3 ApplicationContext

由于BeanFactory的功能还不够强大,于是Spring在BeanFactory的基础上还设计了一个更为高级的接口即ApplicationContext,它是BeanFactory的子接口之一.在我们使用SpringIOC容器时,大部分都是context的实现类。

Spring中Bean的加载与SpringBoot的初始化流程详解

我理解着就是BeanFactory只提供IOC,ApplicationContext还提供很多别的功能。

Spring中Bean的加载与SpringBoot的初始化流程详解

第二章 SpringBoot的初始化流程

1 @SpringBootApplication
2 public class RepApplication {
3      public static void main(String[] args) {
4          //要理解的SpringApplication
5      SpringApplication.run(RepApplication.class, args);
6      }
7 }

SpringApplication的run分为两个阶段,即new SpringApplication()时的执行构造函数的准备阶段,和run时的运行阶段。

1 public static ConfigurableApplicationContext run(Class[] primarySources,
2              String[] args) {
3          return new SpringApplication(primarySources).run(args);
4      }

2.1 准备阶段

在准备阶段会:

  • 配置SpringBean的来源
  • 推断web应用类型
  • 加载应用上下文初始器
  • 加载应用事件监听器
1 public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
 2          this.resourceLoader = resourceLoader;
 3          Assert.notNull(primarySources, "PrimarySources must not be null");
 4          this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
 5          //推断web应用类型
 6          this.webApplicationType = WebApplicationType.deduceFromClasspath();
 7          //加载应用上下文初始化器
 8          setInitializers((Collection) getSpringFactoriesInstances(
 9                  ApplicationContextInitializer.class));
10          //加载应用事件监听器
11          setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
12          this.mainApplicationClass = deduceMainApplicationClass();
13      }

2.2 运行阶段

  • 加载:SpringApplication获得监听器
  • 运行:SpringApplication运行监听器
  • 监听:SpringBoot事件、Spring事件
  • 创建:应用上下文、Enviroment、其它(不重要),应用上下文创建后会被应用上下文初始化器初始化,Enviroment是抽象的环境对象。
  • 失败:故障分析报告。
  • 回调:CommandLineRunner、ApplicationRunner
1 public ConfigurableApplicationContext run(String... args) {
 2          StopWatch stopWatch = new StopWatch();
 3          stopWatch.start();
 4          ConfigurableApplicationContext context = null;
 5          Collection exceptionReporters = new ArrayList<>();
 6          configureHeadlessProperty();
 7          //获得监听器
 8          SpringApplicationRunListeners listeners = getRunListeners(args);
 9          //运行监听器
10          listeners.starting();
11          try {
12              //应用上下文
13              ApplicationArguments applicationArguments = new DefaultApplicationArguments(
14                      args);
15              //环境
16              ConfigurableEnvironment environment = prepareEnvironment(listeners,
17                      applicationArguments);
18              configureIgnoreBeanInfo(environment);
19              Banner printedBanner = printBanner(environment);
20              //依据不同的配置加载不同的ApplicationContext
21              context = createApplicationContext();
22              exceptionReporters = getSpringFactoriesInstances(
23                      SpringBootExceptionReporter.class,
24                      new Class[] { ConfigurableApplicationContext.class }, context);
25              prepareContext(context, environment, listeners, applicationArguments,
26                      printedBanner);
27              refreshContext(context);
28              afterRefresh(context, applicationArguments);
29              stopWatch.stop();
30              if (this.logStartupInfo) {
31                  new StartupInfoLogger(this.mainApplicationClass)
32                      .logStarted(getApplicationLog(), stopWatch);
33              }
34              listeners.started(context);
35              callRunners(context, applicationArguments);
36          }
37          catch (Throwable ex) {
38              handleRunFailure(context, ex, exceptionReporters, listeners);
39              throw new IllegalStateException(ex);
40          }
41
42          try {
43              listeners.running(context);
44          }
45          catch (Throwable ex) {
46              handleRunFailure(context, ex, exceptionReporters, null);
47              throw new IllegalStateException(ex);
48          }
49          return context;
50      }

2.2.1 监听器分析

这个是看了源码后的个人理解,不保证一定正确,只提供一定的参考。

在准备阶段加载实现了ApplicationListener的监听器。

Spring中Bean的加载与SpringBoot的初始化流程详解

然后在运行阶段调用了starting()方法。

1 //获得监听器
2 SpringApplicationRunListeners listeners = getRunListeners(args);
3 //运行监听器
4 listeners.starting();

下面是listeners的源码,可以看到它的每一个方法,都对应SpringBoot的一个阶段,这表明每到对应的阶段,都要广播对应的事件。

Spring中Bean的加载与SpringBoot的初始化流程详解
1 class SpringApplicationRunListeners {
 2      private final Log log;
 3      private final List listeners;
 4      SpringApplicationRunListeners(Log log,
 5              Collectionextends SpringApplicationRunListener> listeners) {
 6          this.log = log;
 7          this.listeners = new ArrayList<>(listeners);
 8      }
 9
10      public void starting() {
11          for (SpringApplicationRunListener listener : this.listeners) {
12              listener.starting();
13          }
14      }
15
16      public void environmentPrepared(ConfigurableEnvironment environment) {
17          for (SpringApplicationRunListener listener : this.listeners) {
18              listener.environmentPrepared(environment);
19          }
20      }
21
22      public void contextPrepared(ConfigurableApplicationContext context) {
23          for (SpringApplicationRunListener listener : this.listeners) {
24              listener.contextPrepared(context);
25          }
26      }
27
28      public void contextLoaded(ConfigurableApplicationContext context) {
29          for (SpringApplicationRunListener listener : this.listeners) {
30              listener.contextLoaded(context);
31          }
32      }
33
34      public void started(ConfigurableApplicationContext context) {
35          for (SpringApplicationRunListener listener : this.listeners) {
36              listener.started(context);
37          }
38      }
39      //省略...

40 }

那么问题来了,广播事件后,事件是怎么被监听到的呢?我们打开listener.environmentPrepared(environment)的源码,发现其调用了initialMulticaster进行了事件广播

1 @Override
2      public void environmentPrepared(ConfigurableEnvironment environment) {
3          this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
4                  this.application, this.args, environment));
5      }

广播的代码如下,看了一下感觉大意就是找到根事件匹配的监听器,然后调用线程池去执行对应的触发函数。

1 @Override
 2      public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
 3          ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
 4          for (final ApplicationListener listener : getApplicationListeners(event, type)) {
 5              Executor executor = getTaskExecutor();
 6              if (executor != null) {
 7                  executor.execute(() -> invokeListener(listener, event));
 8              }
 9              else {
10                  invokeListener(listener, event);
11              }
12          }
13      }

2.2.2 refreshContext

再往下最核心的是refreshContext方法,一直点进去可以看到如下:

  • prepareRefresh:完成配置之类的解析,设置Spring的状态,初始化属性源信息,验证环境信息中必须存在的属性.

  • ConfigurableListableBeanFactory:是用来获取beanFactory的实例的(第一张也写过BeanFactory负责bean的加载与获取)。

  • PrepareBeanFactory:对beanFactory进行相关的设置,为后续的使用做准备,包括设置classLoader用来加载Bean,设置表达式解析器等等.

  • postProcessBeanFactory:是用于在BeanFactory设置之后进行后续的BeanFactory的操作.

  • invokeBeanFactoryPostProcessors:点进去发现调用了如下方法,点进去之后发现逻辑相当复杂,主要调用工厂后处理器,调用Bean标签,扫描Bean文件,并解析成一个个的Bean,这时候这些Bean是被加载进了Spirng容器当中,这里涉及了各种类,我们在这里主要说一下ConfigurationClassParser,主要是解析Bean的类.该方法会对带有@configuration,@import,@bean,以及@SpringBootApplication等标签的Bean进行解析,

  • registerBeanPostProcessors:会从Spring容器中找出实现BeanPostProcessors接口的Bean,并设置到BeanFactory的属性之中,之后Bean实例化时会调用BeanProcessor,也就是Bean的后置处理器.会和AOP比较相关.

  • initMessageSource:初始化消息源(这个自己推断的)

  • initApplicationEventMuticaster:初始化事件广播器
  • onRefresh:是一个模板方法,不同的Spring容器会重写它做不同的事情.比如web程序的容器,会调用create..方法去创建内置的servlet容器.

  • registerListeners:注册事件监听器

  • finishBeanFactoryInitialization:会实例化BeanFactory中已被注册但未被实例化的所有实例,懒加载是不需要被实例化的.前面的invokeBeanFactoryPostProcessors方法中根据各种注解解析出来的Bean在这个时候都会被初始化,同时初始化过程中的各种PostProcessor就会开始起作用了.

  • finishRefresh:会做初始化生命周期处理器相关的事情.

1 @Override
 2      public void refresh() throws BeansException, IllegalStateException {
 3          synchronized (this.startupShutdownMonitor) {
 4              // Prepare this context for refreshing.

 5              prepareRefresh();
 6
 7              // Tell the subclass to refresh the internal bean factory.

 8              ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
 9
10              // Prepare the bean factory for use in this context.

11              prepareBeanFactory(beanFactory);
12
13              try {
14                  // Allows post-processing of the bean factory in context subclasses.

15                  postProcessBeanFactory(beanFactory);
16
17                  // Invoke factory processors registered as beans in the context.

18                  invokeBeanFactoryPostProcessors(beanFactory);
19
20                  // Register bean processors that intercept bean creation.

21                  registerBeanPostProcessors(beanFactory);
22
23                  // Initialize message source for this context.

24                  initMessageSource();
25
26                  // Initialize event multicaster for this context.

27                  initApplicationEventMulticaster();
28
29                  // Initialize other special beans in specific context subclasses.

30                  onRefresh();
31
32                  // Check for listener beans and register them.

33                  registerListeners();
34
35                  // Instantiate all remaining (non-lazy-init) singletons.

36                  finishBeanFactoryInitialization(beanFactory);
37
38                  // Last step: publish corresponding event.

39                  finishRefresh();
40              }
41
42              catch (BeansException ex) {
43                  if (logger.isWarnEnabled()) {
44                      logger.warn("Exception encountered during context initialization - " +
45                      "cancelling refresh attempt: " + ex);
46                  }
47
48                  // Destroy already created singletons to avoid dangling resources.

49                  destroyBeans();
50
51                  // Reset 'active' flag.

52                  cancelRefresh(ex);
53
54                  // Propagate exception to caller.

55                  throw ex;
56              }
57
58              finally {
59                  // Reset common introspection caches in Spring's core, since we
60                  // might not ever need metadata for singleton beans anymore...

61                  resetCommonCaches();
62              }
63          }
64      }

当上面的代码执行完毕后,返回上层代码,后面是注册钩子,这钩子是希望开发者能结合自己的实际需求扩展出一些在Spring容器关闭时的行为.

1 private void refreshContext(ConfigurableApplicationContext context) {
 2          refresh(context);
 3          if (this.registerShutdownHook) {
 4              try {
 5                  context.registerShutdownHook();
 6              }
 7              catch (AccessControlException ex) {
 8                  // Not allowed in some environments.

 9              }
10          }
11      }

继续返回上层代码,可以看到afterRefresh方法它的方法体是空的, 也就说明Spring框架考虑了扩展性,留了很多的口子,让大家在框架层面继承很多的模块并去做自定义的实现

1 protected void afterRefresh(ConfigurableApplicationContext context,
2              ApplicationArguments args) {
3      }

2.3 总结

总的来说,SpringBoot加载的Bean的时机为,点进一开始的run方法,层层递进后,由

1 refreshContext(context);

进行了bean的加载,更详细的话,那就层层递进点进去,是在如下方法进行了bean的加载。

1 invokeBeanFactoryPostProcessors(beanFactory);

转自:http://www.cppcns.com/ruanjian/java/438670.html

Original: https://www.cnblogs.com/fnlingnzb-learner/p/16427745.html
Author: Boblim
Title: Spring中Bean的加载与SpringBoot的初始化流程详解

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/541612/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 数据结构与算法

    数据结构: 数据结构(英语:data structure)是计算机中存储、组织数据的方式。数据结构是一种具有一定逻辑关系,在计算机中应用某种存储结构,并且封装了相应操作的数据元素集…

    Java 2023年6月5日
    0134
  • 数据结构学习笔记

    数据结构学习笔记 数据结构=个体的存储+个体的关系存储 算法=对存储数据的操作 数据结构是专门研究数据存储的问题 狭义的算法是与数据的存储方式密切相关;广义的算法是与数据的存储方式…

    Java 2023年6月7日
    077
  • nginx访问控制

    一、基于IP的访问控制 1、http_access_module a、配置语法 Syntax: allow address | CIDR | unix: | all; Defaul…

    Java 2023年5月30日
    089
  • 聊聊redis的主从复制吧

    聊聊基础概念 主从复制与主从替换 主从复制不同于主从替换,主从复制是正常情况下主节点同步数据到从节点;主从替换是主节点挂了之后,把从节点替换为主节点; 从节点存在的意义:备份主节点…

    Java 2023年6月8日
    085
  • 限流常规设计和实例

    限流算法 计数器限流 固定窗口 滑动窗口 桶限流 令牌桶 漏桶 计数器 计数器限流可以分为: 固定窗口 滑动窗口 固定窗口 固定窗口计数器限流简单明了,就是限制单位之间内的请求数,…

    Java 2023年6月14日
    052
  • Mysql优化

    1、选取最适用的字段属性MySQL可以很好的支持大数据量的存取,但是一般说来,数据库中的表越小,在它上面执行的查询也就会越快。因此,在创建表的时候,为了获得更好的性能,我们可以将表…

    Java 2023年6月9日
    081
  • SpringAMQP的使用

    SpringAMQP是基于RabbitMQ封装的一套模板,并且还利用SpringBoot对其实现了自动装配,使用起来非常方便。 SpringAmqp的官方地址:https://sp…

    Java 2023年6月7日
    082
  • 用无感知的方式为你的数据加上一层缓存

    前言 本篇文章会介绍一个我自己写的库,库地址在这里,主要作用是提供一个注解,在你方法上使用这个注解,库提供的功能会帮你把数据自动缓存起来,下次再调用这个方法只要入参是一致的则直接会…

    Java 2023年6月5日
    089
  • SpringMVC工作流程

    SpringMVC 1、MVC Model(模型)+View(视图)+Controller(控制器),通过将业务逻辑、数据、显示分离来组织代码。 Service层(处理业务)、Da…

    Java 2023年6月5日
    096
  • springboot使用log4j2代替内置log4j

    前言 log4j是apache实现的一个开源日志组件 logback同样是由log4j的作者设计完成的,拥有更好的特性,用来取代log4j的一个日志框架,是slf4j的原生实现 l…

    Java 2023年5月30日
    078
  • echarts datazoom 显示的位置设置

    设置grid属性里的bottom var eleCurves = document.getElementById(‘eleCourtsBeforeCurves’); var ele…

    Java 2023年6月8日
    0128
  • IDEA中提示配置jdk1.8

    问题描述:运行Java Web项目时,IDEA中提示:Warning:java: 源值1.5已过时, 将在未来所有发行版中删除 解决方法:1. 打开【File】—【Project …

    Java 2023年5月30日
    068
  • Spring Boot + Mybatis 实现动态数据源

    动态数据源 在很多具体应用场景的时候,我们需要用到动态数据源的情况,比如多租户的场景,系统登录时需要根据用户信息切换到用户对应的数据库。又比如业务A要访问A数据库,业务B要访问B数…

    Java 2023年5月30日
    089
  • 根据map的value进行排序

    1、将Map放入List中 List> entryList = new ArrayList(result Map.entrySet()); 2、利用Collections的s…

    Java 2023年6月5日
    089
  • easyUI 添加排序到datagrid

    @author YHC 这个示例展示如何排序datagrid通过点击列表头. 查看 Demo 在datagrid的所有columns 可以通过点击列表头排序,你可以定义哪行可以排序…

    Java 2023年5月29日
    089
  • 琐碎的想法(一)代码“优雅”的含义

    优雅的含义 代码优雅曾是翻译而来的,优雅这一个词语源于单词elegant。 elegant有三种含义,优美的(形容举止),精美的(形容物品),简明的。 形容代码上,应该包含了后两种…

    Java 2023年6月8日
    084
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球