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。
这种设计模式有点像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;
}
但其实,这里面只是利用反射机制,构造器也都是同一个构造器数组 parameterTypes
即 new 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
,再比如 resourceLoader
,ConversionService
,可以看出这里都是在为下一步的初始化准备类加载器,类型转换器,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();
}
}
}