深入理解 Spring 的 Lazy Loading:原理、实现与应用场景

延迟加载(Lazy Loading)是 Spring 容器管理 Bean 的一种策略,指 只有在需要时(调用 getBean() 方法获取 Bean 时)才会实例化该 Bean。这是 Spring 提供的一种优化机制,用于提高启动效率和降低资源占用。


1. 延迟加载的含义

  • 在延迟加载模式下,Spring 容器初始化时不会立即实例化所有 Bean,而是等到真正需要使用时(即调用 getBean() 方法时),才创建 Bean 实例。
  • 如果不启用延迟加载(非 Lazy 模式),则所有单例(singleton) Bean 会在容器启动时立即实例化。

2. 延迟加载的优点

  1. 节省资源:

    • 容器启动时不需要加载和创建所有 Bean,启动速度更快。
    • 避免不必要的对象实例化,降低内存占用,尤其是对于未使用的 Bean。
  2. 适合轻量级应用:

    • 在资源有限的环境中(如嵌入式系统),延迟加载可以显著优化性能。
    • 在开发或测试阶段,也可以通过延迟加载缩短启动时间。
  3. 按需加载:

    • 只有在确实需要使用某个 Bean 时,才会创建它的实例,避免初始化不必要的逻辑。

3. 延迟加载的实现方式

(1) 使用 @Lazy 注解

在类或方法上添加 @Lazy 注解,可以让单例 Bean 延迟加载。

示例代码:

复制代码
@Component
@Lazy // 延迟加载
public class ExpensiveBean {
    public ExpensiveBean() {
        System.out.println("ExpensiveBean created!");
    }
}

测试代码:

复制代码
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println("Container initialized.");
ExpensiveBean bean = context.getBean(ExpensiveBean.class); // 此时 Bean 才会创建

运行结果:

复制代码
Container initialized.
ExpensiveBean created!

解释:

  • @Lazy 生效后,ExpensiveBean 不会在容器启动时被创建。
  • 只有在调用 getBean() 时,ExpensiveBean 才会被实例化。

(2) 如果未使用 @Lazy

代码:

复制代码
@Configuration
public class AppConfig {
    @Bean
    public ExpensiveBean expensiveBean() {
        return new ExpensiveBean(); // 无 @Lazy,默认预加载
    }
}

运行结果:

复制代码
ExpensiveBean created!
Container initialized.

解释:

  • 默认情况下,ApplicationContext 会在启动时实例化所有单例 Bean,所以在输出 "Container initialized." 前就会创建 ExpensiveBean。

(3) XML 配置方式

在 XML 文件中为 Bean 配置延迟加载:

复制代码
<bean id="expensiveBean" class="com.example.ExpensiveBean" lazy-init="true"/>

(4) 在 @Configuration 中使用

对于使用 Java 配置的项目,可以在配置类中的 @Bean 方法上添加 @Lazy:

复制代码
@Configuration
public class AppConfig {
    @Bean
    @Lazy
    public ExpensiveBean expensiveBean() {
        return new ExpensiveBean();
    }
}

4. 延迟加载的默认行为

(1) BeanFactory 的默认行为
  • BeanFactory 是一个轻量级容器,默认使用延迟加载策略。
  • 它不会在容器启动时实例化任何 Bean,而是等到调用 getBean() 方法时才实例化。
(2) ApplicationContext 的默认行为
  • ApplicationContext 是 Spring 容器的常用实现,默认会在启动时预加载所有单例(singleton) Bean。
  • 例外情况:
    • 如果使用 @Lazy 注解或 XML 中的 lazy-init="true",单例 Bean 会延迟加载。
    • 非单例(prototype Scope)的 Bean 本身默认是延迟加载的。

5. 延迟加载与非延迟加载的对比

行为 延迟加载 非延迟加载
实例化时机 调用 getBean() 时实例化 容器启动时实例化
适用范围 需要优化启动时间或资源消耗的场景 高性能服务器或需要预加载依赖的场景
容器类型 BeanFactory 默认延迟加载 ApplicationContext 默认非延迟加载
预加载的 Bean 无(除非显式调用) 单例 Bean 默认全部实例化
性能表现 启动速度快,可能会导致首次调用延迟 启动时占用更多内存,运行时性能更高

6. 示例场景

  1. 资源密集型对象的加载:

    • 如果某个 Bean 的创建非常耗时(例如连接外部服务),使用延迟加载可以避免容器启动时的性能瓶颈。
  2. 开发与测试阶段:

    • 在开发和测试中,延迟加载可以缩短容器的启动时间,提升开发效率。
  3. 条件性加载:

    • 某些 Bean 只有在特定条件下才会被使用,延迟加载可以避免不必要的资源消耗。

7. 总结

  • 延迟加载的本质: Bean 只有在第一次使用时才会被实例化。
  • 优点: 减少容器启动时的资源消耗,适合轻量级或资源受限的场景。
  • 默认行为: BeanFactory 默认延迟加载,而 ApplicationContext 默认预加载单例 Bean。
  • 实现方式: 使用 @Lazy 注解或 XML 配置轻松启用延迟加载。

通过灵活地使用延迟加载,可以显著优化应用程序的启动时间和资源利用率,特别是在复杂项目或资源密集型应用中。

相关推荐
高频因子挖掘机13 分钟前
量化回测应该保存不复权数据吗?从数据口径到收益计算的工程实践
后端·github·api
高频因子挖掘机1 小时前
实时行情为什么不应该逐只股票请求?批量查询与标的池管理实践
后端·github·api
程序猿DD6 小时前
OctaFuse Gateway 2.15.0:MCP 聚合、向量 Embeddings 与官方目录同步
后端·agent
蛋蛋的就会顺顺的9 小时前
hot100 p0——栈,贪心算法,动态规划
java·前端·javascript·算法·leetcode·力扣
余槐i9 小时前
PostgreSQL 函数索引报错 must be immutable:3种绕法与代价
数据库·后端·sql·postgresql·性能优化
需要8269 小时前
Spring Bean 生命周期:从实例化到销毁的完整钩子
java·后端·spring
小 黄 鸡9 小时前
HashMap 面试必会知识点详解(JDK 8)
java·面试·职场和发展
死磕音视频10 小时前
让小模型做选择题:限定候选项并读取 logits 概率的入门实践(附 Python 代码)
开发语言·python·大模型·文本分类·transformers
凤山老林10 小时前
Spring Boot 接入国密 SM2/SM3/SM4 的一些实践记录
spring boot·后端·python
伯恩bourne11 小时前
Spring Boot Bean 注册的两种方式:@Component 家族 vs @Configuration + @Bean
java·spring boot·后端