SpringBoot整个启动过程的分析

前言

前一篇分析了SpringBoot如何启动以及内置web容器,这篇我们一起看一下SpringBoot的整个启动过程,废话不多说,正文开始。

正文

一、SpringBoot的启动类是**application,以注解@SpringBootApplication注明。

@SpringBootApplication
public class CmsApplication {
 public static void main(String[] args) {
  SpringApplication.run(CmsApplication.class, args);
 }
}

SpringBootApplication注解是@Configuration,@EnableAutoConfiguration,@ComponentScan三个注解的集成,分别表示Springbean的配置bean,开启自动配置spring的上下文,组件扫描的路径,这也是为什么*application.java需要放在根路径的原因,这样@ComponentScan扫描的才是整个项目。

二、该启动类默认只有一个main方法,调用的是SpringApplication.run方法,下面我们来看一下SpringApplication这个类。

public static ConfigurableApplicationContext run(Object source, String... args) {
  return run(new Object[]{source}, args);
 }
...
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
  return (new SpringApplication(sources)).run(args);//sources为具体的CmsApplication.class类
 }
...

抽出其中两个直接调用的run方法,可以看出静态方法SpringApplication.run最终创建了一个SpringApplication,并运行其中run方法。

查看起构造方法:

public SpringApplication(Object... sources) {
  this.bannerMode = Mode.CONSOLE;
  this.logStartupInfo = true;
  this.addCommandLineProperties = true;
  this.headless = true;
  this.registerShutdownHook = true;
  this.additionalProfiles = new HashSet();
  this.initialize(sources);
 }
...

构造方法设置了基础值后调用initialize方法进行初始化,如下:

private void initialize(Object[] sources) {
  if (sources != null && sources.length > 0) {
   this.sources.addAll(Arrays.asList(sources));
  }
  this.webEnvironment = this.deduceWebEnvironment();
  this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
  this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
  this.mainApplicationClass = this.deduceMainApplicationClass();
 }
...

初始化方法主要做了几步:

1.将source放入SpringApplication的sources属性中管理,sources是一个LinkedHashSet(),这意味着我们可以同时创建多个自定义不重复的Application,但是目前只有一个。

2.判断是否是web程序(javax.servlet.Servletorg.springframework.web.context.ConfigurableWebApplicationContext都必须在类加载器中存在),并设置到webEnvironment属性中。

3.从spring.factories中找出ApplicationContextInitializer并设置到初始化器initializers。

4.从spring.factories中找出ApplicationListener,并实例化后设置到SpringApplication的监听器listeners属性中。这个过程就是找出所有的应用程序事件监听器。

5.找出的main方法的类(这里是CmsApplication),并返回Class对象。

默认情况下,initialize方法从spring.factories文件中找出的key为ApplicationContextInitializer的类有:

  • org.springframework.boot.context.config.DelegatingApplicationContextInitializer
  • org.springframework.boot.context.ContextIdApplicationContextInitializer
  • org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer
  • org.springframework.boot.context.web.ServerPortInfoApplicationContextInitializer
  • org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer

key为ApplicationListener的有:

  • org.springframework.boot.context.config.ConfigFileApplicationListener
  • org.springframework.boot.context.config.AnsiOutputApplicationListener
  • org.springframework.boot.logging.LoggingApplicationListener
  • org.springframework.boot.logging.ClasspathLoggingApplicationListener
  • org.springframework.boot.autoconfigure.BackgroundPreinitializer
  • org.springframework.boot.context.config.DelegatingApplicationListener
  • org.springframework.boot.builder.ParentContextCloserApplicationListener
  • org.springframework.boot.context.FileEncodingApplicationListener
  • org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

三、SpringApplication构造和初始化完成后,便是运行其run方法

