Spring Boot的启动流程

Spring Boot的启动流程

梳理Springboot执行流程的时候,先抓大的步骤,不要一上来就每个方法都跟进去看,那样不利于对整体的理解,且一会就看不下去了。要先理解整体的场景是什么,把握主要步骤,全理解之后可以在每个方法挨个点击去细看。

所以先看一下启动的整体流程步骤:

--------------------------------------创建SprigApplication对象----------------------------------
1.推断应用类型,判断是不是web应用
2.获取所有初始化容器(读取META-INFO/spring.factories文件-ApplicationContextInitializer3.获取所有监听器(读取META-INFO/spring.factories文件-ApplicationListener4.根据调用栈,推断出main方法的类名
------------------------------------------run方法执行流程----------------------------------------
1. 获取并启动监听器 (getRunListeners+listeners.starting()2. 准备环境,将配置文件读取到容器中 (prepareEnvironment)
3. 准备Banner打印器,打印Spring图标 (printBanner)
4. 初始化应用上下文,创建spring容器 (createApplicationContext)
5. Spring容器前置处理 (prepareContext)
6. 刷新应用上下文 (prepareContext)
7. Spring容器后置处理 (afterRefresh)
8. 使用广播和回调机制告诉监听者springboot容器已经启动成功 (listeners.started,listeners.running)

下面开始详细介绍

首先,Spring Boot项目创建完成会默认生成一个名为 *Application 的入口类,我们是通过该类的main方法启动Spring Boot项目的。在main方法中,通过SpringApplication的静态方法,即run方法进行SpringApplication类的实例化操作,然后再针对实例化对象调用另外一个run方法来完成整个项目的初始化和启动。
在这里插入图片描述

会调用另外一个run方法
在这里插入图片描述

由此看到,主要干了两件事情:

  1. 构造SpringApplication实例
  2. 调用示例的run方法

下面就按照这两大点来展开

1 构造SpringApplication实例

构造SpringApplication实例主要干了以下几件事:

  1. 给primarySources变量赋值,传参可以是任意类型
  2. 获取应用类型,判断是不是web应用
  3. 获取所有初始化容器,并设置到initializers变量中
  4. 创建并初始化所有监听器,并设置到listeners变量中
  5. 初始化主类mainApplectionClass,定位main方法

下面对每一步的源码进行分析

经过两层过早方法,最终会进入下面这个核心的构造方法中

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 推断应用类型,判断是不是web应用(会根据类型初始化对应的环境,常用的一般都是servlet环境)】
    *	deduce:推断
    */
   this.webApplicationType = WebApplicationType.deduceFromClasspath();
   /**
    * 【2 获取所有初始化容器】
    */
   setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
   /**
    * 【3 获取所有监听器】
    */
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
   /**
    * 【4 根据调用栈,推断出main方法的类名】
    */
   this.mainApplicationClass = deduceMainApplicationClass();
}

首先会将参数sources赋值给primarySources变量

然后回判断应用类型是否是web应用,跟进deduceFromClasspath方法

/**
 * 判断是什么应用类型:三种类型
 *  NONE:非web应用,即不会启动服务器
 *  SERVLET:基于servlet的web应用
 * 	REACTIVE:响应式web应用(暂未接触过)
 * @return
 */
static WebApplicationType deduceFromClasspath() {
   //classpath下是否存在org.springframework.web.reactive.DispatcherHandler
   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;
      }
   }
   //以servlet-web应用启动,并且启动内嵌的servlet  web服务器
   return WebApplicationType.SERVLET;
}

从返回结果来看,一共有三种类型

返回值说明
NONE非web应用,即不会启动服务器
SERVLET基于servlet的web应用
REACTIVE响应式web应用

判断一共设计四个常量,springboot在启动的时候,会对下面四个常量所对应的class进行判断,看看他们是否存在,从而返回应用类型。

WEBFLUX_INDICATOR_CLASS = “org.springframework.web.reactive.DispatcherHandler”;

WEBMVC_INDICATOR_CLASS = “org.springframework.web.servlet.DispatcherServlet”;

JERSEY_INDICATOR_CLASS = “org.glassfish.jersey.servlet.ServletContainer”;

SERVLET_INDICATOR_CLASSES = { “javax.servlet.Servlet”,
“org.springframework.web.context.ConfigurableWebApplicationContext” };

然后是获取所有初始化容器,跟进getSpringFactoriesInstances。该方法是公共方法,会根据传入的class类型获取不同的实例。

