Spring boot 启动源码解读

本文的源码解读Spring boot版本是基于2.2.6,算是当前比较新的一个版本,当然刚观察了下官网,还有2.3.0.RC1,以及2.2.7这两个更新点的,但于本文影响不大。

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

首先我们知道,任何接入Spring boot框架的项目,其启动类都会写类似上图的源码,虽然只有一行代码,但它却包含了整个Spring 容器的初始化过程。跟着源码进入SpringApplication类的run方法里,整个启动的核心代码,就这么39行。接下来我们就来分析Spring boot到底为此做了哪些封装,整个流程又是如何?

// 1 返回ConfigurableApplicationContext 这个类继承的ApplicationContext
	public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		// 2 时钟类启动,初始化
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		// 3 这一步是设置了java.awt.headless这样一个系统变量,目前还不知道干啥用
		configureHeadlessProperty();
		// 4 
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			// 5
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			// 6
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			configureIgnoreBeanInfo(environment);

			// 7
			Banner printedBanner = printBanner(environment);

			// 8
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			
			// 9
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);

			// 10
			refreshContext(context);

			// 11
			afterRefresh(context, applicationArguments);

			// 12
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}

			// 13
			listeners.started(context);

			// 14
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			// 15
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			// 16
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

整个39行的代码,我们先把它做了 16个标记,先从整体上寻找前后逻辑的联系。

标记1:方法返回线

标记1 声明了run方法的返回类型是一个ConfigurableApplicationContext,这意味着我们在启动类通过SpringApplication调用run方法时,是可以手动的做一些初始设置的。并且应该是启动成功之后所做的操作,例如增加一个Listener

public static void main(String[] args) {
        SpringApplication.run(ServerApplication.class, args)
            .addApplicationListener("xxx");
    }

标记2:时钟类准备,context定义

标记2 是由Spring boot封装了一个时钟类StopWatch,并启动stopWatch,然后定义了一个ConfigurableApplicationContext,并初始化为空,看起来,这就是我们方法最后要返回的辣个类,也就是下面的大部分操作都应该是围绕这个变量进行的,那个SpringBootExceptionReporter感觉在整个流程中并不是很核心,这里先放着。

stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();

标记3:java.awt.headless (略)

这一行也暂时略过

// 3 这一步是设置了java.awt.headless这样一个系统变量,目前还不知道干啥用
		configureHeadlessProperty();

标记4:listeners 初get

SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();

这里的代码已经开始复杂了,不过大致一看,这里是根据args,也就是启动类,命令行那边传过来的参数,定义了一系列的SpringApplicationRunListener,注意这里返回的是一个SpringApplicationRunListeners,加了个s,即是一个复数,也可以当做是一个集合的封装,其实这里就是Spring常用的一种设计模式,把功能相同的对象数组封装成一个类,然后提供一个starting()的方法,本质是遍历数组执行starting()。

class SpringApplicationRunListeners {

	private final Log log;

	private final List<SpringApplicationRunListener> listeners;

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

	void starting() {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.starting();
		}
	}
	/**省略代码*/
}

可以理解为,通过getRunListeners(args)把所有能get到的Listener都starting起来。

标记5: ApplicationArguments 三板斧

// 5
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

在try catch块的第一行代码里,发现同样,这里也是一个s结尾。但点进去可能事实有些令人失望,它与SpringApplicationRunListeners 还是有所区别的,可以看到DefaultApplicationArguments是从Spring boot 1.4.1才开始有的,也不算太老,但肯定不新。

/**
 * Default implementation of {@link ApplicationArguments}.
 *
 * @author Phillip Webb
 * @since 1.4.1
 */
public class DefaultApplicationArguments implements ApplicationArguments {

	private final Source source;

	private final String[] args;

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

	@Override
	public String[] getSourceArgs() {
		return this.args;
	}

	@Override
	public Set<String> getOptionNames() {
		String[] names = this.source.getPropertyNames();
		return Collections.unmodifiableSet(new HashSet<>(Arrays.asList(names)));
	}

	@Override
	public boolean containsOption(String name) {
		return this.source.containsProperty(name);
	}

	@Override
	public List<String> getOptionValues(String name) {
		List<String> values = this.source.getOptionValues(name);
		return (values != null) ? Collections.unmodifiableList(values) : null;
	}

	@Override
	public List<String> getNonOptionArgs() {
		return this.source.getNonOptionArgs();
	}

