Spring @Lazy 详解及详细源码展示

@Lazy 是 Spring 框架中用于延迟初始化 Bean的核心注解,其核心作用是将 Bean 的实例化时机从“容器启动时”推迟到“首次被使用时”,从而减少应用启动时间、降低内存占用,尤其适用于初始化成本高或非立即使用的 Bean。以下从注解定义、源码解析、核心功能、使用场景注意事项展开详细说明。


一、@Lazy 注解的定义与源码解析

@Lazy 位于 org.springframework.context.annotation 包中,其源码定义如下(简化版):

@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lazy {

    /**
     * 是否延迟初始化(默认 true,即延迟;false 表示立即初始化)
     */
    boolean value() default true;

    /**
     * 条件判断(可选,满足条件时才延迟初始化)
     */
    String condition() default "";
}

关键属性说明

  • value:布尔值,控制是否启用延迟初始化(默认 true)。
  • condition:SpEL 表达式(可选),仅当表达式结果为 true 时,才启用延迟初始化(用于条件化延迟)。

二、核心功能:延迟初始化的实现机制

1. 默认行为:立即初始化

Spring 容器默认在启动时初始化所有单例 Bean(singleton 作用域),无论是否被使用。这可能导致:

  • 启动时间过长(尤其是初始化成本高的 Bean)。
  • 内存浪费(未使用的 Bean 占用资源)。

2. @Lazy 的延迟初始化逻辑

当 Bean 被 @Lazy 标记后,Spring 会:

  1. 不立即创建实例:容器启动时不初始化该 Bean,仅记录其延迟初始化的配置。
  2. 首次使用时创建:当第一次通过 @AutowiredgetBean() 等方式获取该 Bean 时,才触发实例化。

3. 与作用域的协同

@Lazy 对不同作用域的 Bean 影响不同:

作用域默认初始化时机@Lazy 效果
singleton(单例)容器启动时初始化延迟到首次使用时初始化(全局仅一次)。
prototype(原型)每次 getBean() 时初始化无变化(原型 Bean 本就每次新建,@Lazy 不影响)。
request/session(Web 作用域)作用域创建时初始化延迟到作用域首次使用时初始化(如 HTTP 请求首次访问时)。

三、典型使用场景与示例

1. 高初始化成本的 Bean

例如,数据库连接池(如 HikariCP)、缓存客户端(如 RedisTemplate)等,初始化时需要加载大量配置或建立长连接,延迟初始化可显著减少启动时间。

示例

@Service
@Lazy // 延迟初始化(首次使用时创建)
public class RedisClient {

    private final RedisTemplate<String, Object> redisTemplate;

    // 构造器初始化成本高(连接 Redis 服务器)
    public RedisClient(RedisConnectionFactory connectionFactory) {
        this.redisTemplate = new RedisTemplate<>();
        this.redisTemplate.setConnectionFactory(connectionFactory);
        this.redisTemplate.afterPropertiesSet(); // 触发连接
    }

    public void setValue(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }
}

效果:应用启动时,RedisClient 不会立即连接 Redis,而是在第一次调用 setValue 时才初始化。

2. 条件化延迟加载

结合 condition 属性,仅当满足特定条件时才延迟初始化(如根据环境变量或配置属性)。

示例

@Service
@Lazy(condition = "#{environment.getProperty('feature.analytics.enabled') == 'true'}") // 仅当 analytics 功能启用时延迟初始化
public class AnalyticsService {

    // 初始化成本高(连接分析服务)
    public AnalyticsService(AnalyticsClient client) {
        // ...
    }
}

效果:若 application.propertiesfeature.analytics.enabled=false,则 AnalyticsService 会被立即初始化(即使未使用);若为 true,则延迟到首次使用时初始化。

3. 避免循环依赖

在循环依赖场景中,@Lazy 可打破“构造器注入导致的循环依赖死锁”。例如,A 依赖 B,B 依赖 A,若两者均使用构造器注入,Spring 无法解决循环依赖;但通过 @Lazy 延迟其中一个的初始化,可避免启动时报错。

示例

@Service
public class A {

    private final B b;

    // 构造器注入 B(但 B 被 @Lazy 标记)
    @Autowired
    public A(@Lazy B b) {
        this.b = b;
    }
}

@Service
@Lazy // B 延迟初始化
public class B {

    private final A a;

    // 构造器注入 A(A 已初始化,无循环依赖)
    @Autowired
    public B(A a) {
        this.a = a;
    }
}

效果:A 初始化时,B 被标记为延迟,因此不会立即触发 B 的构造器(避免循环依赖)。当首次使用 B 时,A 已存在,可正常初始化。


四、源码实现细节与关键类

1. LazyAnnotationBeanPostProcessor

Spring 处理 @Lazy 的核心类,继承自 InstantiationAwareBeanPostProcessorAdapter,负责将 @Lazy 标记的 Bean 转换为“延迟初始化”的代理对象。其关键逻辑如下:

(1)扫描 @Lazy 注解

在解析 @Bean 方法或 @Component 类时,ConfigurationClassParser 会扫描 @Lazy 注解,获取其 valuecondition 属性。

(2)生成代理对象

@Lazy 启用(value=truecondition 满足),LazyAnnotationBeanPostProcessor 会为该 Bean 生成一个延迟初始化代理(通常是 CGLIB 或 JDK 动态代理)。该代理在首次调用方法时,才会触发实际的 Bean 实例化。

(3)条件判断

@Lazycondition 属性存在,LazyAnnotationBeanPostProcessor 会通过 ExpressionEvaluator 解析 SpEL 表达式,判断是否启用延迟初始化。

2. BeanDefinitionlazyInit 属性

Spring 的 BeanDefinition 接口有一个 isLazyInit() 方法,用于标记该 Bean 是否延迟初始化。LazyAnnotationBeanPostProcessor 会根据 @Lazy 的配置设置该属性为 true,从而告知 Spring 容器延迟初始化该 Bean。


五、注意事项与常见问题

1. 首次使用的性能开销

延迟初始化的 Bean 在首次使用时会触发实例化,可能导致短暂的延迟(如连接数据库、加载配置)。需权衡启动时间与首次使用时间的总和。

2. @PostConstruct 的顺序

若 Bean 同时被 @Lazy@PostConstruct 标记,@PostConstruct 方法会在 Bean 实例化后(首次使用时)执行,而非容器启动时。

3. 循环依赖的限制

@Lazy 可打破构造器注入的循环依赖,但无法解决所有循环依赖场景(如字段注入的循环依赖仍需通过 @Lazy 或重构代码解决)。

4. @Conditional 的协同

@Lazy 可与 @Conditional 注解(如 @ConditionalOnProperty)结合使用,实现更灵活的条件化延迟初始化。例如:仅当某个属性为 true 时,才延迟初始化。

5. 原型作用域的 @Lazy

prototype 作用域的 Bean 本身每次 getBean() 时都会创建新实例,@Lazy 对其无影响(因为 prototype 本就延迟初始化)。

6. 全局懒加载配置

Spring Boot 支持全局懒加载(通过 spring.main.lazy-initialization=true),此时所有单例 Bean 默认延迟初始化(除非显式标记 @Lazy(false))。@Lazy 可覆盖全局配置。


六、总结

@Lazy 是 Spring 中优化启动时间和资源占用的核心注解,通过延迟初始化非立即使用的 Bean,显著提升应用性能。其核心机制依赖 LazyAnnotationBeanPostProcessorBeanDefinitionlazyInit 属性,支持条件化延迟和与多种注解的协同。理解其源码和使用场景,有助于开发者编写更高效、可维护的 Spring 应用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值