springboot系列文章之启动原理详解

前言

还是从SpringBoot的启动类说起,这篇文章主要分析启动类中的SpringApplication

@SpringBootApplication
public class Application {


    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
复制代码

可以看出main函数中重要的就是SpringApplication.run(),这可以分为两部分来探讨:

  • SpringApplication的构造过程
  • SpringApplication的run()方法

SpringApplication的初始化

首先进入SpringApplication的构造函数,先是单个参数的构造方法,后进入两个参数的构造方法,ResourceLoader是Spring的资源加载器,这里没有自定义的ResourceLoader传入,所以为NULL,而primarySources参数就是我们传入的Application.class启动类

public SpringApplication(Class<?>... primarySources) {
		this(null, 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));
		//1. 推断应用类型
		this.webApplicationType = deduceWebApplicationType();
		//2. initializer初始化模块,加载ApplicationContextInitializer
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
		//3. 加载监听器
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		//4. 配置应用main方法所在的类
		this.mainApplicationClass = deduceMainApplicationClass();
	}
复制代码

SpringApplication的初始化主要包括以下4个步骤:

  • 推断应用类型
  • 加载初始化构造器ApplicationContextInitializer
  • 创建应用监听器
  • 设置应用main()方法所在的类

1. 推断应用类型

this.webEnvironment=deduceWebApplicationType(); 判断应用的类型,是否是servlet应用还是reactive应用或者是none,webEnvironment中定义了这三种类型

webApplication
web

2. 加载初始化构造器ApplicationContextInitializer

setInitializers((Collection)getSpringFactoriesInstances(ApplicationContextInitializer.class)): 通过SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationContextInitializer

init
进入loadFactoryNames方法,然后 进入loadSpringFactories方法,获取当前ClassLoader下的所有META-INF/spring.factories文件的配置信息

而后通过loadSpringFactories(classloader).getOrDefault(factoryClassName,Collections.emptyList()) 从所有META-INF/spring.factories文件的配置信息的map中获取指定的factory的值

laod
spring
默认情况下,从 spring.factories 文件找出的 key 为 ApplicationContextInitializer 的类有如上图中所示4种

对于 ApplicationContextInitializer,它是应用程序初始化器,做一些初始化工作

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

	/**
	 * Initialize the given application context.
	 * @param applicationContext the application to configure
	 */
	void initialize(C applicationContext);

}
复制代码

3. 创建应用监听器

setListeners()方法与setInitializers()方法类似,只不过它是使用SpringFactoriesLoader在应用的classpath的META-INT/spring.factories中查找并加载所有可用的ApplicationListener

		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

复制代码

a
ApplicationListener,应用程序事件(ApplicationEvent)监听器:

public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
	void onApplicationEvent(E event);
}
复制代码

更详细的分析可以参阅我之前的文章: springboot系列文章之启动时初始化数据

4. 设置应用main()方法所在的类

在SpringApplication构造函数的最后一步,根据调用栈推断并设置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;
	}
复制代码

SpringApplication的run方法

SpringApplication实例初始化完成并且完成设置后,就可以开始run方法的逻辑了,对于这个run方法我将分为以下几点进行逐步剖析,而StopWatch是一个工具类,主要是方便记录程序运行时间,这里就不仔细介绍了。

	public ConfigurableApplicationContext run(String... args) {
	        //构造一个任务执行观察期
		StopWatch stopWatch = new
		StopWatch();
		//开始执行,记录开始时间
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		//1
		configureHeadlessProperty();
		//2
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
	
		     //3
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
			//4
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
			//5
			configureIgnoreBeanInfo(environment);
			//6
			Banner printedBanner = printBanner(environment);
			//7
			context = createApplicationContext();
			//8
			exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			//9
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
			//10
			refreshContext(context);
			//2.0版本中是空实现
			//11
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
			//12
			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;
	}
复制代码

SpringApplication的run方法主要分为以下几步:

  • Headless模式设置
  • 加载SpringApplicationRunListeners监听器
  • 封装ApplicationArguments对象
  • 配置环境模块
  • 根据环境信息配置要忽略的bean信息
  • Banner配置SpringBoot彩蛋
  • 创建ApplicationContext应用上下文
  • 加载SpringBootExceptionReporter
  • ApplicationContext基本属性配置
  • 更新应用上下文
  • 查找是否注册有CommandLineRunner/ApplicationRunner

1. Headless模式设置

configureHeadlessProperty()设置 headless 模式,即设置系统属性java.awt.headless,它是J2SE的一种模式,用于在缺少显示屏,键盘,或者鼠标时的系统配置,该属性会被设置为true,更多的信息可以参考这里

private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";
    ...
