Spring框架中@Lazy延遲加載原理和使用詳解
一、@Lazy延遲加載原理
如果某個(gè)類想要使它在Spring啟動(dòng)時(shí)不加載我們聽的最多的便是為其加上@Lazy注解或者在@ComponentScan掃描注解中設(shè)置lazyInit為true即可完成。那么我們先來看看這兩者分別的實(shí)現(xiàn)原理。
1.延遲加載原理
1.1 @Lazy三種配置方法
我們使用延遲加載一般有三種實(shí)現(xiàn)方式,第一種也是最原始的配置方式是在XML文件中直接配置標(biāo)簽屬性:
<bean id="XXX" class="XXX.XXX.XXXX" lazy-init="true"/>
第二種方式為在@Component類上加上@Lazy注解:
@Lazy
@Component
public class XXXX {
...
}第三種方式是在@Configuration類中配置@Bean時(shí)添加@Lazy注解:
@Configuration
public class XXXX {
@Lazy
@Bean
public XXX getXXX() {
return new XXX();
}
}1.2 @ComponentScan配置延遲加載
使用包掃描的配置方式如下:
@ComponentScan(value = "XXX.XXX", lazyInit = true)
@Configuration
public class XXXX {
...
}1.3 加載原理
當(dāng)使用上述三種配置后,Spring在掃描加載Bean時(shí)會(huì)讀取@Lazy和@Component注解相應(yīng)值,并設(shè)置Bean定義的lazyInit屬性。
讀取注解配置時(shí)最終會(huì)調(diào)用ClassPathBeanDefinitionScanner及其子類實(shí)現(xiàn)的doScan方法,在這個(gè)方法中完成注解的讀取配置。
關(guān)鍵源碼如下:
public class ClassPathBeanDefinitionScanner
extends ClassPathScanningCandidateComponentProvider {
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 不管是讀取注解或者XML配置方式bean,最終讀取加載Bean時(shí)都會(huì)進(jìn)入到該方法
// 對相應(yīng)的包進(jìn)行處理
// beanDefinitions是保存返回bean定義的集合
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
// 遍歷多個(gè)包下的類
for (String basePackage : basePackages) {
// 獲取滿足條件的bean定義集合
Set<BeanDefinition> candidates =
findCandidateComponents(basePackage);
// 對每個(gè)bean定義進(jìn)行處理
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver
.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator
.generateBeanName(candidate, this.registry);
// 這個(gè)方法會(huì)處理@ComponentScan中的lazyInit值,因?yàn)樵谑褂?
// @ComponentScan注解時(shí)會(huì)首先把該值賦值到beanDefinitionDefaults
// 默認(rèn)bean定義值的對象中,在postProcessBeanDefinition方法中
// 會(huì)首先應(yīng)用一次這些默認(rèn)值,其中就包括lazyInit、autowireMode等
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition(
(AbstractBeanDefinition) candidate, beanName);
}
// 讀取@Lazy、@Primary和@DependsOn等注解值
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils
.processCommonDefinitionAnnotations(
(AnnotatedBeanDefinition) candidate);
}
// 如果候選者滿足要求則將其注冊到Bean定義中心
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder =
new BeanDefinitionHolder(candidate, beanName);
definitionHolder = AnnotationConfigUtils
.applyScopedProxyMode(scopeMetadata,
definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
// 注冊bean定義
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
protected void postProcessBeanDefinition(
AbstractBeanDefinition beanDefinition, String beanName) {
// 此處會(huì)應(yīng)用默認(rèn)值,如lazyInit、autowireMode、initMethod等
beanDefinition.applyDefaults(this.beanDefinitionDefaults);
if (this.autowireCandidatePatterns != null) {
beanDefinition.setAutowireCandidate(PatternMatchUtils
.simpleMatch(this.autowireCandidatePatterns, beanName));
}
}
}經(jīng)過ClassPathBeanDefinitionScanner或子類實(shí)現(xiàn)的掃描讀取后,延遲加載的配置便被配置到了Bean定義中,等初始化時(shí)再使用該屬性,這里需要注意的是@ComponentScan延遲加載屬性是可以被@Lazy覆蓋的,因?yàn)锧Lazy是在@ComponentScan后面處理的。
2.延遲加載實(shí)現(xiàn)原理
前面我們已經(jīng)知道了在何處讀取注解配置的屬性,現(xiàn)在我們稍微看下其具體判斷實(shí)現(xiàn)的地方。
2.1 AbstractApplicationContext
Spring框架在刷新時(shí)會(huì)初始化非延遲加載的單例bean,而一般我們使用的bean都是單例的。其關(guān)鍵源碼如下:
public abstract class AbstractApplicationContext
extends DefaultResourceLoader
implements ConfigurableApplicationContext {
@Override
public void refresh() throws BeansException, IllegalStateException {
...
// 刷新流程中執(zhí)行初始化非延遲單例的方法
finishBeanFactoryInitialization(beanFactory);
...
}
protected void finishBeanFactoryInitialization(
ConfigurableListableBeanFactory beanFactory) {
...
// 實(shí)際執(zhí)行初始化非延遲加載單例
beanFactory.preInstantiateSingletons();
}
}2.2 DefaultListableBeanFactory
最終會(huì)調(diào)用Spring工廠來實(shí)例化,直接看到其實(shí)現(xiàn)方法源碼:
public class DefaultListableBeanFactory
extends AbstractAutowireCapableBeanFactory
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry,
Serializable {
// 當(dāng)BeanDefinition被創(chuàng)建注冊到工廠中時(shí)bean定義的名字將會(huì)被保存到這個(gè)集合中
// 且里面的順序?yàn)樽赃M(jìn)來的順序
private volatile List<String> beanDefinitionNames =
new ArrayList<>(256);
@Override
public void preInstantiateSingletons() throws BeansException {
// 獲取所有已注冊到Spring工廠中的bean定義
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
// 遍歷bean定義,初始化非抽象、單例且非延遲加載的bean對象
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
// !bd.isLazyInit()便是判斷非延遲加載的,因此前面獲取到的延遲加載
// 屬性會(huì)在這里進(jìn)行判斷
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
// 這里面會(huì)判斷是否是FactoryBean類型,最終都會(huì)調(diào)用到getBean中
...
}
}
// 后續(xù)略過
...
}
}二、使用細(xì)節(jié)
讀取源碼其中一個(gè)目的是為了更好的實(shí)際的應(yīng)用以及準(zhǔn)確的把握對應(yīng)功能的生效范圍,因此在使用延遲加載時(shí)需要額外注意一些點(diǎn)。
Spring框架延遲加載屬性在調(diào)用getBean之后將會(huì)失效,因?yàn)間etBean方法是初始化bean的入口,這不難理解,那么平時(shí)我們使用@Autowired等自動(dòng)注入注解時(shí)能和@Lazy注解一起使用嗎?
接下來我們從兩個(gè)實(shí)例來說明一下,這兩個(gè)實(shí)例都是使用平時(shí)的使用用法,在Component上添加@Lazy注解,且讓其實(shí)現(xiàn)InitializingBean接口,當(dāng)Bean被加載時(shí)我們便能得知,看其是否會(huì)生效,示例如下:
1.@Lazy失效實(shí)例
1.1 Controller非延遲加載類
聲明一個(gè)Controller控制器:
@Controller
public class TestController implements InitializingBean{
@Autowired
private TestService testService;
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("testController Initializing");
}
}1.2 Service延遲加載類
再聲明一個(gè)Service服務(wù)類:
@Lazy
@Service
public class TestService implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("testService Initializing");
}
}1.3 結(jié)果輸出
啟動(dòng)程序后控制臺輸出:
testService Initializing
testController Initializing
啟動(dòng)完Spring程序后輸出了TestService里面打印的字符串。這就奇怪了,明明使用了@Lazy注解,但是卻并沒有其作用,在Spring啟動(dòng)項(xiàng)目時(shí)還是加載了這個(gè)類?這就涉及到@Autowired等自動(dòng)注入注解的使用了,如果有興趣了解其實(shí)現(xiàn)的可以去看文章Spring框架原理之實(shí)例化bean和@Autowired實(shí)現(xiàn)原理。
由于Controller類不是延遲加載的,且里面使用@Autowired自動(dòng)注入注解注入了Service,因此在程序初始化時(shí)Controller將會(huì)被初始化,同時(shí)在處理@Autowired注解的字段時(shí),會(huì)調(diào)用getBean方法從Spring工廠中獲取字段的bean對象,因此通過@Autowired路線加在了Service,這就導(dǎo)致了@Lazy注解失效了,因此雖然沒通過refresh方法流程初始化,但是卻通過@Autowired的處理類初始化了。
2.@Lazy起效實(shí)例
想要@Lazy注解起作用,只需要改一步,即把Controller也改成@Lazy,讓其在啟動(dòng)時(shí)不被加載,不觸發(fā)@Autowired注解依賴鏈的調(diào)用即可。
2.1 修改的Controller實(shí)例
修改后如下:
@Lazy
@Controller
public class TestController implements InitializingBean{
@Autowired
private TestService testService;
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("testController Initializing");
}
}如果是這種配置@Lazy將會(huì)起作用,在項(xiàng)目啟動(dòng)時(shí)將不會(huì)加載這兩個(gè)需要延遲加載的bean。
總結(jié)
從上面的例子我們可以總結(jié)及延伸出兩個(gè)注意點(diǎn):
- 非延遲加載的類中不能自動(dòng)注入延遲加載的類,會(huì)導(dǎo)致延遲加載失效;
- 如果想要實(shí)現(xiàn)某個(gè)類延遲加載使用自動(dòng)注入功能時(shí)需要調(diào)用鏈前都不存在非延遲加載類,否則延遲加載失效。
作用效果總結(jié)圖如下:

其實(shí)@Scope指定原型和單例時(shí)有些情況也會(huì)導(dǎo)致原型bean“失效”,這又是另外一個(gè)故事了,后面有機(jī)會(huì)再分析一波。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringMVC的REST風(fēng)格的四種請求方式總結(jié)
下面小編就為大家?guī)硪黄猄pringMVC的REST風(fēng)格的四種請求方式總結(jié)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-08-08
SpringBoot中@EnableAutoConfiguration注解源碼分析
這篇文章主要介紹了SpringBoot中@EnableAutoConfiguration注解源碼分析,@EnableAutoConfiguration,主要是用于加載Starter目錄包之外的、需要Spring自動(dòng)生成Bean對象的、帶有@Configuration注解的類,需要的朋友可以參考下2023-08-08
JDK都出到14了,你有什么理由不會(huì)函數(shù)式編程(推薦)
這篇文章主要介紹了JDK都出到14了,你有什么理由不會(huì)函數(shù)式編程,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05

