Spring Boot运行流程浅析

第一篇:Spring Boot自动配置原理浅析

第二篇:Spring Boot构造流程浅析

第三篇:Spring Boot运行流程浅析

前面我们对SpringApplication对象的构造流程进行了浅析,SpringApplication对象构造完成后会调用其run方法进行Spring Boot的启动和运行,本文开始重点分析Spring Boot是如何通过run方法完成整个项目的启动和运行的。

目录

run方法总览

一、获取SpringApplicationRunListener监听器、启动监听器

二、创建ApplicationArguments对象、初始化ConfigurableEnvironment

三、忽略信息配置

四、打印Banner

五、创建容器

六、准备容器

七、刷新容器

七、调用ApplicationRunner和CommandLineRunner


run方法总览

首先回顾一下本文要剖析的run方法是在哪里出现的(并非启动类中的run方法):在Spring Boot构造流程浅析中我们跟进启动类中的run方法后发现SpringApplication.run(xxx.class)主要进行SpringApplication类的实例化操作,然后这个实例化对象再去调用了另外一个更牛逼的run方法来完成整个项目的初始化和启动,而这个更牛逼的run方法就是本文的主角。

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

进入这个run方法,源码及注释如下。

	public ConfigurableApplicationContext run(String... args) {
                //用于统计run方法启动时长
		StopWatch stopWatch = new StopWatch();
                //启动统计
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
                //获取SpringApplicationRunListener监听器
		SpringApplicationRunListeners listeners = getRunListeners(args);
                //启动监听器
		listeners.starting();
		try {
                        //创建ApplicationArguments对象
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
                        //初始化ConfigurableEnvironment
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
                        //忽略信息配置
			configureIgnoreBeanInfo(environment);
                        //打印Banner 
			Banner printedBanner = printBanner(environment);
                        //创建容器
			context = createApplicationContext();
                        //异常报告器
			exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
                        //准备容器
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
                        //刷新化容器
			refreshContext(context);
                        //初始化之后执行
			afterRefresh(context, applicationArguments);
                        //停止时长统计
			stopWatch.stop();
                        //打印启动日志
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}
                        //通知监听器:容器启动完成
			listeners.started(context);
                        //调用ApplicationRunner和CommandLineRunner的运行方法
			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;
	}

忽略调一些非核心操作现在我们来总结一下run方法的核心操作流程:

  1. 获取SpringApplicationRunListener监听器、启动监听器
  2. 创建ApplicationArguments对象、初始化ConfigurableEnvironment
  3. 忽略信息配置
  4. 打印Banner
  5. 创建容器
  6. 准备容器
  7. 刷新容器
  8. 调用ApplicationRunner和CommandLineRunner

下面将逐步浅析上述流程。

一、获取SpringApplicationRunListener监听器、启动监听器

现在开始对run方法的核心流程一步一步的分析,首先是通过getRunListeners方法获取到SpringApplicationRunListeners,可以把SpringApplicationRunListeners理解为一个容器,它里面存放了很多SpringApplicationRunListener相关的监听器,下面是该方法源码:

	private SpringApplicationRunListeners getRunListeners(String[] args) {
		Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
		return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
				SpringApplicationRunListener.class, types, this, args));
	}

发现getRunListeners也只是调用了SpringApplicationRunListeners的构造方法而已,注意看该构造方法的第二个参数是去调用了getSpringFactoriesInstances方法,这个方法的主要作用是去获取META-INF/spring.factories中对应的监听器配置,并进行实例化操作,源码如下:

	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
			Class<?>[] parameterTypes, Object... args) {
                //获得类加载器
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		// Use names and ensure unique to protect against duplicates
                //加载spring.factories文件中监听器的配置
		Set<String> names = new LinkedHashSet<>(
				SpringFactoriesLoader.loadFactoryNames(type, classLoader));
                //实例化刚才获取到的监听器
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
				classLoader, args, names);
                //排序
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

可以看到这里依然是通过SpringFactoriesLoader.loadFactoryNames来加载spring.factories中的监听器配置信息,该方法在之前Spring Boot自动配置原理浅析Spring Boot构造流程浅析已经多次提到,这里就不再重复说明了。最后是通过createSpringFactoriesInstances方法来实例化刚获取到的监听器,再将这些实例经过排序后返回,返回的结果是一个SpringApplicationRunListener集合。

现在我们进入SpringApplicationRunListeners的构造方法来看一下:

	SpringApplicationRunListeners(Log log,
			Collection<? extends SpringApplicationRunListener> listeners) {
		this.log = log;
		this.listeners = new ArrayList<>(listeners);
	}