public ConfigurableApplicationContext run(String... args) {
  StopWatch stopWatch = new StopWatch();// 构造一个任务执行观察器
  stopWatch.start();// 开始执行,记录开始时间
  ConfigurableApplicationContext context = null;
  FailureAnalyzers analyzers = null;
  this.configureHeadlessProperty();
  // 获取SpringApplicationRunListeners,内部只有一个EventPublishingRunListener
  SpringApplicationRunListeners listeners = this.getRunListeners(args);
  // 封装成SpringApplicationEvent事件然后广播出去给SpringApplication中的listeners所监听,启动监听
  listeners.starting();
  try {
   // 构造一个应用程序参数持有类
   ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
   // 加载配置环境
   ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
   Banner printedBanner = this.printBanner(environment);
   // 创建Spring容器(使用BeanUtils.instantiate)
   context = this.createApplicationContext();
   // 若容器创建失败,分析输出失败原因
   new FailureAnalyzers(context);
   // 设置容器配置环境,监听等
   this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
   // 刷新容器
   this.refreshContext(context);
   this.afterRefresh(context, applicationArguments);
   // 广播出ApplicationReadyEvent事件给相应的监听器执行
   listeners.finished(context, (Throwable)null);
   stopWatch.stop();// 执行结束,记录执行时间
   if (this.logStartupInfo) {
    (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
   }
   return context;// 返回Spring容器
  } catch (Throwable var9) {
   this.handleRunFailure(context, listeners, (FailureAnalyzers)analyzers, var9);
   throw new IllegalStateException(var9);
  }
 }

run方法过程分析如上,该方法几个关键步骤如下:

1.创建了应用的监听器SpringApplicationRunListeners并开始监听

2.加载SpringBoot配置环境(ConfigurableEnvironment),如果是通过web容器发布,会加载StandardEnvironment,其最终也是继承了ConfigurableEnvironment,类图如下 

可以看出,*Environment最终都实现了PropertyResolver接口,我们平时通过environment对象获取配置文件中指定Key对应的value方法时,就是调用了propertyResolver接口的getProperty方法。

3.配置环境(Environment)加入到监听器对象中(SpringApplicationRunListeners)

4.创建Spring容器:ConfigurableApplicationContext(应用配置上下文),我们可以看一下创建方法

protected ConfigurableApplicationContext createApplicationContext() {
  Class<?> contextClass = this.applicationContextClass;
  if (contextClass == null) {
   try {
    contextClass = Class.forName(this.webEnvironment ? "org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext" : "org.springframework.context.annotation.AnnotationConfigApplicationContext");
   } catch (ClassNotFoundException var3) {
    throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
   }
  }
  return (ConfigurableApplicationContext)BeanUtils.instantiate(contextClass);
 }

方法会先获取显式设置的应用上下文(applicationContextClass),如果不存在,再加载默认的环境配置(通过是否是web environment判断),默认选择AnnotationConfigApplicationContext注解上下文(通过扫描所有注解类来加载bean),最后通过BeanUtils实例化上下文对象,并返回,ConfigurableApplicationContext类图如下

主要看其继承的两个方向:

  • LifeCycle:生命周期类,定义了start启动、stop结束、isRunning是否运行中等生命周期空值方法
  • ApplicationContext:应用上下文类,其主要继承了beanFactory(bean的工厂类)。

5.回到run方法内,设置容器prepareContext方法,将listeners、environment、applicationArguments、banner等重要组件与上下文对象关联

6.刷新容器,refresh()方法,初始化方法如下:

public void refresh() throws BeansException, IllegalStateException {
  Object var1 = this.startupShutdownMonitor;
  synchronized(this.startupShutdownMonitor) {
   this.prepareRefresh();
   ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
   this.prepareBeanFactory(beanFactory);
   try {
    this.postProcessBeanFactory(beanFactory);
    this.invokeBeanFactoryPostProcessors(beanFactory);
    this.registerBeanPostProcessors(beanFactory);
    this.initMessageSource();
    this.initApplicationEventMulticaster();
    this.onRefresh();
    this.registerListeners();
    this.finishBeanFactoryInitialization(beanFactory);
    this.finishRefresh();
   } catch (BeansException var9) {
    if (this.logger.isWarnEnabled()) {
     this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
    }
    this.destroyBeans();
    this.cancelRefresh(var9);
    throw var9;
   } finally {
    this.resetCommonCaches();
   }
  }
 }

refresh()方法做了很多核心工作比如BeanFactory的设置,BeanFactoryPostProcessor接口的执行、BeanPostProcessor接口的执行、自动化配置类的解析、spring.factories的加载、bean的实例化、条件注解的解析、国际化的初始化等等。这部分内容会在之后的文章中分析。

7.广播出ApplicationReadyEvent,执行结束返回ConfigurableApplicationContext。

至此,SpringBoot启动完成,回顾整体流程,Springboot的启动,主要创建了配置环境(environment)、事件监听(listeners)、应用上下文(applicationContext),并基于以上条件,在容器中开始实例化我们需要的Bean。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我们的支持。如果你想了解更多相关内容请查看下面相关链接

(0)

相关推荐

  • springboot启动时运行代码详解

    Intellij IDEA开发工具,基于Maven框架的SpringBoot简单示例演示启动. Maven工程pom.xml配置,主要引入spring-boot-starter-web等依赖,如下图所示. SpringBoot主程序入口,通过该类启动SpringBoot应用. 通过@Component.@RestController等注解,实现在SpringBoot启动时,自动运行相应的代码块.如下图.为其中一示例.

  • 详解SpringBoot应用服务启动与安全终止

    SpringBoot应用服务启动 参照官方示例工程可以快速搭建简单SpringBoot应用,官方连接如下:http://projects.spring.io/spring-boot/#quick-start 闲话少叙,上代码: package hello; import org.springframework.boot.*; import org.springframework.boot.autoconfigure.*; import org.springframework.stereotype

  • 详解SpringBoot注册Windows服务和启动报错的原因

    Windows系统启动Java程序会弹出黑窗口.黑窗口有几点不好.首先它不美观:其次容易误点导致程序关闭:但最让我匪夷所思的是:将鼠标光标选中黑窗口日志信息,程序竟然不会继续执行,日志也不会继续输出.从而导致页面一直处于请求状态.回车后程序才能正常执行.同时客户希望我们能部署在Windows系统上并且做到开机自动启动.针对以上需求将系统程序注册成Windows服务变得尤为重要. 针对于SpringBoot程序,目前主流的方法是采用winsw,简单方便.可是在开发过程中,针对不同的系统,启动服务可

  • SpringBoot启动报错Failed to determine a suitable driver class

    SpringBoot启动报错如下 Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled. 2019-05-06 21:27:18.275 ERROR 10968 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** AP

  • SpringBoot中如何启动Tomcat流程

    前面在一篇文章中介绍了 Spring 中的一些重要的 context.有一些在此文中提到的 context,可以参看上篇文章. SpringBoot 项目之所以部署简单,其很大一部分原因就是因为不用自己折腾 Tomcat 相关配置,因为其本身内置了各种 Servlet 容器.一直好奇: SpringBoot 是怎么通过简单运行一个 main 函数,就能将容器启动起来,并将自身部署到其上 .此文想梳理清楚这个问题. 我们从SpringBoot的启动入口中分析: Context 创建 // Crea

  • Linux 启动停止SpringBoot jar 程序部署Shell 脚本的方法

    废话不多说了,先给大家上代码,具体代码如下所示: #!/bin/bash cd `dirname $0` CUR_SHELL_DIR=`pwd` CUR_SHELL_NAME=`basename ${BASH_SOURCE}` #修改这里jar包名即可 JAR_NAME="xxxxxxxxxxxx.jar" JAR_PATH=$CUR_SHELL_DIR/$JAR_NAME #JAVA_MEM_OPTS=" -server -Xms1024m -Xmx1024m -XX:Pe

  • SpringBoot整个启动过程的分析

    前言 前一篇分析了SpringBoot如何启动以及内置web容器,这篇我们一起看一下SpringBoot的整个启动过程,废话不多说,正文开始. 正文 一.SpringBoot的启动类是**application,以注解@SpringBootApplication注明. @SpringBootApplication public class CmsApplication { public static void main(String[] args) { SpringApplication.run

  • SpringBoot启动过程逐步分析讲解

    springboot启动是通过一个main方法启动的,代码如下 @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } 从该方法我们一路跟进去,进入SpringApplication的构造函数,我们可以看到如下代码primarySources,为我们从run方法塞进来的

  • Android 系统服务TelecomService启动过程原理分析

    由于一直负责的是Android Telephony部分的开发工作,对于通信过程的上层部分Telecom服务以及UI都没有认真研究过.最近恰好碰到一个通话方面的问题,涉及到了Telecom部分,因而就花时间仔细研究了下相关的代码.这里做一个简单的总结.这篇文章,主要以下两个部分的内容: 什么是Telecom服务?其作用是什么? Telecom模块的启动与初始化过程: 接下来一篇文章,主要以实际通话过程为例,分析下telephony收到来电后如何将电话信息发送到Telecom模块以及Telecom是

  • Android系统进程间通信(IPC)机制Binder中的Server启动过程源代码分析

    在前面一篇文章Android系统进程间通信(IPC)机制Binder中的Server和Client获得Service Manager接口之路中,介绍了在Android系统中Binder进程间通信机制中的Server角色是如何获得Service Manager远程接口的,即defaultServiceManager函数的实现.Server获得了Service Manager远程接口之后,就要把自己的Service添加到Service Manager中去,然后把自己启动起来,等待Client的请求.

  • Android Service启动过程完整分析

    刚开始学习Service的时候以为它是一个线程的封装,也可以执行耗时操作.其实不然,Service是运行在主线程的.直接执行耗时操作是会阻塞主线程的.长时间就直接ANR了. 我们知道Service可以执行一些后台任务,是后台任务不是耗时的任务,后台和耗时是有区别的喔. 这样就很容易想到音乐播放器,天气预报这些应用是要用到Service的.当然如果要在Service中执行耗时操作的话,开个线程就可以了. 关于Service的运行状态有两种,启动状态和绑定状态,两种状态可以一起. 启动一个Servi

  • 分析Android Activity的启动过程

    分析Android Activity的启动过程 对于Android Activity 的启动过程,我在Android源码中读了好久的源码,以下是我整理出来的Activity启动过程和大家分享下: Activity作为Android的四大组件之一,也是最基本的组件,负责与用户交互的所有功能.Activity的启动过程也并非一件神秘的事情,接下来就简单的从源码的角度分析一下Activity的启动过程. 根Activity一般就是指我们项目中的MainActivity,代表了一个android应用程序

  • Android系统进程间通信(IPC)机制Binder中的Client获得Server远程接口过程源代码分析

    在上一篇文章中,我们分析了Android系统进程间通信机制Binder中的Server在启动过程使用Service Manager的addService接口把自己添加到Service Manager守护过程中接受管理.在这一篇文章中,我们将深入到Binder驱动程序源代码去分析Client是如何通过Service Manager的getService接口中来获得Server远程接口的.Client只有获得了Server的远程接口之后,才能进一步调用Server提供的服务. 这里,我们仍然是通过A

  • 分析SpringBoot的启动原理

    背景 1> 大家都知道SpringBoot是通过main函数启动的,这里面跟踪代码到处都没有找到while(true),为什么启动后可以一直跑? 2> SpringBoot默认使用tomcat作为web容器.大家也可以通过在pom文件中exclusion掉tomcat,denpendency jetty 的方法来使用jetty.那SpringBoot是怎么做到在不同web容器之间切换的呢? 3> 传统的web容器比如jetty本质上是直接通过java start.jar 来启动,之后来加

  • Springboot详解底层启动过程

    目录 SpringApplication构造分析 SpringApplication run分析 SpringApplication构造分析 1.记录 BeanDefinition 源 spring容器刚开始是空的,要去各个源找到beanDefinition,这些源可能是配置类,可能是xml文件.在构造方法里会获取一个主源,也就是引导类,根据引导类去获取beanDefinition. 2.推断应用类型 根据jar包去判断是什么引用类型 3.记录 ApplicationContext 初始化器 对

  • SpringBoot加载读取配置文件过程详细分析

    目录 配置文件的读取顺序 多坏境的配置文件 个性化配置 自定义配置文件名称和路径 加载yml文件 springboot默认读取的配置文件名字是:“application.properties”和“application.yml”,默认读取四个位置的文件:根目录下.根目录的config目录下.classpath目录下.classpath目录里的config目录下: 配置文件的读取顺序 根目录/config/application.properties 根目录/config/application.

随机推荐