private void configureHeadlessProperty() {
		System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, System.getProperty(
				SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
	}
复制代码

2. 加载SpringApplicationRunListeners监听器

		SpringApplicationRunListeners listeners = getRunListeners(args);
复制代码

getRunListeners(args)也是通过 SpringFactoriesLoaderMETA-INF/spring.factories查找到并加载的SpringApplicationRunListener。该类实际上是监听SpringApplication的run方法的执行

	private SpringApplicationRunListeners getRunListeners(String[] args) {
		Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
		return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
				SpringApplicationRunListener.class, types, this, args));
	}
	
	.....
	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
		//通过SpringFactoriesLoader可以查找到并加载的SpringApplicationRunListner
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
				classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

复制代码

这里的SpringApplicationRunListener监听器与SpringApplication时加载的ApplicationListener监听器不同,SpringApplicationRunListener是SpringBoot新增的类,SpringApplicationRunListener目前只有一个实现类EventPublishingRunListener。虽然说是新增的, 但是它们之间是有联系的,它们之间的的关系是通过ApplicationEventMulticaster广播出去的SpringApplicationEvent所联系起来的

startup

更详细的分析请参阅 :SpringBoot源码分析之SpringBoot的启动过程

3. 封装ApplicationArguments对象

将args参数封装成 ApplicationArguments 对象

	public DefaultApplicationArguments(String[] args) {
		Assert.notNull(args, "Args must not be null");
		this.source = new Source(args);
		this.args = args;
	}
复制代码

官网对 ApplicationArguments 的解释如下

args

4. 配置环境模块

根据listenersapplicationArguments 创建并配置当前SpringBoot应用将要使用的Enviroment

	private ConfigurableEnvironment prepareEnvironment(
			SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		// Create and configure the environment
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		listeners.environmentPrepared(environment);
		bindToSpringApplication(environment);
		if (this.webApplicationType == WebApplicationType.NONE) {
			environment = new EnvironmentConverter(getClassLoader())
					.convertToStandardEnvironmentIfNecessary(environment);
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}
复制代码

遍历调用所有SpringApplicationRunListener的enviromentPrepared()方法就是宣告当前SpringBoot应用使用的Enviroment准备好了

5. 根据环境信息配置要忽略的bean信息

	private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
		if (System.getProperty(
				CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {
			Boolean ignore = environment.getProperty("spring.beaninfo.ignore",
					Boolean.class, Boolean.TRUE);
			System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME,
					ignore.toString());
		}
	}
复制代码

6. Banner配置SpringBoot彩蛋

打印banner标志,就是启动SpringBoot项目时出现的Spring字样,当然我们也可以自定义banner,这里就不多说了

private Banner printBanner(ConfigurableEnvironment environment) {
		if (this.bannerMode == Banner.Mode.OFF) {
			return null;
		}
		ResourceLoader resourceLoader = (this.resourceLoader != null)
				? this.resourceLoader : new DefaultResourceLoader(getClassLoader());
		SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(
				resourceLoader, this.banner);
		if (this.bannerMode == Mode.LOG) {
			return bannerPrinter.print(environment, this.mainApplicationClass, logger);
		}
		return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
	}
复制代码

7. 创建ApplicationContext应用上下文

createApplicationContext()根据用户是否明确设置了applicationContextClass类型以及SpringApplication初始化阶段的推断结果,决定该为当前SpringBoot应用创建什么类型的ApplicationContext并创建完成。

public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
        + "annotation.AnnotationConfigApplicationContext";
public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot."
        + "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
        "org.springframework.web.context.ConfigurableWebApplicationContext" };	
        
        
