首页 > 编程语言 >springboot启动流程源码解析(带流程图)

springboot启动流程源码解析(带流程图)

时间:2023-10-17 14:46:35浏览次数:42  
标签:初始化 springboot beanFactory spring environment bean 源码 context 流程图

大致流程如下:

  1. 初始化SpringApplication,从META-INF下的spring.factories读取
  2. ApplicationListener/ApplicationContextInitializer
  3. 运行SpringApplication的run方法
  4. 读取项目中环境变量、jvm配置信息、配置文件信息等
  5. 创建Spring容器对象(ApplicationContext)
  6. 利用ApplicationContextInitializer初始化Spring容器对象,读取启动类
  7. 调用spring的refresh加载IOC容器、自动配置类,并创建bean、servlet容器等信息
  8. springboot会调用很多监听器
  9. 如果启动时发生异常,则发送ApplicationFailedEvent事件
下面对上述流程进行源码细化分析,首先调用SpringApplication.run启动springboot应用:
@SpringBootApplication(exclude = DruidDataSourceAutoConfigure.class)
@EnableTransactionManagement(proxyTargetClass = true)
@Import(MyDynamicDataSourceConfig.class)
public class MySpringBootApplication {
    public static void main(String[] args) {
        SpringApplication.run(MySpringBootApplication.class, args);
    }
}
进入run方法后,会进行SpringApplication进行启动,分两大步,第一步初始化SpringApplication,第二步调用run方法:
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
   return new SpringApplication(primarySources).run(args);
}
第一步初始化SpringApplication,new SpringApplication(primarySources)的流程如下(具体方法含义参考注释):
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
   this.resourceLoader = resourceLoader;
   Assert.notNull(primarySources, "PrimarySources must not be null");
   this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
  // 根据classpath下的类,推算当前web应用类型(REACTIVE/SERVLET/NONE,我们的项目一般是SERVLET)
   this.webApplicationType = WebApplicationType.deduceFromClasspath();
  // 获取BootstrapRegistryInitializer对象,从META-INF/spring.factories中读取key为BootstrapRegistryInitializer,并实例化出对象
 // BootstrapRegistryInitializer的作用是可以初始化BootstrapRegistry
  this.bootstrapRegistryInitializers = new ArrayList<>(
         getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
  //去spring.factories中去获取所有key:org.springframework.context.ApplicationContextInitializer为了初始化Spring容器ApplicationContext对象(可以利用
 //ApplicationContextInitializer向Spring容器中添加ApplicationListener)
  setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
  //去spring.factories中去获取所有key: org.springframework.context.ApplicationListener,ApplicationListener是Spring中的监听器
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  //推测main()方法所在的类
   this.mainApplicationClass = deduceMainApplicationClass();
}
第二步调用run方法,初始化完SpringApplication开始运行run方法,源码如下:
public ConfigurableApplicationContext run(String... args) {
   long startTime = System.nanoTime();//记录时间
   //创建DefaultBootstrapContext对象,利用BootstrapRegistryInitializer初始化DefaultBootstrapContext对象
   DefaultBootstrapContext bootstrapContext = createBootstrapContext();
   ConfigurableApplicationContext context = null;
  // 开启了Headless模式:
   configureHeadlessProperty();
  //获取SpringApplicationRunListeners, //SpringBoot提供了一个EventPublishingRunListener,它实现了SpringApplicationRunListener接口
  //spring会利用这个类,发布一个ApplicationContextInitializedEvent事件,可以通过定义ApplicationListener来消费这个事件
   SpringApplicationRunListeners listeners = getRunListeners(args);
  // 发布ApplicationStartingEvent事件,在运行开始时发送
   listeners.starting(bootstrapContext, this.mainApplicationClass);
   try {
     // 根据命令行参数 实例化一个ApplicationArguments
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
     // 预初始化环境(见下面的源码分析): 读取环境变量(操作系统的环境变量/JVM的环境变量),读取配置文件信息(基于监听器,会利用EventPublishingRunListener发布一个ApplicationEnvironmentPreparedEvent事件,
		//默认会有一个EnvironmentPostProcessorApplicationListener来处理这个事件,当然也可以通过自定义ApplicationListener来处理这个事件,当ApplicationListener接收到这个事件之后,就会解析application.properties、application.yml文件,
   //并添加到Environment中)
      ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
      configureIgnoreBeanInfo(environment);
      Banner printedBanner = printBanner(environment);// 打印Banner
     //据webApplicationType创建不同的Spring上下文容器(有三种)
      context = createApplicationContext();
      context.setApplicationStartup(this.applicationStartup);
     //预初始化spring上下文,见下面的源码分析
      prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
     //刷新Spring容器,这一步中创建并初始化bean,创建并启动tomacat等(以tomcat为例,调到ServletWebServerApplicationContext的createWebServer()方法
     //最后执行TomcatServletWebServerFactory的getWebServer方法)
     refreshContext(context);
      afterRefresh(context, applicationArguments);
      Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
      }
     //发布ApplicationStartedEvent事件和AvailabilityChangeEvent事件
      listeners.started(context, timeTakenToStartup);
     // 获取Spring容器中的ApplicationRunner/CommandLineRunner类型的Bean,并执行run方法
      callRunners(context, applicationArguments);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, listeners);//发布ApplicationFailedEvent事件
      throw new IllegalStateException(ex);
   }
   try {
      Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
     //发布ApplicationReadyEvent事件和AvailabilityChangeEvent事件
      listeners.ready(context, timeTakenToReady);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, null);//发布ApplicationFailedEvent事件
      throw new IllegalStateException(ex);
   }
   return context;
}
预初始化环境,创建Environment对象源码解析:
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
      DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
   // 根据webApplicationType 创建Environment 创建就会读取: java环境变量和系统环境变量
   ConfigurableEnvironment environment = getOrCreateEnvironment();
  // 将命令行参数读取环境变量中
   configureEnvironment(environment, applicationArguments.getSourceArgs());
  // 将@PropertieSource的配置信息 放在第一位,它的优先级是最低的
   ConfigurationPropertySources.attach(environment);
  // 发布了ApplicationEnvironmentPreparedEvent 的监听器 读取了全局配置文件
   listeners.environmentPrepared(bootstrapContext, environment);
  // 将所有spring.main 开头的配置信息绑定到SpringApplication中
   DefaultPropertiesPropertySource.moveToEnd(environment);
   Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
         "Environment prefix cannot be set via properties.");
   bindToSpringApplication(environment);
   if (!this.isCustomEnvironment) {
      EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());
      environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
   }
   ConfigurationPropertySources.attach(environment);//更新PropertySources
   return environment;
}
预初始化spring上下文源码解析:
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
      ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
      ApplicationArguments applicationArguments, Banner printedBanner) {
   context.setEnvironment(environment);
   postProcessApplicationContext(context);
   applyInitializers(context);// 拿到之前读取到所有ApplicationContextInitializer的组件, 循环调用initialize方法
   listeners.contextPrepared(context);// 发布了ApplicationContextInitializedEvent
   bootstrapContext.close(context);
   if (this.logStartupInfo) {
      logStartupInfo(context.getParent() == null);
      logStartupProfileInfo(context);
   }
  // 获取当前spring上下文beanFactory (负责创建bean)
   ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
   beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
   if (printedBanner != null) {
      beanFactory.registerSingleton("springBootBanner", printedBanner);
   }
   if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
      ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
      if (beanFactory instanceof DefaultListableBeanFactory) {
        //在SpringBoot 在这里设置了不允许覆盖, 当出现2个重名的bean 会抛出异常
         ((DefaultListableBeanFactory) beanFactory)
               .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
      }
   }
  // 设置当前spring容器是不是要将所有的bean设置为懒加载
   if (this.lazyInitialization) {
      context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
   }
   context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
   // Load the sources
   Set<Object> sources = getAllSources();
   Assert.notEmpty(sources, "Sources must not be empty");
  // 读取主启动类 (因为后续要根据配置类解析配置的所有bean),将启动类作为配置类注册到Spring容器中
   load(context, sources.toArray(new Object[0]));
   listeners.contextLoaded(context);//读取完配置类后发送ApplicationPreparedEvent,默认利用EventPublishingRunListener发布一个ApplicationPreparedEvent事件
}
refreshContext(context),初始化,启动tomcat,这里就是常说到的springbean生命周期相关的代码:
@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
 
			// Prepare this context for refreshing.
			prepareRefresh();
 
			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
 
			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);
 
			try {
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);
 
				StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);
 
				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);
				beanPostProcess.end();
 
				// Initialize message source for this context.
				initMessageSource();
 
				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();
 
				// Initialize other special beans in specific context subclasses.
                // 这里启动了tomcat
				onRefresh();
 
				// Check for listener beans and register them.
				registerListeners();
                 // 初始化非懒加载的bean,springboot的单例默认是懒加载的
				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);
 
				// Last step: publish corresponding event.
				finishRefresh();
			}
 
			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}
 
				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();
 
				// Reset 'active' flag.
				cancelRefresh(ex);
 
				// Propagate exception to caller.
				throw ex;
			}
 
			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
				contextRefresh.end();
			}
		}
	}
