開(kāi)發(fā)技巧!@Lazy注解這5種用法非常實(shí)用
環(huán)境:Spring6.1.8
1. 簡(jiǎn)介
Spring中的@Lazy注解主要用于實(shí)現(xiàn)惰性加載(延遲加載),它可以應(yīng)用在類(lèi)、方法、構(gòu)造方法、參數(shù)和字段上。以下是@Lazy注解的作用、應(yīng)用場(chǎng)景。
1.1 作用
在Spring框架中,默認(rèn)情況下,所有的Bean在容器啟動(dòng)時(shí)都會(huì)被初始化。但是,有些Bean的初始化可能涉及到一些重量級(jí)的操作,如網(wǎng)絡(luò)IO操作、復(fù)雜計(jì)算等,這些操作會(huì)消耗大量的系統(tǒng)資源。通過(guò)使用@Lazy注解,可以讓這些Bean在真正需要時(shí)才進(jìn)行初始化,從而提高系統(tǒng)的啟動(dòng)速度和性能。
1.2 應(yīng)用場(chǎng)景
- 提升系統(tǒng)啟動(dòng)速度:當(dāng)應(yīng)用包含大量的Bean,如果存在某些Bean初始化操作非常耗時(shí)(如網(wǎng)絡(luò)IO操作或復(fù)雜耗時(shí)計(jì)算),通過(guò)@Lazy注解可以顯著提升系統(tǒng)的啟動(dòng)速度。如:應(yīng)用啟動(dòng)時(shí),需要從Redis讀取大量的緩存數(shù)據(jù),如果將此Bean使用@Lazy標(biāo)注,那么應(yīng)用啟動(dòng)會(huì)非常快,而當(dāng)在使用緩存服務(wù)時(shí)才去讀取redis初始化數(shù)據(jù)。
- 解決循環(huán)依賴(lài):如果兩個(gè)Bean之間存在循環(huán)依賴(lài),即A依賴(lài)B,B又依賴(lài)A(構(gòu)造函數(shù)注入),這會(huì)導(dǎo)致Spring容器在初始化這些Bean時(shí)陷入死循環(huán)。使用@Lazy注解可以解決這類(lèi)問(wèn)題。
- 單例Bean正確注入多例Bean:如果A是單例,B是多例,在A中注入B實(shí)例,要想正確的注入(每次使用B時(shí)都是新對(duì)象)通過(guò)使用@Lazy能夠輕松解決。
以上是關(guān)于@Lazy注解的簡(jiǎn)介及應(yīng)用場(chǎng)景,接下來(lái)將詳細(xì)介紹@Lazy的5種使用方式。
2. 實(shí)戰(zhàn)案例
2.1 環(huán)境準(zhǔn)備
public class PersonDAO {
}
public class PersonService {
private PersonDAO dao ;
public String toString() {
return "PersonService [dao=" + dao.getClass() + "]";
}
}
// 測(cè)試入口代碼
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.register(PersonDAO.class) ;
context.register(PersonService.class) ;
context.refresh() ;
System.out.println(context.getBean(PersonService.class)) ;
}
接下的每個(gè)示例都將基于上面的類(lèi)進(jìn)行。
2.2 字段注入
// @Resource
// @Autowired
@Lazy
private PersonDAO dao ;
輸出結(jié)果
PersonService [dao=class com.pack.PersonDAO$$SpringCGLIB$$0]
通過(guò)@Lazy標(biāo)注的字段,最終注入的是代理類(lèi)(不管上面使用的@Resource還是@Autowired)。
注:在上面測(cè)試入口代碼中,我們使用的是AnnotationConfigApplicationContext,如果你使用的是GenericApplicationContext那么在默認(rèn)情況下@Autowired是不會(huì)生效的,這時(shí)候你還需要做如下設(shè)置:
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory() ;
beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver()) ;
將此BeanFactory設(shè)置到ApplicationContext中即可。
2.3 方法注入
@Resource
@Lazy
public void setPersonDAO(PersonDAO dao) {
this.dao = dao ;
}
輸出結(jié)果
PersonService [dao=class com.pack.PersonDAO$$SpringCGLIB$$0]
同樣的生成了代理。
方法注入,你還可以將@Lazy放到方法參數(shù)上,如下示例:
@Resource
public void setPersonDAO(@Lazy PersonDAO dao) {
this.dao = dao ;
}
這種方式也是會(huì)被生成代理對(duì)象。
2.4 構(gòu)造函數(shù)注入
@Lazy
public PersonService(PersonDAO dao) {
this.dao = dao ;
}
輸出結(jié)果
PersonService [dao=class com.pack.PersonDAO$$SpringCGLIB$$0]
同樣,注解也可以使用在參數(shù)上
public PersonService(@Lazy PersonDAO dao) {
this.dao = dao ;
}
構(gòu)造函數(shù)注入與方法注入基本一致。
2.5 單例Bean注入多例Bean
修改PersonDAO;
@Scope("prototype")
public class PersonDAO {
}
通過(guò)@Scope將其聲明為多例。
修改PersonService隨意添加一個(gè)方法。
public class PersonService {
@Autowired
private PersonDAO dao ;
public void save() {
System.out.printf("PersonDAO hashCode: %s%n", dao) ;
}
}
測(cè)試類(lèi):
PersonService ps = context.getBean(PersonService.class);
ps.save() ;
ps.save() ;
ps.save() ;
當(dāng)dao字段上不添加@Lazy注解時(shí),輸出結(jié)果:
PersonDAO hashCode: com.pack.PersonDAO@66565121
PersonDAO hashCode: com.pack.PersonDAO@66565121
PersonDAO hashCode: com.pack.PersonDAO@66565121
每次都是同一個(gè)對(duì)象,這不是我們期望的結(jié)果
dao字段添加@Lazy注解后,再次運(yùn)行
PersonDAO hashCode: com.pack.PersonDAO@73a2e526
PersonDAO hashCode: com.pack.PersonDAO@13f95696
PersonDAO hashCode: com.pack.PersonDAO@68be8808
正確的輸出結(jié)果,每次使用都是不同的實(shí)例。
2.6 循環(huán)依賴(lài)
class class A {
private B b ;
public A(B b) {
this.b = b ;
}
}
public class B {
private A a ;
public B(A a) {
this.a = a ;
}
}
上面的依賴(lài)通過(guò)構(gòu)造方法注入,這種情況下容器啟動(dòng)是會(huì)報(bào)錯(cuò)的,如下:
圖片
出現(xiàn)循環(huán)依賴(lài)錯(cuò)誤,通過(guò)@Lazy注解解決此問(wèn)題,只需要在任意類(lèi)的構(gòu)造函數(shù)上使用@Lazy注解,如下:
public A(@Lazy B b) {
this.b = b ;
}
只需要在其中一方加入了@Lazy注解后,問(wèn)題得到解決。