目录
大家好,我是一名Java开发工程师。今天想和大家深入探讨一下Spring Boot应用的启动流程,特别是从我们熟悉的main()方法开始,到Spring容器完全初始化的整个过程。相信很多使用Spring Boot的同学都写过这样的代码:
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
但你知道这行简单的SpringApplication.run()背后发生了什么吗?让我们一起来揭开它的神秘面纱!
一、Spring Boot启动概览
Spring Boot的启动过程可以大致分为以下几个阶段:
- SpringApplication实例化阶段
- 环境准备阶段
- 应用上下文创建阶段
- 前置处理阶段
- 刷新上下文阶段(核心!)
- 后置处理阶段
今天我们要重点分析的就是第5个阶段——刷新上下文,也就是AbstractApplicationContext.refresh()方法,这是Spring容器初始化的核心。
二、SpringApplication.run()执行链路
让我们先看看SpringApplication.run()的整体调用链路:
// SpringApplication.java
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] {primarySource}, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
public ConfigurableApplicationContext run(String... args) {
// 1. 创建并启动计时器
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 2. 初始化应用上下文和异常报告器
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 3. 配置headless属性
configureHeadlessProperty();
// 4. 获取SpringApplicationRunListeners并启动
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
// 5. 准备环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// 6. 打印Banner
Banner printedBanner = printBanner(environment);
// 7. 创建应用上下文
context = createApplicationContext();
// 8. 准备上下文
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 9. 刷新上下文(核心!)
refreshContext(context);
// 10. 刷新后处理
afterRefresh(context, applicationArguments);
// 11. 停止计时器并打印启动日志
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// 12. 发布应用启动完成事件
listeners.started(context);
// 13. 执行Runner
callRunners(context, applicationArguments);
} catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// 14. 发布应用就绪事件
listeners.running(context);
} catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
三、核心:AbstractApplicationContext.refresh()源码分析
现在,让我们进入今天的重头戏——AbstractApplicationContext.refresh()方法。这是Spring容器初始化的核心方法,几乎所有重要的初始化工作都在这里完成。
// AbstractApplicationContext.java
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 1. 准备刷新上下文
prepareRefresh();
// 2. 获取新的BeanFactory,销毁之前的BeanFactory并创建一个新的
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 3. 准备BeanFactory用于使用此上下文
prepareBeanFactory(beanFactory);
try {
// 4. 允许子类对BeanFactory进行后置处理
postProcessBeanFactory(beanFactory);
// 5. 调用BeanFactoryPostProcessor
invokeBeanFactoryPostProcessors(beanFactory);
// 6. 注册BeanPostProcessor
registerBeanPostProcessors(beanFactory);
// 7. 初始化MessageSource(国际化)
initMessageSource();
// 8. 初始化事件广播器
initApplicationEventMulticaster();
// 9. 子类扩展点:初始化特殊bean
onRefresh();
// 10. 注册监听器
registerListeners();
// 11. 初始化所有非懒加载的单例bean
finishBeanFactoryInitialization(beanFactory);
// 12. 完成刷新过程,发布相应事件
finishRefresh();
} catch (BeansException ex) {
// 13. 销毁已创建的bean
destroyBeans();
// 14. 重置活动标志
cancelRefresh(ex);
throw ex;
}
}
}
让我们深入分析几个关键步骤:
1. prepareRefresh() - 准备刷新上下文
protected void prepareRefresh() {
this.startupDate = System.currentTimeMillis();
this.closed.set(false);
this.active.set(true);
// 初始化占位符资源
initPropertySources();
// 验证必要的属性
getEnvironment().validateRequiredProperties();
// 早期事件存储
this.earlyApplicationEvents = new LinkedHashSet<>();
}
2. obtainFreshBeanFactory() - 获取新的BeanFactory
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
// 子类实现此方法以提供BeanFactory
refreshBeanFactory();
return getBeanFactory();
}
在GenericApplicationContext中,这个方法的实现很简单:
@Override
protected final void refreshBeanFactory() throws IllegalStateException {
if (!this.refreshed.compareAndSet(false, true)) {
throw new IllegalStateException("GenericApplicationContext does not support multiple refresh attempts");
}
this.beanFactory.setSerializationId(getId());
}
5. invokeBeanFactoryPostProcessors() - 调用BeanFactoryPostProcessor
这是Spring Boot自动配置的关键所在:
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
// 如果运行在原生镜像中,清除反射缓存
if (!NativeDetector.inNativeImage() && beanFactory.getTempClassLoader() == null) {
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
}
}
11. finishBeanFactoryInitialization() - 初始化所有非懒加载的单例bean
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// 初始化ConversionService
if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) {
beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class);
}
// 如果没有注册嵌入值解析器,则注册默认的
if (!beanFactory.hasEmbeddedValueResolver()) {
beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
}
// 尽早初始化LoadTimeWeaverAware bean
String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
for (String weaverAwareName : weaverAwareNames) {
getBean(weaverAwareName);
}
// 停止使用临时类加载器进行类型匹配
beanFactory.setTempClassLoader(null);
// 允许缓存所有bean定义元数据
beanFactory.freezeConfiguration();
// 实例化所有剩余的非懒加载单例bean
beanFactory.preInstantiateSingletons();
}
四、关键点:Bean的实例化过程
在finishBeanFactoryInitialization()方法中调用的beanFactory.preInstantiateSingletons()是实际创建bean实例的地方:
// DefaultListableBeanFactory.java
@Override
public void preInstantiateSingletons() throws BeansException {
// 获取所有bean定义名称
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
// 遍历所有bean定义
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
if (isFactoryBean(beanName)) {
// 处理FactoryBean
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
if (bean instanceof SmartFactoryBean && ((SmartFactoryBean<?>) bean).isEagerInit()) {
getBean(beanName);
}
} else {
// 普通bean的实例化
getBean(beanName);
}
}
}
// 所有单例bean初始化完成后,调用SmartInitializingSingleton的回调
for (String beanName : beanNames) {
Object singletonInstance = getSingleton(beanName);
if (singletonInstance instanceof SmartInitializingSingleton) {
SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
smartSingleton.afterSingletonsInstantiated();
}
}
}
五、实战:自定义Spring启动扩展点
了解Spring启动流程后,我们可以利用这些知识进行扩展。例如,我们可以实现自己的BeanPostProcessor:
@Component
public class MyBeanPostProcessor implements BeanPostProcessor, Ordered {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof MySpecialBean) {
System.out.println("Before initialization of " + beanName);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof MySpecialBean) {
System.out.println("After initialization of " + beanName);
}
return bean;
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
或者实现ApplicationRunner在应用启动后执行一些逻辑:
@Component
public class MyApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("Application started with arguments: " + Arrays.toString(args.getSourceArgs()));
}
}
六、总结
Spring Boot的启动流程看似简单,实则背后隐藏着复杂的机制。通过今天的分析,我们可以总结出几个关键点:
- 环境准备:Spring Boot会先准备好运行环境,包括配置属性、Profile等
- 上下文创建:根据应用类型(Web/非Web)创建不同的应用上下文
- Bean工厂准备:创建并配置BeanFactory,这是IoC容器的基础
- 后置处理器:调用各种BeanFactoryPostProcessor和BeanPostProcessor
- Bean实例化:初始化所有非懒加载的单例Bean
- 事件发布:在整个过程中,Spring会发布各种事件通知监听器
理解这些流程对于排查启动问题、进行高级定制都非常有帮助。希望这篇文章能帮助你更深入地理解Spring Boot的启动机制!
如果你对某个具体环节还有疑问,或者想了解更多细节,欢迎在评论区留言讨论。下次我们可能会深入分析Spring Boot的自动配置原理,敬请期待!
思考题:在你的项目中,有没有遇到过Spring启动相关的问题?是如何解决的呢?


被折叠的 条评论
为什么被折叠?



