Spring @Lazy注解的定義及詳細(xì)源碼展示
@Lazy 是 Spring 框架中用于延遲初始化 Bean的核心注解,其核心作用是將 Bean 的實(shí)例化時(shí)機(jī)從“容器啟動(dòng)時(shí)”推遲到“首次被使用時(shí)”,從而減少應(yīng)用啟動(dòng)時(shí)間、降低內(nèi)存占用,尤其適用于初始化成本高或非立即使用的 Bean。以下從注解定義、源碼解析、核心功能、使用場(chǎng)景及注意事項(xiàng)展開(kāi)詳細(xì)說(shuō)明。
一、@Lazy注解的定義與源碼解析
@Lazy 位于 org.springframework.context.annotation 包中,其源碼定義如下(簡(jiǎn)化版):
@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)鍵屬性說(shuō)明:
value:布爾值,控制是否啟用延遲初始化(默認(rèn)true)。condition:SpEL 表達(dá)式(可選),僅當(dāng)表達(dá)式結(jié)果為true時(shí),才啟用延遲初始化(用于條件化延遲)。
二、核心功能:延遲初始化的實(shí)現(xiàn)機(jī)制
1.默認(rèn)行為:立即初始化
Spring 容器默認(rèn)在啟動(dòng)時(shí)初始化所有單例 Bean(singleton 作用域),無(wú)論是否被使用。這可能導(dǎo)致:
- 啟動(dòng)時(shí)間過(guò)長(zhǎng)(尤其是初始化成本高的 Bean)。
- 內(nèi)存浪費(fèi)(未使用的 Bean 占用資源)。
2.@Lazy的延遲初始化邏輯
當(dāng) Bean 被 @Lazy 標(biāo)記后,Spring 會(huì):
- 不立即創(chuàng)建實(shí)例:容器啟動(dòng)時(shí)不初始化該 Bean,僅記錄其延遲初始化的配置。
- 首次使用時(shí)創(chuàng)建:當(dāng)?shù)谝淮瓮ㄟ^(guò)
@Autowired、getBean()等方式獲取該 Bean 時(shí),才觸發(fā)實(shí)例化。
3.與作用域的協(xié)同
@Lazy 對(duì)不同作用域的 Bean 影響不同:
| 作用域 | 默認(rèn)初始化時(shí)機(jī) | @Lazy 效果 |
|---|---|---|
singleton(單例) | 容器啟動(dòng)時(shí)初始化 | 延遲到首次使用時(shí)初始化(全局僅一次)。 |
prototype(原型) | 每次 getBean() 時(shí)初始化 | 無(wú)變化(原型 Bean 本就每次新建,@Lazy 不影響)。 |
request/session(Web 作用域) | 作用域創(chuàng)建時(shí)初始化 | 延遲到作用域首次使用時(shí)初始化(如 HTTP 請(qǐng)求首次訪問(wèn)時(shí))。 |
三、典型使用場(chǎng)景與示例
1.高初始化成本的 Bean
例如,數(shù)據(jù)庫(kù)連接池(如 HikariCP)、緩存客戶端(如 RedisTemplate)等,初始化時(shí)需要加載大量配置或建立長(zhǎng)連接,延遲初始化可顯著減少啟動(dòng)時(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)用啟動(dòng)時(shí),RedisClient 不會(huì)立即連接 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 會(huì)被立即初始化(即使未使用);若為 true,則延遲到首次使用時(shí)初始化。
3.避免循環(huán)依賴
在循環(huán)依賴場(chǎng)景中,@Lazy 可打破“構(gòu)造器注入導(dǎo)致的循環(huán)依賴死鎖”。例如,A 依賴 B,B 依賴 A,若兩者均使用構(gòu)造器注入,Spring 無(wú)法解決循環(huán)依賴;但通過(guò) @Lazy 延遲其中一個(gè)的初始化,可避免啟動(dòng)時(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 已初始化,無(wú)循環(huán)依賴)
@Autowired
public B(A a) {
this.a = a;
}
}效果:A 初始化時(shí),B 被標(biāo)記為延遲,因此不會(huì)立即觸發(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)換為“延遲初始化”的代理對(duì)象。其關(guān)鍵邏輯如下:
(1)掃描@Lazy注解
在解析 @Bean 方法或 @Component 類時(shí),ConfigurationClassParser 會(huì)掃描 @Lazy 注解,獲取其 value 和 condition 屬性。
(2)生成代理對(duì)象
若 @Lazy 啟用(value=true 且 condition 滿足),LazyAnnotationBeanPostProcessor 會(huì)為該 Bean 生成一個(gè)延遲初始化代理(通常是 CGLIB 或 JDK 動(dòng)態(tài)代理)。該代理在首次調(diào)用方法時(shí),才會(huì)觸發(fā)實(shí)際的 Bean 實(shí)例化。
(3)條件判斷
若 @Lazy 的 condition 屬性存在,LazyAnnotationBeanPostProcessor 會(huì)通過(guò) ExpressionEvaluator 解析 SpEL 表達(dá)式,判斷是否啟用延遲初始化。
2.BeanDefinition的lazyInit屬性
Spring 的 BeanDefinition 接口有一個(gè) isLazyInit() 方法,用于標(biāo)記該 Bean 是否延遲初始化。LazyAnnotationBeanPostProcessor 會(huì)根據(jù) @Lazy 的配置設(shè)置該屬性為 true,從而告知 Spring 容器延遲初始化該 Bean。
五、注意事項(xiàng)與常見(jiàn)問(wèn)題
1.首次使用的性能開(kāi)銷
延遲初始化的 Bean 在首次使用時(shí)會(huì)觸發(fā)實(shí)例化,可能導(dǎo)致短暫的延遲(如連接數(shù)據(jù)庫(kù)、加載配置)。需權(quán)衡啟動(dòng)時(shí)間與首次使用時(shí)間的總和。
2.與@PostConstruct的順序
若 Bean 同時(shí)被 @Lazy 和 @PostConstruct 標(biāo)記,@PostConstruct 方法會(huì)在 Bean 實(shí)例化后(首次使用時(shí))執(zhí)行,而非容器啟動(dòng)時(shí)。
3.循環(huán)依賴的限制
@Lazy 可打破構(gòu)造器注入的循環(huán)依賴,但無(wú)法解決所有循環(huán)依賴場(chǎng)景(如字段注入的循環(huán)依賴仍需通過(guò) @Lazy 或重構(gòu)代碼解決)。
4.與@Conditional的協(xié)同
@Lazy 可與 @Conditional 注解(如 @ConditionalOnProperty)結(jié)合使用,實(shí)現(xiàn)更靈活的條件化延遲初始化。例如:僅當(dāng)某個(gè)屬性為 true 時(shí),才延遲初始化。
5.原型作用域的@Lazy
prototype 作用域的 Bean 本身每次 getBean() 時(shí)都會(huì)創(chuàng)建新實(shí)例,@Lazy 對(duì)其無(wú)影響(因?yàn)?prototype 本就延遲初始化)。
6.全局懶加載配置
Spring Boot 支持全局懶加載(通過(guò) spring.main.lazy-initialization=true),此時(shí)所有單例 Bean 默認(rèn)延遲初始化(除非顯式標(biāo)記 @Lazy(false))。@Lazy 可覆蓋全局配置。
六、總結(jié)
@Lazy 是 Spring 中優(yōu)化啟動(dòng)時(shí)間和資源占用的核心注解,通過(guò)延遲初始化非立即使用的 Bean,顯著提升應(yīng)用性能。其核心機(jī)制依賴 LazyAnnotationBeanPostProcessor 和 BeanDefinition 的 lazyInit 屬性,支持條件化延遲和與多種注解的協(xié)同。理解其源碼和使用場(chǎng)景,有助于開(kāi)發(fā)者編寫更高效、可維護(hù)的 Spring 應(yīng)用。
到此這篇關(guān)于Spring @Lazy 詳解及詳細(xì)源碼展示的文章就介紹到這了,更多相關(guān)Spring @Lazy內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用Filter實(shí)現(xiàn)登錄權(quán)限驗(yàn)證
這篇文章主要為大家詳細(xì)介紹了使用Filter實(shí)現(xiàn)登錄權(quán)限驗(yàn)證,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-10-10
JavaWeb開(kāi)發(fā)之使用jQuery與Ajax實(shí)現(xiàn)動(dòng)態(tài)聯(lián)級(jí)菜單效果
這篇文章主要介紹了JavaWeb開(kāi)發(fā)之使用jQuery與Ajax實(shí)現(xiàn)動(dòng)態(tài)聯(lián)級(jí)菜單效果的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-10-10
java 實(shí)現(xiàn)切割文件和合并文件的功能
這篇文章主要介紹了java 實(shí)現(xiàn)切割文件和合并文件的功能的相關(guān)資料,這里實(shí)現(xiàn)文件的切割的實(shí)現(xiàn)代碼和文件合并的實(shí)現(xiàn)代碼,需要的朋友可以參考下2017-07-07
@SpringBootTest 注解報(bào)紅問(wèn)題及解決
這篇文章主要介紹了@SpringBootTest 注解報(bào)紅問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11
java實(shí)現(xiàn)學(xué)生信息錄入界面
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)學(xué)生信息錄入界面,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04
Mybatis中如何設(shè)置sqlSession自動(dòng)提交
在MyBatis中,默認(rèn)情況下,獲取的SqlSession對(duì)象不會(huì)自動(dòng)提交事務(wù),這意味著在進(jìn)行更新、刪除或插入等操作后,需要顯式調(diào)用commit方法來(lái)提交事務(wù),但是,可以在獲取SqlSession時(shí)通過(guò)將openSession方法的參數(shù)設(shè)置為true2024-09-09

