续接上文,本章继续。Spring 是企业级开发的核心框架之一,而 Spring Boot 进一步简化了 Spring 应用的开发和部署。了解 Spring 容器的初始化过程以及 Spring Boot 的启动流程,不仅有助于排查异常,还能帮助我们更好地理解 Spring 的核心机制。
本文将通过源码视角,深入解析:
Spring 容器是一个管理 Bean 的工厂,它负责 Bean 的创建、依赖注入、生命周期管理和销毁。Spring 容器主要有以下两种:
Q: 为什么在实际开发中通常推荐使用 ApplicationContext 而不是 BeanFactory?
A: 因为ApplicationContext
提供了更多的功能,例如事件监听、国际化支持、AOP、事务管理等,同时启动速度也更快。
Spring 中,容器的初始化主要由 refresh()
方法驱动。它位于 AbstractApplicationContext
中。
源码路径:org.springframework.context.support.AbstractApplicationContext
@Override
public void refresh() throws BeansException {
synchronized (this.startupShutdownMonitor) {
// 1. 初始化环境配置
prepareRefresh();
// 2. 创建 BeanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 3. BeanFactory 配置
prepareBeanFactory(beanFactory);
try {
// 4. 执行 BeanFactoryPostProcessor
invokeBeanFactoryPostProcessors(beanFactory);
// 5. 注册 BeanPostProcessor
registerBeanPostProcessors(beanFactory);
// 6. 初始化消息源
initMessageSource();
// 7. 初始化事件广播器
initApplicationEventMulticaster();
// 8. 执行子类的 onRefresh 方法
onRefresh();
// 9. 初始化所有非懒加载的单例 Bean
finishBeanFactoryInitialization(beanFactory);
// 10. 发布容器启动事件
finishRefresh();
} catch (BeansException ex) {
throw ex;
}
}
}
Q: 你能简单讲解一下refresh()
方法中的核心步骤吗?
A:
refresh()
主要完成环境准备、Bean 加载、依赖注入、事件发布等任务。关键步骤包括:
- prepareRefresh():初始化配置,加载环境变量。
- obtainFreshBeanFactory():创建 BeanFactory 并加载 BeanDefinition。
- invokeBeanFactoryPostProcessors():执行
BeanFactoryPostProcessor
,如配置类的解析。- finishBeanFactoryInitialization():实例化所有非懒加载的单例 Bean。
- finishRefresh():发布
ContextRefreshedEvent
通知监听器。
Spring 为了解决 Setter 注入 场景下的循环依赖,采用了 三级缓存机制:
源码示例:
singletonFactories.put(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
当 A 依赖 B,B 又依赖 A 时,Spring 会通过 ObjectFactory 提前暴露 A 的引用,避免循环依赖导致的 BeanCurrentlyInCreationException
。
Q: 为什么 Spring 需要三级缓存而不是直接用二级缓存解决循环依赖?
A:
三级缓存相比二级缓存更加灵活。在对象未完成初始化前,Spring 可以通过 ObjectFactory 提供一个代理对象,避免暴露不完整的 Bean,从而增强安全性。
具体也可查看上一篇进行详细了解。
Spring Boot 的启动入口通常是 SpringApplication.run()
:
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return new SpringApplication(primarySource).run(args);
}
启动监听器加载
SpringApplicationRunListeners
,监听启动事件。环境准备
ConfigurableEnvironment
,加载外部配置文件。创建容器
AnnotationConfigServletWebServerApplicationContext
或其他合适的 ApplicationContext。刷新容器
refresh()
,完成 Bean 的实例化与初始化。执行 Runner
CommandLineRunner
和 ApplicationRunner
。
Q: Spring Boot 的自动配置原理是什么?
A:
Spring Boot 使用@EnableAutoConfiguration
注解,通过SpringFactoriesLoader
加载spring.factories
文件中的自动配置类。这些类会在启动时生效,实现自动配置。
refresh()
完成环境配置、Bean 加载、依赖注入和事件发布。Spring 的事件机制基于 观察者模式,主要由以下三部分组成:
ApplicationEvent
,创建自定义事件类。ApplicationListener
接口或使用 @EventListener
注解监听事件。ApplicationEventPublisher
发布事件。ApplicationContext
本身实现了 ApplicationEventPublisher
接口,可以直接发布事件。SimpleApplicationEventMulticaster
进行分发,通知所有监听器。@Async
注解实现。applicationContext.publishEvent(new MyCustomEvent(this));
Q: Spring 的事件机制的优势是什么?
A: Spring 事件机制解耦了事件的生产者和消费者,使得组件之间的通信更加灵活,尤其适合应用于事务提交后、资源释放等场景。
优化 Spring Boot 的启动速度可以从以下几个方面入手:
@ConditionalOnMissingBean
、@ConditionalOnProperty
等条件注解,按需加载 Bean。spring.main.lazy-initialization=true
进行懒加载。spring.autoconfigure.exclude
排除不需要的自动配置类。META-INF/spring.factories
定义精简的自动配置类。Q: Spring Boot 使用 AOT 编译的主要作用是什么?
A: AOT 编译可以在构建阶段生成静态代码,避免运行时的反射和字节码增强,从而加快启动速度。
在实际开发中,常见的 Spring 启动相关问题包括:
场景:
@Autowired
进行字段注入时容易出现循环依赖问题。解决方法:
allowCircularReferences
。spring:
main:
allow-circular-references: true
场景:
解决方法:
@ConfigurationProperties
进行类型安全的配置管理。spring-boot-configuration-processor
生成自动提示。场景:
解决方法:
server:
port: 0 # 使用随机端口
lsof -i :8080
或 netstat
查看占用端口的进程并关闭。场景:
ClassNotFoundException
或 NoSuchMethodError
。解决方法:
mvn dependency:tree
分析依赖冲突。spring-boot-dependencies
中推荐的版本。场景:
解决方法:
spring.datasource.hikari
优化连接池配置。通过以上,应该可以很快了解初始化跟启动的流程,也能更快的去排查跟定位问题,已经可以调用Spring先关的API进行相关业务逻辑的编写。
如果你还有其他问题或想了解更多,欢迎留言交流!