发现原来刚才获取到的集合被传入SpringApplicationRunListeners的构造方法后,只是简单的赋值给了listeners成员变量而已。在获取到监听器后,将会调用listeners.starting()来启动监听器。

在构造方法下面还能看到很多关于listeners成员变量的各种遍历操作方法: starting、environmentPrepared、contextPrepared、contextLoaded、started、running、failed。在run方法执行期间将会调用这些方法,通过下面的图来了解下这些方法具体将会在什么时候被调用。

 可以结合图片和后面的源码内容做对比,加深理解。

二、创建ApplicationArguments对象、初始化ConfigurableEnvironment

在初始化ConfigurableEnvironment之前还需要先初始化ApplicationArguments,我们只需要知道ApplicationArguments的作用是提供访问运行SpringApplication的参数几即可。接着便是初始化ConfigurableEnvironment。ConfigurableEnvironment的作用是对“环境”服务的,是提供当前运行环境的接口。

run方法中初始化ConfigurableEnvironment的相关代码如下,可以看到传入的参数是之前获取到的监听器以及ApplicationArguments。

        //初始化ConfigurableEnvironment
	ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);

进入prepareEnvironment方法,源码及注释如下:

	private ConfigurableEnvironment prepareEnvironment(
			SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		//获取或创建环境
		ConfigurableEnvironment environment = getOrCreateEnvironment();
                //配制环境
		configureEnvironment(environment, applicationArguments.getSourceArgs());
                //将ConfigurationPropertySources附加到指定环境的第一位
		ConfigurationPropertySources.attach(environment);
		listeners.environmentPrepared(environment);
                //将环境绑定到SpringApplication
		bindToSpringApplication(environment);
                //判断是否是定制的环境,如果不是,则将将环境转换为StandardEnvironment
		if (!this.isCustomEnvironment) {
			environment = new EnvironmentConverter(getClassLoader())
					.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
		}
                //将ConfigurationPropertySources附加到指定环境的第一位
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

大致流程:首先通过getOrCreateEnvironment方法获取或创建环境;然后配置环境;接着将ConfigurationPropertySources附加到指定环境的第一位,动态跟踪环境的添加和删除;调用listeners的environmentPrepared方法通知环境准备(对比之前的图片理解);将环境绑定到SpringApplication;判断是否是定制的环境,如果不是,则将将环境转换为标准环境;最后一步也是将ConfigurationPropertySources附加到指定环境的第一位,动态跟踪环境的添加和删除。

三、忽略信息配置

在初始化ConfigurableEnvironment以后,还需要通过configureIgnoreBeanInfo方法进行忽略信息配置,该方法源码如下:

	private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
                //若系统中spring.beainfo.ignore的值为null
		if (System.getProperty(
				CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {
                        //获取环境中spring.beainfo.ignore配置的值
			Boolean ignore = environment.getProperty("spring.beaninfo.ignore",
					Boolean.class, Boolean.TRUE);
                        //将系统中spring.beainfo.ignore的值设为环境中的值                           
                        System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME,
					ignore.toString());
		}
	}

可以明白这一过程大概就是:如果系统中“spring.beaninfo.ignore”的值为null,则把它设置为环境中“spring.beaninfo.ignore”配置的值,spring.beaninfo.ignore的作用是用来决定是否跳过BeanInfo类的扫描。

四、打印Banner

打印Banner其实没什么可说的,我们启动SpringBoot项目时能看到如下图的打印信息。这主要是通过printBanner方法来实现的,由于这个流程并不是很重要,我们甚至可以关闭Banner的打印,因此这个步骤就不再此过多说明。

五、创建容器

接着是创建容器ConfigurableAoolicationContext,即Spring应用上下文的创建,这个时候如果没有指定要创建的类,则会根据之前推断出来的类型进行上下文类的创建。

还记得在Spring Boot构造流程浅析中进行的web应用类型推断吗?回顾代码片段:

	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		……
		//推断Web应用类型
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		……
	}

现在就要根据这个被推断出来的应用类型进行上下文类的创建,即容器的创建。来看一下创建容器的方法:createApplicationContext()

	protected ConfigurableApplicationContext createApplicationContext() {
                //首先获取容器的变量
		Class<?> contextClass = this.applicationContextClass;
                //如果容器的变量还为null,则根据web应用类型创建容器
		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);
			}
		}
                //最后通过BeanUtils进行实例化
		return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
	}

用一句话总结就是先获取容器的变量,如果此时容器的变量还为null,那么就根据web应用类型去创建容器,最终通过BeanUtils进行实例化后返回。

六、准备容器