	private static class Source extends SimpleCommandLinePropertySource {

		Source(String[] args) {
			super(args);
		}

		@Override
		public List<String> getNonOptionArgs() {
			return super.getNonOptionArgs();
		}

		@Override
		public List<String> getOptionValues(String name) {
			return super.getOptionValues(name);
		}

	}

}

args是我们传过来的参数,它不仅递给了DefaultApplicationArguments这个类自己本身,还提供给自己final修饰的一个内部属性类Source,这个Source又是自己封装的一个私有静态内部类, 且这个类继承的是SimpleCommandLinePropertySource。有点绕,有点蒙,我们先缓缓。先探究DefaultApplicationArguments外面这层是在干嘛?

抛开Source不谈,里面提供了为数不多的几个Option相关的方法,可以看到有getOptionNames(),返回的是个Set,还有getOptionValues,返回的是个List。仔细观察代码,发现无论是Set还是List,返回的都是unmodifiable的,如果你不太了解这是干嘛的,可以发现它们都继承unmodifiableCollection,这个Collection的add remove相关方法都是直接抛出异常UnsupportedOperationException,说明它是一个只读集合。

然后仔细观察方法里的代码,发现这两个方法实际都是委托给了source去调用的,整个DefaultApplicationArguments,是Resouce进行了一层封装,除了args可以直接获取外,其他方法都委托给source。

spring boot 博客源码 springboot源码解读_Source


这种设计模式有点像Bridge模式,即 args 是 Source的属性,但Source 与 args 都是DefaultApplicationArguments的属性,解耦了Source与args,至于为啥要这样做,先挖个坑看看,然后我们回到Source本身,它继承的SimpleCommandLinePropertySource,其实后面的就不用看,肯定是解析传过来的args,把args拆分成optionName 和optionValue。 optionName 和 optionValue 可能就是你启动的时候输入的

–server.port = 8081

标记6: ConfigurableEnvironment 从哪来,到哪去

ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);

如果你理解了标记4 与标记5, 那么这一部字面意思就很好理解,就是拿前面一堆的listeners, 和 applicationArguments 去 prepareEnviroment。这里面又有很复杂很深的逻辑,咱们安排下一章来分析,总之你就当,这里有个加工厂,它把listeners 和 applicationArguments 当作原料拿去加工,给我们的applicationContext 配好了环境enviroment,这里有一个问题要思考:如果说,我们通过命令行在项目启动的时候,把一些配置参数输入进来,由DefaultApplicationArguments获取后,给环境拿去做一些乱七八糟初始化的操作尚还能理解,那么listeners呢?我们哪来的那么多listeners,为什么要在准备enviroment的时候把它也带上,Spring 默认提供了哪些Listener要在这一部参与加工?

首先猜想,Spring 本身有一套Event机制,也就是基于事件监听,来达到一些模块之间相互解耦的目的,我们平时在写代码的过程中也可以通过继承ApplicationEvent或者SpringApplicationEvent来做一些业务解耦的事情,就比如业务日志这种比较通用的功能,相当于内部的消息中间件,这里不再详细赘述。

言归正传,当ConfigurableEnvironment被创建完成后,接着configureIgnoreBeanInfo(environment) 下一行代码,是为了设置一个系统变量spring.beaninfo.ignore

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());
		}
	}

那这个属性有什么用呢?来看下官方给出的文档:

System property that instructs Spring to use the Introspector.IGNORE_ALL_BEANINFO mode when calling the JavaBeans Introspector: “spring.beaninfo.ignore”, with a value of “true” skipping the search for BeanInfo classes (typically for scenarios where no such classes are being defined for beans in the application in the first place)

翻译过来就是,值为“true”时表示跳过对BeanInfo类的搜索,Spring默认给出的是false,并建议如果遇到对不存在的BeanInfo类重复的ClassLoader访问,考虑将此标志切换为“true”,以防此类访问在启动或延迟加载时很昂贵的开销,具体想了解BeanInfo的,可以{@link Introspector} .未完待续

标记7: Banner?没啥好说的

这一步没什么好说,就是利用logger或者System.out打印出一个Banner来,

Banner printedBanner = printBanner(environment);

 	//#############
	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);
	}

标记8: 千呼万唤始出来,context终于开始create了

context = createApplicationContext();
	exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);

