首页 > 其他分享 >SpringBoot运行流程

SpringBoot运行流程

时间:2023-05-08 10:36:09浏览次数:42  
标签:return SpringBoot 流程 args SpringApplication new type Class 运行

SpringBoot运行流程

一、准备阶段

我们先看一下这个SpringApplication的构造方法中做了什么事情,为run方法准备了那些事情

通常在一个spring boot的应用中,会看到下面一段代码作为应用的入口

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

那么这段代码究竟做了什么呢,让我们深入来分析它背后的原理。当我们点击run来查看源代码时,会看到下面这段代码,这段注释说明这是一个助手方法,可以通过指定一个primarySource的source源来启动,这个primarySource其实就是我们的启动类Application

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
		return run(new Class<?>[] { primarySource }, args);
	}

接着,我们继续进入run方法,你会看到另外一个helper方法,这个helper方法首先初始化一个SpringApplication,然后再一次执行SpringApplication实例的run方法

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return new SpringApplication(primarySources).run(args);
	}	

SpringApplication构造方法又调用了其重载构造方法

	public SpringApplication(Class<?>... primarySources) {
		this(null, primarySources);
	}
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		// 步骤 1 初始化webApplicationType
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		// 步骤 2 初始化ApplicationContextInitializer
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		// 步骤 3 初始化ApplicationListener
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		// 步骤 4 获取Main方法所在类
		this.mainApplicationClass = deduceMainApplicationClass();
	}

1.1 初始化webApplicationType

	static WebApplicationType deduceFromClasspath() {
		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
			return WebApplicationType.REACTIVE;
		}
		for (String className : SERVLET_INDICATOR_CLASSES) {
			if (!ClassUtils.isPresent(className, null)) {
				return WebApplicationType.NONE;
			}
		}
		return WebApplicationType.SERVLET;
	}

webApplicationType确定当前应用的类型,进入deduceFromClasspath()可以看到spring boot是如何确定类型的,从下面这段代码中可以看出当前应用属于哪一种类型取决于classpath中是否加载到了相应的类。

  • 如果classpath中有org.springframework.web.reactive.DispatcherHandler类,但是没有DispatcherServletServletContainer类,则类型为Reactive web application,会启动一个嵌入式的reactive web server
  • 如果classpath中既没有javax.servlet.Servlet又没有 org.springframework.web.context.ConfigurableWebApplicationContext,那么该应用不是一个web application
  • 如果不是以上两种,那么就判断为一个servletweb application,会启动一个嵌入式的servlet web server

1.2 初始化ApplicationContextInitializer

	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
		return getSpringFactoriesInstances(type, new Class<?>[] {});
	}
	
    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = getClassLoader();
		// Use names and ensure unique to protect against duplicates
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

	@SuppressWarnings("unchecked")
	private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
			ClassLoader classLoader, Object[] args, Set<String> names) {
		List<T> instances = new ArrayList<>(names.size());
		for (String name : names) {
			try {
				Class<?> instanceClass = ClassUtils.forName(name, classLoader);
				Assert.isAssignable(type, instanceClass);
				Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
				T instance = (T) BeanUtils.instantiateClass(constructor, args);
				instances.add(instance);
			}
			catch (Throwable ex) {
				throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
			}
		}
		return instances;
	}	

通过getSpringFactoriesInstances最终获得META-INF/spring.factories中所有的ApplicationContextInitializer,而ApplicationContextInitializer是用来初始化ConfigurableApplicationContext

1.3 初始化ApplicationListener

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
		return getSpringFactoriesInstances(type, new Class<?>[] {});
	}

	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = getClassLoader();
		// Use names and ensure unique to protect against duplicates
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

	@SuppressWarnings("unchecked")
	private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
			ClassLoader classLoader, Object[] args, Set<String> names) {
		List<T> instances = new ArrayList<>(names.size());
		for (String name : names) {
			try {
				Class<?> instanceClass = ClassUtils.forName(name, classLoader);
				Assert.isAssignable(type, instanceClass);
				Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
				T instance = (T) BeanUtils.instantiateClass(constructor, args);
				instances.add(instance);
			}
			catch (Throwable ex) {
				throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
			}
		}
		return instances;
	}

通过getSpringFactoriesInstances最终获得META-INF/spring.factories中所有的ApplicationListener

1.4 获取Main方法所在类

private Class<?> deduceMainApplicationClass() {
		try {
			StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
			for (StackTraceElement stackTraceElement : stackTrace) {
				if ("main".equals(stackTraceElement.getMethodName())) {
					return Class.forName(stackTraceElement.getClassName());
				}
			}
		}
		catch (ClassNotFoundException ex) {
			// Swallow and continue
		}
		return null;
	}

