Spring框架中@Lazy延遲加載原理和使用詳解
一、@Lazy延遲加載原理
如果某個類想要使它在Spring啟動時不加載我們聽的最多的便是為其加上@Lazy注解或者在@ComponentScan掃描注解中設置lazyInit為true即可完成。那么我們先來看看這兩者分別的實現(xiàn)原理。
1.延遲加載原理
1.1 @Lazy三種配置方法
我們使用延遲加載一般有三種實現(xiàn)方式,第一種也是最原始的配置方式是在XML文件中直接配置標簽屬性:
<bean id="XXX" class="XXX.XXX.XXXX" lazy-init="true"/>
第二種方式為在@Component類上加上@Lazy注解:
@Lazy @Component public class XXXX { ... }
第三種方式是在@Configuration類中配置@Bean時添加@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 加載原理
當使用上述三種配置后,Spring在掃描加載Bean時會讀取@Lazy和@Component注解相應值,并設置Bean定義的lazyInit屬性。
讀取注解配置時最終會調用ClassPathBeanDefinitionScanner及其子類實現(xiàn)的doScan方法,在這個方法中完成注解的讀取配置。
關鍵源碼如下:
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider { protected Set<BeanDefinitionHolder> doScan(String... basePackages) { // 不管是讀取注解或者XML配置方式bean,最終讀取加載Bean時都會進入到該方法 // 對相應的包進行處理 // beanDefinitions是保存返回bean定義的集合 Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>(); // 遍歷多個包下的類 for (String basePackage : basePackages) { // 獲取滿足條件的bean定義集合 Set<BeanDefinition> candidates = findCandidateComponents(basePackage); // 對每個bean定義進行處理 for (BeanDefinition candidate : candidates) { ScopeMetadata scopeMetadata = this.scopeMetadataResolver .resolveScopeMetadata(candidate); candidate.setScope(scopeMetadata.getScopeName()); String beanName = this.beanNameGenerator .generateBeanName(candidate, this.registry); // 這個方法會處理@ComponentScan中的lazyInit值,因為在使用 // @ComponentScan注解時會首先把該值賦值到beanDefinitionDefaults // 默認bean定義值的對象中,在postProcessBeanDefinition方法中 // 會首先應用一次這些默認值,其中就包括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) { // 此處會應用默認值,如lazyInit、autowireMode、initMethod等 beanDefinition.applyDefaults(this.beanDefinitionDefaults); if (this.autowireCandidatePatterns != null) { beanDefinition.setAutowireCandidate(PatternMatchUtils .simpleMatch(this.autowireCandidatePatterns, beanName)); } } }
經(jīng)過ClassPathBeanDefinitionScanner或子類實現(xiàn)的掃描讀取后,延遲加載的配置便被配置到了Bean定義中,等初始化時再使用該屬性,這里需要注意的是@ComponentScan延遲加載屬性是可以被@Lazy覆蓋的,因為@Lazy是在@ComponentScan后面處理的。
2.延遲加載實現(xiàn)原理
前面我們已經(jīng)知道了在何處讀取注解配置的屬性,現(xiàn)在我們稍微看下其具體判斷實現(xiàn)的地方。
2.1 AbstractApplicationContext
Spring框架在刷新時會初始化非延遲加載的單例bean,而一般我們使用的bean都是單例的。其關鍵源碼如下:
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext { @Override public void refresh() throws BeansException, IllegalStateException { ... // 刷新流程中執(zhí)行初始化非延遲單例的方法 finishBeanFactoryInitialization(beanFactory); ... } protected void finishBeanFactoryInitialization( ConfigurableListableBeanFactory beanFactory) { ... // 實際執(zhí)行初始化非延遲加載單例 beanFactory.preInstantiateSingletons(); } }
2.2 DefaultListableBeanFactory
最終會調用Spring工廠來實例化,直接看到其實現(xiàn)方法源碼:
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable { // 當BeanDefinition被創(chuàng)建注冊到工廠中時bean定義的名字將會被保存到這個集合中 // 且里面的順序為注冊進來的順序 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()便是判斷非延遲加載的,因此前面獲取到的延遲加載 // 屬性會在這里進行判斷 if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) { // 這里面會判斷是否是FactoryBean類型,最終都會調用到getBean中 ... } } // 后續(xù)略過 ... } }
二、使用細節(jié)
讀取源碼其中一個目的是為了更好的實際的應用以及準確的把握對應功能的生效范圍,因此在使用延遲加載時需要額外注意一些點。
Spring框架延遲加載屬性在調用getBean之后將會失效,因為getBean方法是初始化bean的入口,這不難理解,那么平時我們使用@Autowired等自動注入注解時能和@Lazy注解一起使用嗎?
接下來我們從兩個實例來說明一下,這兩個實例都是使用平時的使用用法,在Component上添加@Lazy注解,且讓其實現(xiàn)InitializingBean接口,當Bean被加載時我們便能得知,看其是否會生效,示例如下:
1.@Lazy失效實例
1.1 Controller非延遲加載類
聲明一個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延遲加載類
再聲明一個Service服務類:
@Lazy @Service public class TestService implements InitializingBean { @Override public void afterPropertiesSet() throws Exception { System.out.println("testService Initializing"); } }
1.3 結果輸出
啟動程序后控制臺輸出:
testService Initializing
testController Initializing
啟動完Spring程序后輸出了TestService里面打印的字符串。這就奇怪了,明明使用了@Lazy注解,但是卻并沒有其作用,在Spring啟動項目時還是加載了這個類?這就涉及到@Autowired等自動注入注解的使用了,如果有興趣了解其實現(xiàn)的可以去看文章Spring框架原理之實例化bean和@Autowired實現(xiàn)原理。
由于Controller類不是延遲加載的,且里面使用@Autowired自動注入注解注入了Service,因此在程序初始化時Controller將會被初始化,同時在處理@Autowired注解的字段時,會調用getBean方法從Spring工廠中獲取字段的bean對象,因此通過@Autowired路線加在了Service,這就導致了@Lazy注解失效了,因此雖然沒通過refresh方法流程初始化,但是卻通過@Autowired的處理類初始化了。
2.@Lazy起效實例
想要@Lazy注解起作用,只需要改一步,即把Controller也改成@Lazy,讓其在啟動時不被加載,不觸發(fā)@Autowired注解依賴鏈的調用即可。
2.1 修改的Controller實例
修改后如下:
@Lazy @Controller public class TestController implements InitializingBean{ @Autowired private TestService testService; @Override public void afterPropertiesSet() throws Exception { System.out.println("testController Initializing"); } }
如果是這種配置@Lazy將會起作用,在項目啟動時將不會加載這兩個需要延遲加載的bean。
總結
從上面的例子我們可以總結及延伸出兩個注意點:
- 非延遲加載的類中不能自動注入延遲加載的類,會導致延遲加載失效;
- 如果想要實現(xiàn)某個類延遲加載使用自動注入功能時需要調用鏈前都不存在非延遲加載類,否則延遲加載失效。
作用效果總結圖如下:
其實@Scope指定原型和單例時有些情況也會導致原型bean“失效”,這又是另外一個故事了,后面有機會再分析一波。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
SpringBoot中@EnableAutoConfiguration注解源碼分析
這篇文章主要介紹了SpringBoot中@EnableAutoConfiguration注解源碼分析,@EnableAutoConfiguration,主要是用于加載Starter目錄包之外的、需要Spring自動生成Bean對象的、帶有@Configuration注解的類,需要的朋友可以參考下2023-08-08JDK都出到14了,你有什么理由不會函數(shù)式編程(推薦)
這篇文章主要介紹了JDK都出到14了,你有什么理由不會函數(shù)式編程,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-05-05