@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 会:
- 不立即创建实例:容器启动时不初始化该 Bean,仅记录其延迟初始化的配置。
- 首次使用时创建:当第一次通过
@Autowired
、getBean()
等方式获取该 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.properties
中 feature.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
注解,获取其 value
和 condition
属性。
(2)生成代理对象
若 @Lazy
启用(value=true
且 condition
满足),LazyAnnotationBeanPostProcessor
会为该 Bean 生成一个延迟初始化代理(通常是 CGLIB 或 JDK 动态代理)。该代理在首次调用方法时,才会触发实际的 Bean 实例化。
(3)条件判断
若 @Lazy
的 condition
属性存在,LazyAnnotationBeanPostProcessor
会通过 ExpressionEvaluator
解析 SpEL 表达式,判断是否启用延迟初始化。
2. BeanDefinition
的 lazyInit
属性
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,显著提升应用性能。其核心机制依赖 LazyAnnotationBeanPostProcessor
和 BeanDefinition
的 lazyInit
属性,支持条件化延迟和与多种注解的协同。理解其源码和使用场景,有助于开发者编写更高效、可维护的 Spring 应用。