网站Logo 苏叶的belog

Spring延迟加载

wdadwa
1
2026-03-05

一,引言

在 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 延迟加载主要有四种方式:

方式说明
全局 Lazyspring.main.lazy-initialization=true
类级 Lazy@Lazy
Bean 方法 Lazy@Bean @Lazy
注入 Lazy@Lazy 注入

核心原理:

Spring 通过代理对象实现 Lazy

完整流程:

Bean 注入
   ↓
创建 Lazy Proxy
   ↓
第一次调用方法
   ↓
BeanFactory.getBean()
   ↓
创建真实 Bean

合理使用 Lazy 可以:

减少 SpringBoot 启动时间
优化资源使用

但需要注意:

避免关键 Bean 延迟初始化
防止首个请求变慢
动物装饰