一,引言
在 Spring Boot 开发中,Spring 容器在启动时通常会 提前创建所有的 Singleton Bean。这种行为称为:
Eager Initialization(预初始化 / 饿汉式加载)
例如:
Spring Boot 启动
↓
ApplicationContext.refresh()
↓
preInstantiateSingletons()
↓
实例化所有 Singleton Bean
这种方式的优点是:
- 启动时可以提前发现错误
- 运行时性能更稳定
但在某些情况下会带来问题:
- Bean 数量很多
- Bean 初始化很耗时
- 第三方 SDK 初始化慢
例如:
Elasticsearch Client
Kafka Producer
机器学习模型加载
这时候就可以使用 延迟加载(Lazy Initialization)。
延迟加载指的是:
在 Spring 容器启动时不立即初始化 Bean,
而是在第一次使用 Bean 时才进行初始化。
这样可以:
- 减少 SpringBoot 启动时间
- 避免不必要的 Bean 初始化
二,全局延迟加载
从 Spring Boot 2.2 版本开始,SpringBoot 支持 全局延迟加载。
可以在配置文件中开启:
spring:
main:
lazy-initialization: true
启用后:
所有 Spring Bean 默认都会延迟初始化
即:
Spring Boot 启动
↓
不会创建 Bean
↓
第一次 getBean 时创建
2.1 指定 Bean 不延迟加载
如果开启了全局延迟加载,但某些 Bean 需要 提前初始化,可以使用:
@Lazy(false)
@Component
public class EagerService {
public EagerService() {
System.out.println("EagerService initialized eagerly");
}
}
此时:
该 Bean 在 Spring 启动时立即创建
三,局部延迟加载
如果只希望 某些 Bean 延迟加载,可以使用 @Lazy 注解。
@Lazy 可以作用在:
- 类
- 方法
- 注入点
3.1 在类级别使用 @Lazy
@Lazy
@Component
public class MyLazyService {
public MyLazyService() {
System.out.println("MyLazyService initialized");
}
}
效果:
Spring 启动时不会创建该 Bean
只有在第一次使用时才创建
3.2 在 @Bean 方法上使用 @Lazy
@Configuration
public class MyConfig {
@Bean
@Lazy
public MyLazyService myLazyService() {
return new MyLazyService();
}
}
class MyLazyService {
public MyLazyService() {
System.out.println("MyLazyService initialized");
}
}
效果相同:
Bean 延迟初始化
四,延迟注入
除了延迟初始化 Bean 本身,还可以 延迟依赖注入。
示例:
@Component
public class MyService {
private final MyLazyService myLazyService;
@Autowired
public MyService(@Lazy MyLazyService myLazyService) {
this.myLazyService = myLazyService;
}
public void useLazyService() {
System.out.println("Using MyLazyService...");
myLazyService.doSomething();
}
}
@Component
class MyLazyService {
public MyLazyService() {
System.out.println("MyLazyService initialized");
}
public void doSomething() {
System.out.println("Doing something...");
}
}
这里的行为是:
MyService 创建时
不会创建 MyLazyService
Spring 会注入:
MyLazyService 的代理对象
只有在 第一次调用代理对象的方法时:
myLazyService.doSomething()
Spring 才会:
创建真实 Bean
五,@Lazy 的底层原理
在 Spring Framework 中,@Lazy 的实现机制是:
代理对象(Proxy)
具体流程:
注入 Lazy Bean
↓
Spring 不创建真实对象
↓
创建 Lazy Proxy
↓
代理对象被注入
↓
第一次调用方法
↓
触发 BeanFactory.getBean()
↓
创建真实 Bean
示意:
MyService
│
▼
Lazy Proxy
│
▼
MyLazyService (第一次调用才创建)
Spring 使用的核心类:
ContextAnnotationAutowireCandidateResolver
LazyResolutionProxy
六,哪些 Bean 无法延迟加载
并不是所有 Bean 都可以 Lazy。
以下 Bean 必须在容器启动时初始化:
6.1 BeanFactoryPostProcessor
例如:
BeanFactoryPostProcessor
BeanDefinitionRegistryPostProcessor
原因:
需要在 Bean 创建前修改 BeanDefinition
6.2 BeanPostProcessor
例如:
@Component
public class MyPostProcessor implements BeanPostProcessor
原因:
用于处理其他 Bean
6.3 SmartInitializingSingleton
class A implements SmartInitializingSingleton
需要:
在所有 Singleton 创建后执行
6.4 ApplicationListener
例如:
ApplicationListener<ApplicationReadyEvent>
通常需要提前初始化。
七,Lazy 的副作用
延迟加载虽然可以优化启动速度,但也有一些副作用。
7.1 首次调用变慢
例如:
用户第一次访问接口
↓
Service 是 Lazy
↓
初始化 Bean
↓
请求变慢
7.2 错误延迟暴露
例如:
数据库配置错误
如果 Bean 是 Lazy:
系统启动成功
第一次调用才报错
可能导致生产事故。
7.3 代理带来的开销
Lazy Bean 会多一层:
Proxy
虽然开销不大,但仍然存在。
八,另一种延迟获取 Bean 的方式
Spring 提供了更灵活的方式:
ObjectProvider
示例:
@Autowired
private ObjectProvider<MyService> provider;
使用时:
MyService service = provider.getObject();
特点:
真正按需获取 Bean
更灵活
避免循环依赖
九,使用建议
建议遵循以下原则:
9.1 适合使用 Lazy 的场景
例如:
第三方 SDK
大对象初始化
初始化耗时服务
机器学习模型
搜索引擎客户端
示例:
Elasticsearch Client
Kafka Producer
MinIO Client
9.2 不建议 Lazy 的 Bean
例如:
Controller
Service
Repository
原因:
可能导致首个请求变慢
十,总结
Spring 延迟加载主要有四种方式:
| 方式 | 说明 |
|---|---|
| 全局 Lazy | spring.main.lazy-initialization=true |
| 类级 Lazy | @Lazy |
| Bean 方法 Lazy | @Bean @Lazy |
| 注入 Lazy | @Lazy 注入 |
核心原理:
Spring 通过代理对象实现 Lazy
完整流程:
Bean 注入
↓
创建 Lazy Proxy
↓
第一次调用方法
↓
BeanFactory.getBean()
↓
创建真实 Bean
合理使用 Lazy 可以:
减少 SpringBoot 启动时间
优化资源使用
但需要注意:
避免关键 Bean 延迟初始化
防止首个请求变慢