通过构造一个RuntimeException,然后得到StackTrace的方法,来获得main方法所在的类,这样做的原因是因为primarySource是作为数组形式传入方法的,如果有超过一个primarySource,那么就没法直接从primarySource来判断哪个source是main方法所在实例。

二、运行阶段

SpringApplication类的构造方法看完后,我们就来看一下他的run方法吧:

/**
	 * Run the Spring application, creating and refreshing a new
	 * {@link ApplicationContext}.
	 * @param args the application arguments (usually passed from a Java main method)
	 * @return a running {@link ApplicationContext}
	 */
	public ConfigurableApplicationContext run(String... args) {
		//创建StopWatch对象用于统计run方法的执行耗时
		StopWatch stopWatch = new StopWatch();
		//调用start方法表示开始启动计时
		stopWatch.start();
		//声明上下文
		ConfigurableApplicationContext context = null;
		//故障分析集合
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		//设置headless模式 就是设置系统属性java.awt.headless 
		configureHeadlessProperty();
		//通过SPI机制加载所有的SpringApplicatoinRunListener监听器
		//SpringBoot启动过程的不同阶段会回调该监听器的不同方法
		SpringApplicationRunListeners listeners = getRunListeners(args);
		//使用组合对象的设计模式 迭代的执行starting() 
		listeners.starting();
		try {
			//将SpringApplication的启动参数封装为ApplicationArguments
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			//创建SpringBoot应用使用的环境变量对象,内部会根据webApplicationType创建不同的环境对象,
			//这里会创建StandardServletEnvironment对象
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			configureIgnoreBeanInfo(environment);
			//打印Banner 
			Banner printedBanner = printBanner(environment);
			//创建使用的ApplicationContext上下文对象,这里会根据webApplicationType创建不同的对象上下文对象
			//这里会创建StandardServletEnvironment对象
			context = createApplicationContext();
			//获取启动错误报告实例
			//通过SPI机制加载SpringBoot的异常报告对象SpringBootExceptionReporter
			//当SpringBoot启动过程中抛出异常时,会通过该对象打印错误日志
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			//ApplicationContext上下文对象创建完毕后,会调用prepareContext为ApplicationContext做一些准备工作
			//比如为ApplicationContext设置环境变量,回调ApplicationContextInitializer对象的initialize方法等
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			//调用ApplicationContext的refresh方法,启动整个Spring应用程序
			refreshContext(context);
			//刷新后的上下文处理  
			afterRefresh(context, applicationArguments);
			//计时结束
			stopWatch.stop();
			//打印日志 
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			//调用SpringApplcationListener对象的started监听方法
			//监听spring上下文,此时上下文已启动,Spring Bean已初始化完成
			listeners.started(context);
			//回调Spring中的的ApplicationRunner对象和CommandLineRunner对象
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}
		try {
			//启动成功,则调用SpringApplicationListener对象的running监听方法
			listeners.running(context);
		}
		catch (Throwable ex) {
			//抛出异常,则使用SpringExceptionReporter打印异常报告
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		//返回创建的ApplicationContext应用上下文
		return context;
	}

1.创建 Spring Application 实例,调用 run 方法,同时将启动入口类作 为参数传递进去;

2.通过 Spring Factories Loader 加载 META-INF/spring.factories 文件;

3.然后由 SpringApplicationRunListener 来发出 starting 消息;

4.创建参数,并配置当前 SpringBoot 应用需要使用的 Environment 实例;

5.完成之后,依然由 SpringApplicationRunListener 来发出 environmentPrepared 消息;

6.创建 Spring 的应用上下文实例:ApplicationContext,初始化该实例 并设置应用环境配置实例:Environment,同时加载相关的配置项;

7.由 SpringApplicationRunListener 发出 contextPrepared 消息,告知 SpringBoot 应用当前使用的 ApplicationContext 已准备完毕;

8.将各种 Bean 组件装载入 SpringIO 容器/应用上下文ApplicationContext 中,继续由 SpringApplicationRunListener 来发出 contextLoaded 消息,告知 SpringBoot 应用当前使用的 ApplicationContext 已准备完毕;

9.重新刷新 Refresh Spring 的应用上下文实例:ApplicationContext, 完成 IOC 容器可用的最后一步;

10.由 SpringApplicationRunListener 发出 started 消息,完成最终的程序的启动;

11.由 SpringApplicationRunListener 发出 running 消息,告知程序已成功运行起来了。

image

SpringApplication运行阶段主要就分为:

  • 加载:SpringApplication运行监听器(SpringApplicationRunListener)
  • 运行:SpringApplication运行监听器(SpringApplicationRunListeners)
  • 监听:Spring-boot事件,spring事件。
  • 创建:创建上下文,Environment,其他。
  • 失败:打印故障分析报告。
  • 回调。

标签:return,SpringBoot,流程,args,SpringApplication,new,type,Class,运行
From: https://www.cnblogs.com/leepandar/p/17380945.html

相关文章

  • SpringBoot版本接口
    SpringBoot版本接口前言为什么接口会出现多个版本一般来说,RestfulAPI接口是提供给其它模块,系统或是其他公司使用,不能随意频繁的变更。然而,需求和业务不断变化,接口和参数也会发生相应的变化。如果直接对原来的接口进行修改,势必会影响线其他系统的正常运行。这就必须对api接口......
  • SpringBoot参数校验
    SpringBoot参数校验为什么需要参数校验在日常的接口开发中,为了防止非法参数对业务造成影响,经常需要对接口的参数进行校验,例如登录的时候需要校验用户名和密码是否为空,添加用户的时候校验用户邮箱地址、手机号码格式是否正确。靠代码对接口参数一个个校验的话就太繁琐了,代码可读......
  • Linux运行卡死【INFO: task multipathd:5832 blocked for more than 120 seconds】
    目录问题背景分析过程解决方法问题背景系统在正常运行过程中,突然收到监控平台告警,服务器无法ping通。分析过程机房人员重启服务器后,查看/var/log/messages日志发现如下报错:May619:43:45xxxkernel:INFO:taskmultipathd:5832blockedformorethan120seconds.M......
  • SpringBoot统一异常处理
    SpringBoot统一异常处理概述Spring在3.2版本增加了一个注解@ControllerAdvice,可以与@ExceptionHandler、@InitBinder、@ModelAttribute等注解注解配套使用。简单的说,该注解可以把异常处理器应用到所有控制器,而不是单个控制器。借助该注解,我们可以实现:在独立的某个地方,比如单独......
  • SpringBoot添加日志
    SpringBoot添加日志前言SpringBoot使用ApacheCommons日志记录进行所有内部日志记录。SpringBoot的默认配置支持使用JavaUtilLogging,Log4j2和Logback。使用这些,可以配置控制台日志记录以及文件日志记录。如果使用的是SpringBootStarters,Logback将为日志记录提供良好的支......
  • Java开发、SpringBoot开发(狂神说Java)
    目录JavaSpringBoot开发学习(狂神说Java)SpringBoot概述微服务SpringBoot程序安装测试配置文件原理自动配置主启动类yaml语法给属性赋值的几种方式JR303校验多环境配置及配置文件位置SpringBootWeb开发理论静态资源首页模板引擎Thymeleaf语法MVC配置原理,扩展SpringMVC视图解析视......
  • scrapy爬虫标准流程
    Scrapy爬虫的标准流程一般包括以下几个步骤:1、明确需求和目标网站的结构,确定需要爬取的数据以及爬取规则。2、创建一个Scrapy项目,使用命令行工具创建一个新的Scrapy项目。3、定义数据模型和item,即确定要爬取的数据结构。4、编写爬虫类,使用Scrapy的Spider类编写爬虫程序,根据需......
  • SpringBoot访问外部接口
    SpringBoot访问外部接口原生的Http请求@RequestMapping("/doPostGetJson")publicStringdoPostGetJson()throwsParseException{//此处将要发送的数据转换为json格式字符串StringjsonText="{id:1}";JSONObjectjson=(JSONObject)JSONObject.parse(jsonTe......
  • 使jenkins.war保持后台运行的方法
    直接使用命令java-jarjenkins.war 启动Jenkins,这样当退出窗口时,进程就会结束,怎么样能够让Jenkins保持后台运行呢?在网上搜集到以下三种办法,请参考 ​1.java-jarjenkins.war--httpPort=8080&​​​ 2.执行以下三步:(1)执行java-jarxxx.jar后(2)ctrl+z退出到控制台,......
  • 可视化电脑处理变量赋值、循环、程序运行的过程
    这款线上工具支持Python2,Python3,Java,JavaScript,TypeScript,Ruby,C和C++代码。累计有多于180个国家三百五十万多人次使用。通过它可视化运行的代码有三千万之多。下面的动图展示了一段Python程序的变量赋值,变量交换,列表赋值,列表增删,循环,判断,全局变量在运行时发生的动态......