在创建完容器之后,接下来就是通过prepareContext方法做准备容器的工作,即Spring应用上下文的准备。来看一下prepareContext方法的源码及注释:

	private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
			SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
                //设置环境
		context.setEnvironment(environment);
                //应用上下文后置处理
		postProcessApplicationContext(context);
                //初始化context
		applyInitializers(context);
                //通知监听器context准备完成
		listeners.contextPrepared(context);
                //打印日志、启动Profile
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		//通过context拿到ConfigurableListableBeanFactory 并注册单例对象
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
		if (printedBanner != null) {
                        //注册打印日志对象
			beanFactory.registerSingleton("springBootBanner", printedBanner);
		}
		if (beanFactory instanceof DefaultListableBeanFactory) {
                        //设置是否允许覆盖注册
			((DefaultListableBeanFactory) beanFactory)
					.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
		if (this.lazyInitialization) {
			context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
		}
		//获取全部配置源
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
                //将sources中的bean加载到context中
		load(context, sources.toArray(new Object[0]));
                //通知监听器context加载完成
		listeners.contextLoaded(context);
	}

可以看到虽然这个方法整体定义为准备上下文的过程,但分析其方法内部逻辑又可以细分为两个部分:应用上下文的准备和加载。我们将准备容器的核心流程整理如下:

准备容器应用上下文的准备
  1. 设置上下文的环境ConfigurableEnvironment
  2. 应用上下文后置处理
  3. ApplicationContextInitializer初始化context
应用上下文的加载
  1. 打印日志、启动Profile
  2. 通过context拿到ConfigurableListableBeanFactory并注册单例对象
  3. 判断并设置BeanFactory单例对象是否允许覆盖
  4. 获取全部配置源
  5. 将sources中的bean全部加载到context容器中

七、刷新容器

容器准备工作做完之后接下来是刷新容器,即Spring应用上下文的刷新。

	private void refreshContext(ConfigurableApplicationContext context) {
		refresh(context);
		if (this.registerShutdownHook) {
			try {
				context.registerShutdownHook();
			}
			catch (AccessControlException ex) {
				// Not allowed in some environments.
			}
		}
	}
	protected void refresh(ApplicationContext applicationContext) {
		Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
		((AbstractApplicationContext) applicationContext).refresh();
	}

Spring应用上下文的刷新是通过refreshContext方法来完成,追溯其源码可知核心方法是其内部的refresh方法,而该方法又调用了AbstractApplicationContext中的refresh方法,我们可以通过源码来了解一下这个refresh方法内部都做了些什么。

	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			//准备刷新
			prepareRefresh();

			//通知子类刷新内部bean工厂
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			//为context准备bean工厂
			prepareBeanFactory(beanFactory);

			try {
				// 允许context的子类对bean工厂进行后置处理
				postProcessBeanFactory(beanFactory);

				// 调用context中注册为bean的工厂处理器
				invokeBeanFactoryPostProcessors(beanFactory);

				// 注册bean处理器
				registerBeanPostProcessors(beanFactory);

				// 初始化context的信息源
				initMessageSource();

				// 初始化context的事件传播器
				initApplicationEventMulticaster();

				// 初始化其他子类特殊的bean
				onRefresh();

				// 注册事件监听器
				registerListeners();

				// 实例化所有非懒加载单例
				finishBeanFactoryInitialization(beanFactory);

				// 发布对应事件
				finishRefresh();
			}

七、调用ApplicationRunner和CommandLineRunner

最后,run方法会通过执行callRunners方法来调用ApplicationRunner和CommandLineRunner,目的通过他们来实现在容器启动时执行一些操作,如果有多个实现类,可以通过@Order注解或实现Order接口来控制执行顺序。来看callRunners方法源码:

	private void callRunners(ApplicationContext context, ApplicationArguments args) {
                //准备一个list集合
		List<Object> runners = new ArrayList<>();
                //从context中获取ApplicationRunner和CommandLineRunner的bean,放入集合
		runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
		runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
                //排序
		AnnotationAwareOrderComparator.sort(runners);
                //遍历集合将ApplicationArguments 参数传入
		for (Object runner : new LinkedHashSet<>(runners)) {
			if (runner instanceof ApplicationRunner) {
				callRunner((ApplicationRunner) runner, args);
			}
			if (runner instanceof CommandLineRunner) {
				callRunner((CommandLineRunner) runner, args);
			}
		}
	}

大致流程是通过context拿到ApplicationRunner和CommandLineRunner的bean,放入集合然后排序,然后遍历集合将ApplicationArguments 参数传入执行callRunner方法。

至此,Spring Boot运行流程执行结束。

第一篇:Spring Boot自动配置原理浅析

第二篇:Spring Boot构造流程浅析

第三篇:Spring Boot运行流程浅析

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

大何向东流1997

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

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

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

打赏作者

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

抵扣说明:

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

余额充值