Spring @Lazy注解的定義及詳細(xì)源碼展示
@Lazy 是 Spring 框架中用于延遲初始化 Bean的核心注解,其核心作用是將 Bean 的實(shí)例化時(shí)機(jī)從“容器啟動時(shí)”推遲到“首次被使用時(shí)”,從而減少應(yīng)用啟動時(shí)間、降低內(nèi)存占用,尤其適用于初始化成本高或非立即使用的 Bean。以下從注解定義、源碼解析、核心功能、使用場景及注意事項(xiàng)展開詳細(xì)說明。
一、@Lazy注解的定義與源碼解析
@Lazy 位于 org.springframework.context.annotation 包中,其源碼定義如下(簡化版):
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lazy {
/**
* 是否延遲初始化(默認(rèn) true,即延遲;false 表示立即初始化)
*/
boolean value() default true;
/**
* 條件判斷(可選,滿足條件時(shí)才延遲初始化)
*/
String condition() default "";
}關(guān)鍵屬性說明:
value:布爾值,控制是否啟用延遲初始化(默認(rèn)true)。condition:SpEL 表達(dá)式(可選),僅當(dāng)表達(dá)式結(jié)果為true時(shí),才啟用延遲初始化(用于條件化延遲)。
二、核心功能:延遲初始化的實(shí)現(xiàn)機(jī)制
1.默認(rèn)行為:立即初始化
Spring 容器默認(rèn)在啟動時(shí)初始化所有單例 Bean(singleton 作用域),無論是否被使用。這可能導(dǎo)致:
- 啟動時(shí)間過長(尤其是初始化成本高的 Bean)。
- 內(nèi)存浪費(fèi)(未使用的 Bean 占用資源)。
2.@Lazy的延遲初始化邏輯
當(dāng) Bean 被 @Lazy 標(biāo)記后,Spring 會:
- 不立即創(chuàng)建實(shí)例:容器啟動時(shí)不初始化該 Bean,僅記錄其延遲初始化的配置。
- 首次使用時(shí)創(chuàng)建:當(dāng)?shù)谝淮瓮ㄟ^
@Autowired、getBean()等方式獲取該 Bean 時(shí),才觸發(fā)實(shí)例化。
3.與作用域的協(xié)同
@Lazy 對不同作用域的 Bean 影響不同:
| 作用域 | 默認(rèn)初始化時(shí)機(jī) | @Lazy 效果 |
|---|---|---|
singleton(單例) | 容器啟動時(shí)初始化 | 延遲到首次使用時(shí)初始化(全局僅一次)。 |
prototype(原型) | 每次 getBean() 時(shí)初始化 | 無變化(原型 Bean 本就每次新建,@Lazy 不影響)。 |
request/session(Web 作用域) | 作用域創(chuàng)建時(shí)初始化 | 延遲到作用域首次使用時(shí)初始化(如 HTTP 請求首次訪問時(shí))。 |
三、典型使用場景與示例
1.高初始化成本的 Bean
例如,數(shù)據(jù)庫連接池(如 HikariCP)、緩存客戶端(如 RedisTemplate)等,初始化時(shí)需要加載大量配置或建立長連接,延遲初始化可顯著減少啟動時(shí)間。
示例:
@Service
@Lazy // 延遲初始化(首次使用時(shí)創(chuàng)建)
public class RedisClient {
private final RedisTemplate<String, Object> redisTemplate;
// 構(gòu)造器初始化成本高(連接 Redis 服務(wù)器)
public RedisClient(RedisConnectionFactory connectionFactory) {
this.redisTemplate = new RedisTemplate<>();
this.redisTemplate.setConnectionFactory(connectionFactory);
this.redisTemplate.afterPropertiesSet(); // 觸發(fā)連接
}
public void setValue(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
}
}效果:應(yīng)用啟動時(shí),RedisClient 不會立即連接 Redis,而是在第一次調(diào)用 setValue 時(shí)才初始化。
2.條件化延遲加載
結(jié)合 condition 屬性,僅當(dāng)滿足特定條件時(shí)才延遲初始化(如根據(jù)環(huán)境變量或配置屬性)。
示例:
@Service
@Lazy(condition = "#{environment.getProperty('feature.analytics.enabled') == 'true'}") // 僅當(dāng) analytics 功能啟用時(shí)延遲初始化
public class AnalyticsService {
// 初始化成本高(連接分析服務(wù))
public AnalyticsService(AnalyticsClient client) {
// ...
}
}效果:若 application.properties 中 feature.analytics.enabled=false,則 AnalyticsService 會被立即初始化(即使未使用);若為 true,則延遲到首次使用時(shí)初始化。
3.避免循環(huán)依賴
在循環(huán)依賴場景中,@Lazy 可打破“構(gòu)造器注入導(dǎo)致的循環(huán)依賴死鎖”。例如,A 依賴 B,B 依賴 A,若兩者均使用構(gòu)造器注入,Spring 無法解決循環(huán)依賴;但通過 @Lazy 延遲其中一個(gè)的初始化,可避免啟動時(shí)報(bào)錯(cuò)。
示例:
@Service
public class A {
private final B b;
// 構(gòu)造器注入 B(但 B 被 @Lazy 標(biāo)記)
@Autowired
public A(@Lazy B b) {
this.b = b;
}
}
@Service
@Lazy // B 延遲初始化
public class B {
private final A a;
// 構(gòu)造器注入 A(A 已初始化,無循環(huán)依賴)
@Autowired
public B(A a) {
this.a = a;
}
}效果:A 初始化時(shí),B 被標(biāo)記為延遲,因此不會立即觸發(fā) B 的構(gòu)造器(避免循環(huán)依賴)。當(dāng)首次使用 B 時(shí),A 已存在,可正常初始化。
四、源碼實(shí)現(xiàn)細(xì)節(jié)與關(guān)鍵類
1.LazyAnnotationBeanPostProcessor
Spring 處理 @Lazy 的核心類,繼承自 InstantiationAwareBeanPostProcessorAdapter,負(fù)責(zé)將 @Lazy 標(biāo)記的 Bean 轉(zhuǎn)換為“延遲初始化”的代理對象。其關(guān)鍵邏輯如下:
(1)掃描@Lazy注解
在解析 @Bean 方法或 @Component 類時(shí),ConfigurationClassParser 會掃描 @Lazy 注解,獲取其 value 和 condition 屬性。
(2)生成代理對象
若 @Lazy 啟用(value=true 且 condition 滿足),LazyAnnotationBeanPostProcessor 會為該 Bean 生成一個(gè)延遲初始化代理(通常是 CGLIB 或 JDK 動態(tài)代理)。該代理在首次調(diào)用方法時(shí),才會觸發(fā)實(shí)際的 Bean 實(shí)例化。
(3)條件判斷
若 @Lazy 的 condition 屬性存在,LazyAnnotationBeanPostProcessor 會通過 ExpressionEvaluator 解析 SpEL 表達(dá)式,判斷是否啟用延遲初始化。
2.BeanDefinition的lazyInit屬性
Spring 的 BeanDefinition 接口有一個(gè) isLazyInit() 方法,用于標(biāo)記該 Bean 是否延遲初始化。LazyAnnotationBeanPostProcessor 會根據(jù) @Lazy 的配置設(shè)置該屬性為 true,從而告知 Spring 容器延遲初始化該 Bean。
五、注意事項(xiàng)與常見問題
1.首次使用的性能開銷
延遲初始化的 Bean 在首次使用時(shí)會觸發(fā)實(shí)例化,可能導(dǎo)致短暫的延遲(如連接數(shù)據(jù)庫、加載配置)。需權(quán)衡啟動時(shí)間與首次使用時(shí)間的總和。
2.與@PostConstruct的順序
若 Bean 同時(shí)被 @Lazy 和 @PostConstruct 標(biāo)記,@PostConstruct 方法會在 Bean 實(shí)例化后(首次使用時(shí))執(zhí)行,而非容器啟動時(shí)。
3.循環(huán)依賴的限制
@Lazy 可打破構(gòu)造器注入的循環(huán)依賴,但無法解決所有循環(huán)依賴場景(如字段注入的循環(huán)依賴仍需通過 @Lazy 或重構(gòu)代碼解決)。
4.與@Conditional的協(xié)同
@Lazy 可與 @Conditional 注解(如 @ConditionalOnProperty)結(jié)合使用,實(shí)現(xiàn)更靈活的條件化延遲初始化。例如:僅當(dāng)某個(gè)屬性為 true 時(shí),才延遲初始化。
5.原型作用域的@Lazy
prototype 作用域的 Bean 本身每次 getBean() 時(shí)都會創(chuàng)建新實(shí)例,@Lazy 對其無影響(因?yàn)?prototype 本就延遲初始化)。
6.全局懶加載配置
Spring Boot 支持全局懶加載(通過 spring.main.lazy-initialization=true),此時(shí)所有單例 Bean 默認(rèn)延遲初始化(除非顯式標(biāo)記 @Lazy(false))。@Lazy 可覆蓋全局配置。
六、總結(jié)
@Lazy 是 Spring 中優(yōu)化啟動時(shí)間和資源占用的核心注解,通過延遲初始化非立即使用的 Bean,顯著提升應(yīng)用性能。其核心機(jī)制依賴 LazyAnnotationBeanPostProcessor 和 BeanDefinition 的 lazyInit 屬性,支持條件化延遲和與多種注解的協(xié)同。理解其源碼和使用場景,有助于開發(fā)者編寫更高效、可維護(hù)的 Spring 應(yīng)用。
到此這篇關(guān)于Spring @Lazy 詳解及詳細(xì)源碼展示的文章就介紹到這了,更多相關(guān)Spring @Lazy內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解IntelliJ IDEA中TortoiseSVN修改服務(wù)器地址的方法
這篇文章主要介紹了詳解IntelliJ IDEA中TortoiseSVN修改服務(wù)器地址的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-12-12
用SpringBoot+Vue+uniapp小程序?qū)崿F(xiàn)在線房屋裝修管理系統(tǒng)
這篇文章主要介紹了用SpringBoot+Vue+uniapp實(shí)現(xiàn)在線房屋裝修管理系統(tǒng),針對裝修樣板信息管理混亂,出錯(cuò)率高,信息安全性差,勞動強(qiáng)度大,費(fèi)時(shí)費(fèi)力等問題開發(fā)了這套系統(tǒng),需要的朋友可以參考下2023-03-03
SpringBoot自動配置深入探究實(shí)現(xiàn)原理
在springboot的啟動類中可以看到@SpringBootApplication注解,它是SpringBoot的核心注解,也是一個(gè)組合注解。其中@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三個(gè)注解尤為重要。今天我們就來淺析這三個(gè)注解的含義2022-08-08
關(guān)于JSqlparser使用攻略(高效的SQL解析工具)
這篇文章主要介紹了關(guān)于JSqlparser使用攻略(高效的SQL解析工具),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11
Java基礎(chǔ)之并發(fā)相關(guān)知識總結(jié)
隨著摩爾定律逐步失效,cpu單核性能達(dá)到瓶頸,并發(fā)逐漸逐漸得到廣泛應(yīng)用,因而學(xué)習(xí)了解以及使用并發(fā)就顯得十分重要,但并發(fā)相關(guān)的知識比較瑣碎,不易系統(tǒng)學(xué)習(xí),因而本篇文章參照王寶令老師《Java并發(fā)編程》來勾勒出一張“并發(fā)全景圖”,需要的朋友可以參考下2021-05-05
java通過url讀取遠(yuǎn)程數(shù)據(jù)并保持到本地的實(shí)例代碼
本文通過實(shí)例代碼給大家介紹了java通過url讀取遠(yuǎn)程數(shù)據(jù)并保持到本地的方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-07-07
Netty + ZooKeeper 實(shí)現(xiàn)簡單的服務(wù)注冊與發(fā)現(xiàn)
服務(wù)注冊和發(fā)現(xiàn)一直是分布式的核心組件。本文介紹了借助 ZooKeeper 做注冊中心,如何實(shí)現(xiàn)一個(gè)簡單的服務(wù)注冊和發(fā)現(xiàn)。,需要的朋友可以參考下2019-06-06

