前言
上一篇文章我们简单聊了MySQL的数据库连接过程,写完之后自己读了一遍,感觉分析得还不够透。很多细节其实并没有真正捋清楚,所以这次干脆从Springboot的源码入手,重新从头走一遍。
main 入口分析
package com.springboot.demo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scheduling.annotation.EnableScheduling;
@MapperScan("com.springboot.demo.repository.dao")
@SpringBootApplication
@EnableScheduling
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
一个最简单的Springboot项目,启动类里就一行代码。别小看这一行,背后藏着一整套路。我们就从这行开始拆。
SpringApplication::SpringApplication
public SpringApplication(ResourceLoader resourceLoader, Class>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
构造方法的主要工作就是初始化一堆成员变量:参数列表、应用类型、监听器等等。注意这里的 WebApplicationType.deduceFromClasspath(),它会根据classpath下有没有某些类来判断是web环境还是非web环境。
getSpringFactoriesInstances
private Collection getSpringFactoriesInstances(Class type,
Class>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
Set names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
这个方法说白了就是根据传入的type,从 META-INF/spring.factories 里捞出一堆配置类名,然后一一实例化。过程中还做了去重和排序,挺严谨。
loadFactoryNames 负责读文件,createSpringFactoriesInstances 负责校验类型(必须是parameterTypes的子类)并通过反射创建实例。

SpringApplication::run
/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Ja va main method)
* @return a running {@link ApplicationContext}
*/
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
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);
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 方法里了。下面我们拆开看每一步。
StopWatch
一个计时器,先记下启动的时间点,最后用来算耗时。
变量初始化
各种临时变量的准备工作,后面会一步步用上。
configureHeadlessProperty
设置系统属性 ja va.awt.headless 为 true。说白了就是告诉操作系统:这个服务端程序不需要显示器、鼠标、键盘这些外设,如果代码里用到了图形资源,自己模拟就好了。
getRunListeners
从 spring.factories 中加载并实例化所有 SpringApplicationRunListener,这些监听器负责在启动的不同阶段广播事件。
prepareEnvironment
配置环境:加载配置文件、系统变量、命令行参数等,然后通过监听器广播环境准备完毕的事件。
printBanner
打印启动时那个标志性的横幅。
createApplicationContext
根据 webApplicationType 创建对应的 Spring 上下文实例。
exceptionReporters
初始化异常报告器,一旦启动过程中间出异常,这些报告器会负责输出友好信息。
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);
}
// Load the sources
Set
这一步做了几件事:
setEnvironment:把前面准备好的环境塞进上下文。postProcessApplicationContext:默认空实现,留给子类扩展。applyInitializers:调用之前实例化的ApplicationContextInitializer的initialize方法。listeners.contextPrepared:通知监听器上下文准备完毕。- 注册两个单例 bean:
springApplicationArguments和springBootBanner。 load:通过BeanDefinitionLoader加载各种来源的 bean(注解、XML、package 等),最终全部注册到上下文中。listeners.contextLoaded:通知监听器上下文加载完毕。
refreshContext
@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 a void 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();
}
}
}
refreshContext 是整个容器初始化的核心,调用了 AbstractApplicationContext.refresh()。下面拆解关键步骤:
- prepareRefresh:设置启动时间、初始化占位符、校验必备属性等。
- obtainFreshBeanFactory:通知子类刷新内部的 BeanFactory。
- prepareBeanFactory:为 BeanFactory 做准备工作,比如设置 ClassLoader、注册一些特殊的 bean 等。
- invokeBeanFactoryPostProcessors:重点来了——它会调用
ConfigurationClassPostProcessor.processConfigBeanDefinitions,这个方法是处理 @Configuration、@Component 等注解的入口。
咱们重点看看 processConfigBeanDefinitions 干了什么:
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List configCandidates = new ArrayList<>();
String[] candidateNames = registry.getBeanDefinitionNames();
for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
}
}
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
// Return immediately if no @Configuration classes were found
if (configCandidates.isEmpty()) {
return;
}
// ... 排序、设置BeanNameGenerator等
// Parse each @Configuration class
ConfigurationClassParser parser = new ConfigurationClassParser(...);
Set candidates = new LinkedHashSet<>(configCandidates);
Set alreadyParsed = new HashSet<>(configCandidates.size());
do {
parser.parse(candidates);
parser.validate();
Set configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
// Read the model and create bean definitions based on its content
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(...);
}
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
candidates.clear();
// ... 循环处理新发现的配置类
} while (!candidates.isEmpty());
// ... 注册 ImportRegistry
}
简单说,ConfigurationClassParser 解析 @Configuration、@ComponentScan、@Import 等注解,扫描出所有需要注册的类;ConfigurationClassBeanDefinitionReader 则负责把这些类转成 BeanDefinition 并注册到容器里。
后面还有 invokeBeanDefinitionRegistryPostProcessors、invokeBeanFactoryPostProcessors 等回调,这里就不展开了。
- registerBeanPostProcessors:注册用于拦截 bean 创建的处理器(比如 AOP 的切面)。
- initMessageSource:初始化国际化消息源。
- initApplicationEventMulticaster:初始化事件广播器。
- onRefresh:模板方法,对于 web 环境(如
ServletWebServerApplicationContext)会在这里创建内嵌 Web 服务器。 - registerListeners:把容器中实现了
ApplicationListener的 bean 注册为监听器。 - finishBeanFactoryInitialization:实例化所有非懒加载的单例 bean。像我们之前分析过的 DataSource,就是在这个阶段被创建的。
finishRefresh
结束上下文的刷新,发布 ContextRefreshedEvent 事件。
callRunners
如果容器中定义了 ApplicationRunner 或 CommandLineRunner 类型的 bean,就会调用它们的 run 方法,让应用启动后执行一些自定义逻辑。
总结
到这里,Springboot 的启动过程就串起来了。整体脉络可以概括为:
- 初始化环境
- 加载默认配置
- 初始化各类监听器和事件
- 创建上下文
- 往上下文中添加默认的 bean
- 扫描各类注解(@Component、@Bean、@Import 等),注册所有 bean 定义
- 实例化所有单例 bean
- 启动完毕,执行 Runner
整个过程虽然代码量大,但每一步都职责分明。理解了这个流程,以后遇到启动相关的问题,心里就有底了。