1),springbean在容器里的两种存在形式,一种是我们项目中使用到的javabean,一种BeanDefinition;

2),BeanDefinition存在DefaultListableBeanFactory,javabean存在DefaultSingletonBeanRegistry的三级缓存中,javabean是通过BeanDefinition反射而来。

3),因为springboot默认是单例,单例又默认懒加载,所以如果你的bean不是项目启动就被调用,如在ApplicationRunner中,那么就可能项目启动成功,但是后续报错循环依赖,这种错误很可能发生在你在一个类中注入了另外一个类的实例,只是调了它一个简单的方法,简单到你自信不用单测直接可以上测试或者生产环境。

image

原文链接:https://blog.csdn.net/zkr1234562/article/details/128053698

标签:初始化,springboot,beanFactory,spring,environment,bean,源码,context,流程图
From: https://www.cnblogs.com/hefeng2014/p/17769621.html

相关文章

  • 手机直播源码,关于pyqt5弹出提示框
    手机直播源码,关于pyqt5弹出提示框1.软件关闭弹框这类的弹框一般是在整个软件关闭的时候提醒用户是否需要退出整个软件 (构建成函数的方法)    defcloseEvent(self,event):    #关闭窗口触发以下事件      a=QMessageBox.question(self,'退出'......
  • springboot heapdump信息获取
    springboot信息泄露可能泄漏的路由/api-docs/v2/api-docs/swagger-ui.html/api.html/sw/swagger-ui.html/api/swagger-ui.html/template/swagger-ui.html/spring-security-rest/api/swagger-ui.html/spring-security-oauth-resource/swagger-ui.html/mappings/actua......
  • ahooks 源码实现
    ahooks库源码实现state模块useSetState功能点:1.实现类似class组件中setState功能,只更新传入的值,其他值不用更新;2.且可以穿入第二个回调函数参数同步获取更新后的最新state用于操作。import{useState}from'react';exportconstuseSetState=(init={})=>{c......
  • Linux下源码编译gcc指定版本
    首先你得有一个编译器才能编译编译器下载GCC源码并解压wgethttps://mirrors.tuna.tsinghua.edu.cn/gnu/gcc/gcc-9.4.0/gcc-9.4.0.tar.gztar-zxvfgcc-9.4.0.tar.gz这里我选择了gcc-9.4.0版本下载依赖文件cdgcc-9.4.0./contrib/download_prerequisites编译前配置......
  • Idea调试Tomcat源码
    一、下载Tomcat源码SourceCode:https://dlcdn.apache.org/tomcat/tomcat-8/v8.5.93/src/apache-tomcat-8.5.93-src.zipBinary:https://dlcdn.apache.org/tomcat/tomcat-8/v8.5.93/bin/apache-tomcat-8.5.93.zip建议保持这俩版本一致,不然会出现各种找不到类,方法的情况等。解......
  • java -jar命令及SpringBoot通过java -jav启动项目的过程
    本篇文章将为大家讲述关于SpringBoot项目工程完成后,是如何通过java-jar命令来启动的,以及介绍java-jar命令的详细内容,对SpringBootjava-jav启动过程感兴趣的朋友跟随小编一起看看吧本篇文章将为大家讲述关于SpringBoot项目工程完成后,是如何通过java-jar命令来启动的......
  • java-springboot和servlet的项目搭建
    1.404->启动tomcat->tomcat闪退->配置jre全局环境,重启电脑->8080端口被占用->下载太多tomcat->重新配置->还是被占用->命令行找netstat-ano|findstr80得到PID,在任务管理器找到(用PID排序会更好找)是一个java.exe,结束进程。->成功运行2.入口类3.mysql命令不生效->因为没加分号(我......
  • Avalonia 实现视频聊天、远程桌面(源码,支持Windows、Linux、国产OS)
      现在最火的.NET跨平台UI框架莫过于Avalonia了。Avalonia基于.NETCore,因此它可以运行在任何支持.NETCore的平台上。之前基于CPF跨平台UI框架写过一个视频聊天的demo,而现在看来Avalonia是大势所趋,于是,我再写一个Avalonia版本的Demo来供大家参考,它可以在Windows和Linux(包括国......
  • SpringBoot+内置Tomcat配置,参数调优,最大并发量,最大连接数
    最近在研究这块的信息,记录下一些大神的文章:SpringBoot最大连接数及最大并发数是多少???https://blog.csdn.net/weixin_44421461/article/details/132486085SpringBoot+内置Tomcat配置,参数调优,最大并发量,最大连接数https://blog.csdn.net/myyhtw/article/details/129069586Sprin......
  • Nacos源码 (7) Nacos与Spring
    SpringCloud工程可以使用Nacos作为注册中心和配置中心,配置和使用非常简单,本文将简单介绍使用方式,并分析其实现方式。SpringCloud工程集成NacosSpringCloud工程使用Nacos非常简单,只需要引入依赖、编写配置参数、在启动类上添加注解即可。引入依赖<dependencyManagement><dep......