getSpringFactoriesInstances其实主要干了三件事:

  1. 获取所有初始化器的名称集合
  2. 根据名称集合实例化这些初始化器
  3. 对Spring工厂实例排序
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
   ClassLoader classLoader = getClassLoader();
   /**
    * 获取所有初始化器的名称集合   *  SpringFactoriesLoader.loadFactoryNames:在META-INF/spring.factories配置文件里获取初始化器
    */
   Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
   //创建spring工厂实例  通过反射 成实例对象
   /**
    * 根据名称集合实例化这些初始化器
    */
   List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
   //对Spring工厂实例排序(org.springframework.core.annotation.0rder注解指定的顺序)
   AnnotationAwareOrderComparator.sort(instances);
   return instances;
}

其中SpringFactoriesLoader.loadFactoryNames(type, classLoader)会返回所有容器的名称集合,其中type参数为获取该类别所属的容器名称。跟进方法:

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
   String factoryTypeName = factoryType.getName();
   return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    //此时cache中已经与值了,此处直接返回(问题:此处的cache是什么时候被赋值的)
   MultiValueMap<String, String> result = cache.get(classLoader);
   if (result != null) {
      return result;
   }
//加载META-INF/ spring.factories中的资源
   try {
      Enumeration<URL> urls = (classLoader != null ?
            classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
            ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
      result = new LinkedMultiValueMap<>();
      while (urls.hasMoreElements()) {
         URL url = urls.nextElement();
         UrlResource resource = new UrlResource(url);
         Properties properties = PropertiesLoaderUtils.loadProperties(resource);
         for (Map.Entry<?, ?> entry : properties.entrySet()) {
            String factoryTypeName = ((String) entry.getKey()).trim();
            for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
               result.add(factoryTypeName, factoryImplementationName.trim());
            }
         }
      }
      cache.put(classLoader, result);
      return result;
   }
   catch (IOException ex) {
      throw new IllegalArgumentException("Unable to load factories from location [" +
            FACTORIES_RESOURCE_LOCATION + "]", ex);
   }
}

loadSpringFactories方法中有个核心步骤:classLoader.getResources(FACTORIES_RESOURCE_LOCATION)ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)。顾名思义:用类加载器获取系统资源,传入的路径:FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"

直白来说就是是去META-INF/spring.factories配置文件中,拿到和传入的class类名一致的下面的所有实例类名。

例如:先传入的是ApplicationContextInitializer,通过loadSpringFactories读取所有spring.factories文件中的所有类名,在通过getOrDefault(factoryTypeName)方法取出所需要的类名集合,如下图所示
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

通过此方法,容器的名称已经返回,再通过createSpringFactoriesInstances方法进行实例化。返回后通过setInitializers方法赋值给initializers变量

下一步是获取所有监听器,也是调用getSpringFactoriesInstances方法,只是传入的是ApplicationListener,最后赋值给listeners变量。原理通上一步一样。

最后初始化主类mainApplectionClass,进入deduceMainApplicationClass方法

private Class<?> deduceMainApplicationClass() {
	try {
	    // 通过创建运行时异常的方式获取栈
		StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
		// 遍历获取main方法所在的类并且返回
		for (StackTraceElement stackTraceElement : stackTrace) {
			if ("main".equals(stackTraceElement.getMethodName())) {
				return Class.forName(stackTraceElement.getClassName());
			}
		}
	}
	catch (ClassNotFoundException ex) {
		// Swallow and continue
	}
	return null;
}

到此是创建SpringbootApplication对象的过程,下面进入run方法

2 run方法执行流程

创建完对象,就要执行run方法的逻辑,该方法中主要步骤为以下几步:

  1. 获取并启动监听器
  2. 准备环境,将配置文件读取到容器中
  3. 准备Banner打印器,打印Spring图标
  4. 初始化应用上下文(创建Spring容器)
  5. Spring容器前置处理
  6. 刷新应用上下文
  7. Spring容器后置处理
  8. 使用广播和回调机制告诉监听者springboot容器已经启动成功

下面我们跟进run方法的源码:

public ConfigurableApplicationContext  run(String... args) {
	//记录程序运行时间
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();
	ConfigurableApplicationContext context = null;
	Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
	configureHeadlessProperty();
	/**
	 * 【1 获取并启动监听器】
	 */
	SpringApplicationRunListeners listeners = getRunListeners(args);
	// 循环遍历启动监听器
	listeners.starting();
	try {
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
		/**
		 * 【2 准备环境,将配置文件读取到容器中】
		 */
		ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
		//处理需要忽略的bean
		configureIgnoreBeanInfo(environment);
		/**
		 * 【3 准备Banner打印器,打印Spring图标】
		 */
		Banner printedBanner = printBanner(environment);
		/**
		 * 【4 初始化应用上下文】
		 */
		context = createApplicationContext();

		exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
				new Class[] { ConfigurableApplicationContext.class }, context);
		/**
		 * 【5 Spring容器前置处理】
		 */
		prepareContext(context, environment, listeners, applicationArguments, printedBanner);
		/**
		 * 【6 刷新容器】
		 */
		refreshContext(context);
		/**
		 * 【7 Spring容器后置处理】
		 */
		afterRefresh(context, applicationArguments);
		stopWatch.stop();
		if (this.logStartupInfo) {
			new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
		}
		/**
		 * 【8 使用广播和回调机制告诉监听者springboot容器已经启动化成功】
		 */
		listeners.started(context);
		callRunners(context, applicationArguments);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, listeners);
		throw new IllegalStateException(ex);
	}
	try {
		listeners.running(context);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, null);
		throw new IllegalStateException(ex);
	}
	return context;
}

下面我们一个一个步骤进行分析

首先是获取并启动监听器,getRunListeners这一步跟创建SpringbootApplication对象时获取监听对象操作基本一致,都是读取/META-INF/spring.factories拿到ApplicationListener下的所有类

然后是进行环境准备,是为了查找并设置配置文件信息到 environment 中。打印Spring图标,然后对应用上下文进行初始化,进入createApplicationContext()方法

protected ConfigurableApplicationContext createApplicationContext() {
   Class<?> contextClass = this.applicationContextClass;
   if (contextClass == null) {
      try {
          //判断应用类型
         switch (this.webApplicationType) {
         case SERVLET:
            //反射,初始化
            contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
            break;
         case REACTIVE:
            contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
            break;
         default:
            contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
         }
      }
      catch (ClassNotFoundException ex) {
         throw new IllegalStateException(
               "Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
      }
   }
    //将类实例化
   return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

可以看到该方法根据webApplicationType来赋值不同的类型,并通过BeanUtils.instantiateClass实例化

IOC 容器就是上面创建的 context 对象,它的类型是:AnnotationConfigServletWebServerApplicationContext,这个类名翻译过来是”基于注解配置的内嵌了 servlet 的 web服务应用程序上下文“

接下来是执行prepareContext方法,前置处理其实也给各个变量赋值,进入方法:

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
      SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
   //设置容器环境
   context.setEnvironment(environment);
   //执行容器后置处理
   postProcessApplicationContext(context);
   //执行初始化器
   applyInitializers(context);
   listeners.contextPrepared(context);//向各个监听器发送容器已经准备好的事件
   if (this.logStartupInfo) {
      logStartupInfo(context.getParent() == null);
      logStartupProfileInfo(context);
   }
   // Add boot specific singleton beans
   ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
   //将main函数中的args参数封装成单例Bean,注册进容器
   beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
   if (printedBanner != null) {
      //将printedBanner也封装成单例,注册进容器
      beanFactory.registerSingleton("springBootBanner", printedBanner);
   }
   if (beanFactory instanceof DefaultListableBeanFactory) {
      ((DefaultListableBeanFactory) beanFactory)
            .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
   }
   if (this.lazyInitialization) {
      context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
   }
   // Load the sources 拿到的是主类
   Set<Object> sources = getAllSources();
   Assert.notEmpty(sources, "Sources must not be empty");
   //加载我们的启动类,将启动类生成实例对象注入容器
   load(context, sources.toArray(new Object[0]));
   listeners.contextLoaded(context);
}

前置环境准备完,开始刷新上下文应用,执行refreshContext(context)方法,该方法将应用程序运行所需要的所有 bean(包括开发者自己写的) 加载到了 context 的 beanFactory 中。

刷新 context 中最主要也是最重要的一件事情是(自动配置):将要加载的类做成 bean 保存(注册)到 context 中。具体流程是这样的: 要自动配置的类得先写好了,加上注解;( 在启动过程中调用 BeanFactoryPostProcesser 的某个实现类,它为需要自动配置的每一个类创造一个对应的 beanDefinition,并将所有的beanDefinition 保存(注册) 到 context 中。(实际上是为每一个类构建键值对 className:BeanDefinion 保存到了 context 中的 beanFactory 属性的 beanDefinitionMap 属性中)。 接着再遍历 beanDefinitionMap,根据 className 从 context 中先获取对应的 bean,如果能拿到,就表示这个类已经被注册了,如果拿不到,那就通过 BeanDefinion 创建一个新 bean 注册到 context中。(实际上是从 context 中的 beanFactory 属性的 singletonObject 属性中拿 bean,如果拿不到,创建的新 bean 也是保存到 signletonObject 中)。

执行完refreshContext方法,后面不是重点了,根据注释捋一捋主题步骤即可

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猪大侠0.0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值