Skip to content

延迟初始化的 Bean (Lazy-initialized Beans)

默认情况下,ApplicationContext 的实现会在初始化过程中预先创建并配置所有的单例 (Singleton) Bean。通常这种预实例化是值得推荐的,因为这样可以立即发现配置或环境中的错误,而不是等到几小时甚至几天后才发现。

如果不希望这种默认行为,你可以通过将 Bean 定义标记为延迟初始化(Lazy-initialized),来防止单例 Bean 的预实例化。延迟初始化的 Bean 会告诉 IoC 容器仅在首次请求时才创建 Bean 实例,而不是在启动时。

在 Java 中通过 @Lazy 注解控制,在 XML 中通过 <bean/> 元素的 lazy-init 属性控制。如下例所示:

java
@Bean
@Lazy
ExpensiveToCreateBean lazy() {
	return new ExpensiveToCreateBean();
}

@Bean
AnotherBean notLazy() {
	return new AnotherBean();
}
kotlin
@Bean
@Lazy
fun lazy(): ExpensiveToCreateBean {
	return ExpensiveToCreateBean()
}

@Bean
fun notLazy(): AnotherBean {
	return AnotherBean()
}
xml
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>

<bean name="notLazy" class="com.something.AnotherBean"/>

ApplicationContext 消耗上述配置时,lazy Bean 在启动时不会被预实例化,而 notLazy Bean 被预实例化。

然而,当一个延迟初始化的 Bean 是一个延迟初始化的单例 Bean 的依赖项时,ApplicationContext 仍然会在启动时创建该延迟初始化的 Bean,因为它必须满足单例 Bean 的依赖关系。也就是说,延迟初始化的 Bean 会被注入到其他地方不延迟初始化的单例 Bean 中。

你还可以通过在 @Configuration 类上使用 @Lazy 注解,或者在 XML 的 <beans/> 元素上设置 default-lazy-init="true" 属性,来控制一组 Bean 的全局延迟初始化行为:

java
@Configuration
@Lazy
public class LazyConfiguration {
	// 不会有 Bean 被预实例化...
}
kotlin
@Configuration
@Lazy
class LazyConfiguration {
	// 不会有 Bean 被预实例化...
}
xml
<beans default-lazy-init="true">

	<!-- 不会有 Bean 被预实例化... -->

</beans>

补充教学 —— 延迟初始化的利与弊

1. 为什么要“延迟”?

  • 缩短启动时间:如果你的应用有成百上千个 Bean,其中一些初始化非常耗时(如连接远程资源、预加载大数据),延迟初化可以让你在开发调试时更快地启动应用。
  • 节省内存:对于某些在整个应用生命周期中可能根本不会被用到的“边缘功能” Bean,不创建它们可以节省内存。

2. 为什么 Spring 默认不“延迟”?

  • 早起早发现:Spring 的哲学是“Fail Fast”。如果在启动时就报错(如类找不到、属性没填、依赖循环),总好过在用户访问请求时突然崩掉。
  • 减少请求抖动:如果在收到用户第一个请求时才去初始化复杂的 Bean,会导致该请求的响应时间异常变长(冷启动问题)。

3. 最具迷惑性的点:依赖导致“被动激活” 这是初学者最容易掉坑里的地方:

  • 你给 Bean B 加了 @Lazy
  • 你的 Bean A(默认非延迟)引用了 Bean B
  • 结果:启动时 Bean B 还是会被创建!
  • 原理:Spring 必须保证 Bean A 创建好。因为 A 依赖 B,所以它会“强迫” B 提前实例化。如果你希望 B 真的延迟,通常 A 也得声明为延迟引用。

4. 总结:什么时候用?

  • 开发环境:为了启动快,可以全局开启 default-lazy-init
  • 生产环境:除非某个 Bean 极其笨重且使用频率极低,否则建议保持默认的预实例化行为。

Based on Spring Framework.