protected ConfigurableApplicationContext createApplicationContext() {
    //用户是否明确设置了applicationContextClass,在SpringApplication中有对应的setter方法
    Class<?> contextClass = this.applicationContextClass;
        //如果没有主动设置
    if (contextClass == null) {
        try {
            //判断当前应用的类型,也就是之前SpringApplication初始化阶段的推断结果
            switch (this.webApplicationType) {
            //servlet应用程序
            case SERVLET: 
                contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
                break;
            //reactive响应式程序
            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);
}
复制代码

在SpringBoot官网对ApplicationContext的类型是如下定义的:

Applicaiton

  • 当SpringMVC存在的时候,就使用AnnotationConfigServletWebServerApplicationContext
  • 当SpringMVC不存在的时候,Spring WebFlux响应式存在的时候,使用AnnotationConfigReactiveWebServerApplicationContext
  • 如果以上都不是,默认就用AnnotationConfigApplicationContext
  • SpringApplication存在设置ApplicationContext的方法,在JUnit测试中使用SpringApplication通常要设置ApplicationContext

8. 加载SpringBootExceptionReporter

exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
复制代码
	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
		Set<String> names = new LinkedHashSet<>(
				SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
				classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}
复制代码

这里也是通过SpringFactoriesLoader加载META-INF/spring.factories中key为SpringBootExceptionReporter的全类名的value值

  • SpringBootExceptionReporter是一个回调接口,用于支持对SpringApplication启动错误的自定义报告。里面就一个报告启动失败的方法
  • 其实现类:org.springframework.boot.diagnostics.FailureAnalyzers
    用于触发从spring.factories加载的FailureAnalyzerFailureAnalysisReporter实例

9. ApplicationContext基本属性配置

	private void prepareContext(ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
		//设置应用的环境
		context.setEnvironment(environment);
		//对 context 进行了预设置
		postProcessApplicationContext(context);
		applyInitializers(context);
		遍历调用SpringApplicationRunListener的contextPrepared()方法,通告SpringBoot应用使用的ApplicationContext准备好了
		listeners.contextPrepared(context);
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}

		// Add boot specific singleton beans
		context.getBeanFactory().registerSingleton("springApplicationArguments",
				applicationArguments);
		if (printedBanner != null) {
			context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
		}

		// Load the sources
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		load(context, sources.toArray(new Object[0]));
		//遍历调用SpringApplicationRunListener的contextLoaded()方法,通告ApplicationContext装填完毕
		listeners.contextLoaded(context);
	}
复制代码

1). applyInitializers(context);

	protected void applyInitializers(ConfigurableApplicationContext context) {   
		for (ApplicationContextInitializer initializer : getInitializers()) {
			Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
					initializer.getClass(), ApplicationContextInitializer.class);
			Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
			initializer.initialize(context);
		}
	}
复制代码

遍历调用这些ApplicationContextInitializer的initialize(applicationContext)方法来对已经创建好的ApplicationContext进行进一步的处理

2). load(ApplicationContext context, Object[] sources)

protected void load(ApplicationContext context, Object[] sources) {
		if (logger.isDebugEnabled()) {
			logger.debug(
					"Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
		}
		BeanDefinitionLoader loader = createBeanDefinitionLoader(
				getBeanDefinitionRegistry(context), sources);
		if (this.beanNameGenerator != null) {
			loader.setBeanNameGenerator(this.beanNameGenerator);
		}
		if (this.resourceLoader != null) {
			loader.setResourceLoader(this.resourceLoader);
		}
		if (this.environment != null) {
			loader.setEnvironment(this.environment);
		}
		loader.load();
	}
复制代码

设置资源加载器,加载各种beans到ApplicationContext对象中

10. 更新应用上下文

	private void refreshContext(ConfigurableApplicationContext context) {
		refresh(context);
		if (this.registerShutdownHook) {
			try {
				context.registerShutdownHook();
			}
			catch (AccessControlException ex) {
				// Not allowed in some environments.
			}
		}
	}
复制代码

进入内部的refresh()方法,准备环境所需的bean工厂,通过工厂产生环境所需的bean,重点就是产生bean

	@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// 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);

				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				initMessageSource();

				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				onRefresh();

				// Check for listener beans and register them.
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				finishRefresh();
			}
复制代码

11. afterRefresh()

上下文刷新后调用该方法,目前没有操作

	protected void afterRefresh(ConfigurableApplicationContext context,
			ApplicationArguments args) {
	}
复制代码

12. callRunner()

	private void callRunners(ApplicationContext context, ApplicationArguments args) {
		List<Object> runners = new ArrayList<>();
		runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
		runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
		AnnotationAwareOrderComparator.sort(runners);
		for (Object runner : new LinkedHashSet<>(runners)) {
			if (runner instanceof ApplicationRunner) {
				callRunner((ApplicationRunner) runner, args);
			}
			if (runner instanceof CommandLineRunner) {
				callRunner((CommandLineRunner) runner, args);
			}
		}
	}
复制代码

查找当前的ApplicationContext中是否注册有CommandLineRunner或者ApplicationRunner,如果有,就遍历执行他们。

SpringBoot启动流程总结

上面从SpringApplication的初始化到SpringApplication.run()方法执行,基本上按照其内部函数调用的顺序一步一步分析下来,内容非常多,很容易把人搞晕。在网上发现一张图,图出自SpringBoot启动流程解析,画的比较清楚明白,把SpringBoot启动整个流程都包含进来了

appl
再总结下run方法中最关键的几步:

  • 加载SpringApplicationRunListeners监听器
  • 配置环境模块
  • 创建ApplicationContext应用上下文
  • ApplicationContext基本属性配置
  • 更新应用上下文,产生环境所需要的bean

小结

上面的分析都是基于SpringBoot2.0版本,在之前的版本,内容上可能有些偏差,大体思路是差不多的。在阅读源码的过程中,看了很多前人分析的博客文章,也借鉴了他们的分析流程,有点「前人栽树,后人乘凉」的感觉,现在「取之网络,再回馈之网络」

参考资料 & 鸣谢

猜你喜欢

转载自juejin.im/post/5b79a6e651882542aa1b2c22