前言介紹
在上一章的學習中,我們對Bean的建立有了一個粗略的瞭解,接著本文挑一個比較重要的知識點Bean的懶載入進行學習
1、什麼是懶載入?
懶載入(Lazy-initialized beans):懶載入模式是bean在第一次呼叫時候被例項,而不是spring容器啟動時候,預設是不開啟的,( A lazy-initialized bean tells the IoC container to create a bean instance when it is first requested, rather than at startup.),通過改配置lazy-init="true"
2、實驗環境準備
實驗環境:
- SpringFramework版本
- Springframework5.0.x
- 開發環境
- JAR管理:gradle 4.9/ Maven3.+
- 開發IDE:IntelliJ IDEA 2018.2.5
- JDK:jdk1.8.0_31
- Git Server:Git fro window 2.8.3
- Git Client:SmartGit18.1.5(可選)
3、lazy-init使用
import org.springframework.beans.factory.InitializingBean;
/**
* <pre>
* SpringBean
* </pre>
*
* <pre>
* @author mazq
* 修改記錄
* 修改後版本: 修改人: 修改日期: 2020/11/05 10:50 修改內容:
* </pre>
*/
public class SpringBean implements InitializingBean {
public SpringBean(){
System.out.println("SpringBean建構函式");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("SpringBean afterPropertiesSet");
}
}
xml配置方式,在applicationContext.xml載入配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="springBean" lazy-init="true" class="com.example.bean.SpringBean" ></bean>
</beans>
註解方式,使用@Lazy即可
import com.example.bean.SpringBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.example.bean.A;
import org.springframework.context.annotation.Lazy;
/**
* <pre>
* AppConfiguration
* </pre>
*
* <pre>
* @author mazq
* 修改記錄
* 修改後版本: 修改人: 修改日期: 2020/11/05 10:26 修改內容:
* </pre>
*/
@Configuration
public class AppConfiguration {
@Bean
@Lazy // 開啟懶載入
// @Lazy(value = false) 預設
public SpringBean springBean() {
return new SpringBean();
}
}
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
// lazy-init開啟的bean,context.getBean呼叫時候才會被例項
SpringBean springBean = context.getBean(SpringBean.class);
System.out.println(springBean);
4、lazy載入機制原理
為什麼設定為lazy-init之後,Spring IoC容器啟動時候bean不會被例項?可以基於上一章內容找到答案
{@link org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization}
/**
* Finish the initialization of this context's bean factory,
* initializing all remaining singleton beans.
*/
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// Initialize conversion service for this context.
// 初始化ConversionService,這個bean用於將前端傳過來的引數和後端的 controller 方法上的引數進行繫結
if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
beanFactory.setConversionService(
beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
}
// Register a default embedded value resolver if no bean post-processor
// (such as a PropertyPlaceholderConfigurer bean) registered any before:
// at this point, primarily for resolution in annotation attribute values.
if (!beanFactory.hasEmbeddedValueResolver()) {
beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
}
// Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
// 先初始化LoadTimeWeaverAware 型別的Bean
// AspectJ 的內容,IoC的原始碼學習,先跳過
String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
for (String weaverAwareName : weaverAwareNames) {
getBean(weaverAwareName);
}
// Stop using the temporary ClassLoader for type matching.
beanFactory.setTempClassLoader(null);
// Allow for caching all bean definition metadata, not expecting further changes.
// 凍結配置,不讓bean 定義解析、載入、註冊
beanFactory.freezeConfiguration();
// Instantiate all remaining (non-lazy-init) singletons.
// 例項所有非懶載入的單例Bean
beanFactory.preInstantiateSingletons();
}
找到關鍵程式碼beanFactory.preInstantiateSingletons();
@Override
public void preInstantiateSingletons() throws BeansException {
if (logger.isDebugEnabled()) {
logger.debug("Pre-instantiating singletons in " + this);
}
// Iterate over a copy to allow for init methods which in turn register new bean definitions.
// While this may not be part of the regular factory bootstrap, it does otherwise work fine.
// 獲取beanName列表,this.beanDefinitionNames 儲存了所有的 beanNames
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
// Trigger initialization of all non-lazy singleton beans...
// 觸發所有非懶載入的單例bean初始化操作(lazy-init=false)
for (String beanName : beanNames) {
// 合併rootBean中的配置, <bean id="a" class="a" parent="p" />
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
// 非抽象(abstract = false)、非懶載入(lazy-init=false)的單例Bean(scope=singleton)
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
// 處理FactoryBean,注意對比BeanFactory和FactoryBean
if (isFactoryBean(beanName)) {
// factoryBean呼叫在beanName載入字首符號‘&’
// 為什麼要加‘&’,應該是做下標記,不過在bean建立過程要進行轉換,詳情請看下文
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
if (bean instanceof FactoryBean) {
FactoryBean<?> factory = (FactoryBean<?>) bean;
boolean isEagerInit;
// FactoryBean是SmartFactoryBean 的基類
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged(
(PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit,
getAccessControlContext());
}
else {
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean<?>) factory).isEagerInit());
}
if (isEagerInit) {
getBean(beanName);
}
}
}
else {
// 普通的Bean,調這個方法進行例項,往下跟
getBean(beanName);
}
}
}
// Trigger post-initialization callback for all applicable beans...
// SmartInitializingSingleton 的基類在這裡回撥
for (String beanName : beanNames) {
Object singletonInstance = getSingleton(beanName);
if (singletonInstance instanceof SmartInitializingSingleton) {
SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
smartSingleton.afterSingletonsInstantiated();
return null;
}, getAccessControlContext());
}
else {
smartSingleton.afterSingletonsInstantiated();
}
}
}
}
程式碼裡有isLazyInit()
的校驗,所以設定lazy-init=true的bean都不會隨著ioc容器啟動而被例項載入
學習歸納
綜上所述:
- 對於被修飾為lazy-init=false(預設情況)的bean Spring 容器初始化階段會被依賴注入,並且例項到單例池裡
- 對於懶載入的bean,context.getBean的時候會從快取裡獲取,因為容器初始化階段 Bean 已經初始化完成並快取了起來,但是還沒被例項,第一次呼叫時候就會被例項