这里已知前面几部都是为我们准备的args解析,enviroment环境,还有Listeners监听,我们的context上下文从这里也就从这里才刚刚开始创建,这里createApplicationContext() 方法在2.0.0以后利用策略模式提供两种策略,一种是servlet容器,一种是reactive web容器。这两者的差别可以先简单看作传统Servlet容器和Netty容器的比较,这里先挖个坑,排给以后茫茫久的博文里吧。

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;
	}

然后进入getSpringFactoriesInstances 方法里,发现它在开始创建实例,并未这些实例进行了排序

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;
	}

但其实,这里面只是利用反射机制,构造器也都是同一个构造器数组 parameterTypesnew Class[] { ConfigurableApplicationContext.class } 然后通过该构造器生成了一批实例,在当前这些实例 只是 SpringBootExceptionReporter 类,但不难看出的是,这两个方法既然返回的是一个泛型,那么不难猜出,之后很多容器初始化之内的操作都会调用这个getSpringFactoriesInstances 这里看起来像是整个程序的第一次调用,先mark在这里。

标记9 - 标记11: 与context的相爱相杀

prepareContext(context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);

这三步,都是上下文初始化的最核心的方法封装,我们在这里一起标记出来,方便大家有个直观的感觉,初次见面,就发现它还是先为context作了一些准备工作,把我们之前准备的所有原料,模块environment, listeners, applicationArguments, printedBanner ,都和context在这个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();
		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());
		}
		// 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);
	}

可以看出来,我们刚从上一个逻辑好入容易走到标记9这里,却发现这里边好像又得分标记,那么为了不占用太大的篇幅,这里只是作一些宏观的感觉梳理。该方法的第一步是先把环境配给了上下文,我们知道之前在环境的装配中,我们提供了很多配置属性,以及命令行参数的配置属性解析,上下文先根据这些属性值去作一些初始化的操作,也是在情理之中的设计。

因此马上:

postProcessApplicationContext(context);
	applyInitializers(context);
	listeners.contextPrepared(context);

我们从代码应该可以看出,在context 初始化之前居然还有一步postProcessApplicationContext(context) ,这里,可以看到它原来是在给自己配置一些装备,注意这里的自己不一定是指context本身,比如 beanNameGenerator,再比如 resourceLoaderConversionService,可以看出这里都是在为下一步的初始化准备类加载器,类型转换器,beanName生成器,可以理解为工具的准备。

/**
	 * Apply any relevant post processing the {@link ApplicationContext}. Subclasses can
	 * apply additional processing as required.
	 * @param context the application context
	 */
	protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
		if (this.beanNameGenerator != null) {
			context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
					this.beanNameGenerator);
		}
		if (this.resourceLoader != null) {
			if (context instanceof GenericApplicationContext) {
				((GenericApplicationContext) context).setResourceLoader(this.resourceLoader);
			}
			if (context instanceof DefaultResourceLoader) {
				((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader());
			}
		}
		if (this.addConversionService) {
			context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance());
		}
	}

准备好工具之后调用 applyInitializers 发现它跟一个接口关系密切ApplicationContextInitializer,这个东西从我们源码本身是看不出来啥的,它其实是跟SpringApplication的构造器有关,牵扯到了标记代码之外的代码了,这里也先挖个坑吧。但可以知道的是,有些自定义实现该接口的初始化是在这里开始的,也就是说除了Spring本身环境的配备以外,我们容器最开始的初始化就是在这里。

做完初始化的消息以后,立马构造事件通知监听器 listeners.contextPrepared(context) 接下来,就是通过BeanFactory注册单例模式的printedBanner到容器里,诸如此类,除此之外,该方法还会默认增加一些BeanFactoryPostProcessor 等于做了一,如 LazyInitializationBeanFactoryPostProcessor 最后

load(context, sources.toArray(new Object[0]));
		listeners.contextLoaded(context);

这里load方法和 BeanDefinitionLoader 类相关,我们又知道Spring 容器的每个实例信息其实就是BeanDefinition,这里我们也将放在下一章描述。总之做完了这些之后,监听器又会发消息同步给监听器,至于监听器在哪里,谁需要谁监听呗。

标记10执行了 refreshContext(context) 这一个方法,层层点进去又会看到这么个方法,看到这个庞大的体量,我只想说,下次再追补吧…

@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();
			}

			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();
			}
		}
	}