Spring5.0源碼學習系列之淺談懶加載機制原理

前言介紹

附錄:Spring源碼學習專欄html

上一章的學習中,咱們對Bean的建立有了一個粗略的瞭解,接着本文挑一個比較重要的知識點Bean的懶加載進行學習前端

一、什麼是懶加載?

懶加載(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"java

二、實驗環境準備

實驗環境:spring

  • 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(可選)

三、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加載配置bootstrap

<?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);

四、lazy加載機制原理

爲何設置爲lazy-init以後,Spring IoC容器啓動時候bean不會被實例?能夠基於上一章內容找到答案緩存

{@link org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization}app

/**
 * 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();ide

@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 已經初始化完成並緩存了起來,可是還沒被實例,第一次調用時候就會被實例
相關文章
相關標籤/搜索