@Lazy注解源碼分析

@Lazy

一、基本信息

?? 作者 - Lex ?? 博客 - 我的CSDN ?? 文章目錄 - 所有文章 ?? 源碼地址 - @Lazy源碼

二典格、注解描述

@Lazy注解岛宦,它的主要用途是延遲依賴注入的初始化。通常情況下钝计,當(dāng) ApplicationContext 被啟動(dòng)和刷新時(shí)恋博,所有的單例 bean 會(huì)被立即初始化。但有時(shí)私恬,可能希望某些 bean 在首次使用時(shí)才被初始化。

三炼吴、注解源碼

@Lazy注解是 Spring 框架自 3.0 版本開始引入的一個(gè)核心注解本鸣,用于控制 bean 的懶加載行為。默認(rèn)情況下硅蹦,當(dāng) @Lazy 被應(yīng)用荣德,bean 不會(huì)在 Spring 容器啟動(dòng)時(shí)立即初始化闷煤,而是在首次被引用或請(qǐng)求時(shí)。這適用于通過 @Component@Bean 定義的 bean涮瞻。此外鲤拿,@Lazy 還可以用于注入點(diǎn),如 @Autowired署咽,創(chuàng)建一個(gè)懶解析代理近顷,從而實(shí)現(xiàn)延遲注入。當(dāng)用在 @Configuration 類上時(shí)宁否,它影響該配置中所有的 @Bean 定義窒升。

/**
 * 指示一個(gè)bean是否應(yīng)懶加載初始化。
 *
 * 可直接或間接地用于帶 org.springframework.stereotype.Component @Component 注解的類慕匠,
 * 或者用于帶有 Bean @Bean 注解的方法饱须。
 *
 * 如果此注解不在 @Component 或 @Bean 定義上,將會(huì)立即初始化台谊。
 * 如果存在并且設(shè)置為 true蓉媳,除非被另一個(gè)bean引用或從包圍的 org.springframework.beans.factory.BeanFactory BeanFactory 中顯式檢索,
 * 否則 @Bean 或 @Component 不會(huì)初始化锅铅。如果存在并設(shè)置為 false督怜,那么執(zhí)行積極初始化單例的bean工廠將在啟動(dòng)時(shí)實(shí)例化bean。
 *
 * 如果Lazy存在于 Configuration @Configuration 類上狠角,表示該 @Configuration 中的所有 @Bean 方法都應(yīng)懶加載号杠。
 * 如果在一個(gè)帶有 @Lazy 注解的 @Configuration 類的 @Bean 方法上 @Lazy 設(shè)置為false,則表示覆蓋了“默認(rèn)懶惰”行為丰歌,該bean應(yīng)立即初始化家肯。
 *
 * 除了其在組件初始化中的作用外,此注解也可用于帶有 org.springframework.beans.factory.annotation.Autowired 或 javax.inject.Inject 的注入點(diǎn):
 * 在這種情況下母赵,它會(huì)導(dǎo)致為所有受影響的依賴關(guān)系創(chuàng)建一個(gè)懶解析代理碍现,作為使用 org.springframework.beans.factory.ObjectFactory 或 javax.inject.Provider 的替代方法。
 *
 * @author Chris Beams
 * @author Juergen Hoeller
 * @since 3.0
 * @see Primary
 * @see Bean
 * @see Configuration
 * @see org.springframework.stereotype.Component
 */
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lazy {

    /**
     * 是否應(yīng)進(jìn)行懶加載初始化晓勇。
     */
    boolean value() default true;

}

四堂飞、主要功能

  1. 延遲初始化
    • 當(dāng) @Lazy 注解應(yīng)用于一個(gè) @Bean@Component 上時(shí),該 bean 不會(huì)在 Spring 容器啟動(dòng)時(shí)立即初始化绑咱。而是直到首次被引用或請(qǐng)求時(shí)才進(jìn)行初始化绰筛。
  2. 延遲注入
    • 當(dāng) @Lazy 注解應(yīng)用于 @Autowired 或其他注入點(diǎn)上時(shí),它導(dǎo)致為所注入的依賴關(guān)系創(chuàng)建一個(gè)懶解析代理描融,實(shí)現(xiàn)首次訪問時(shí)的延遲注入铝噩。

五、最佳實(shí)踐

首先來看看啟動(dòng)類入口窿克,上下文環(huán)境使用AnnotationConfigApplicationContext(此類是使用Java注解來配置Spring容器的方式)骏庸,構(gòu)造參數(shù)我們給定了一個(gè)MyConfiguration組件類毛甲,然后從中獲取一個(gè) MyService bean 并調(diào)用其 show 方法。

public class LazyApplication {

    public static void main(String[] args) {
        System.out.println("啟動(dòng) Spring ApplicationContext...");
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
        System.out.println("啟動(dòng)完成 Spring ApplicationContext...");

        System.out.println("獲取MyService...");
        MyService myService = context.getBean(MyService.class);

        System.out.println("調(diào)用show方法...");
        myService.show();
    }
}

這里使用@Bean注解具被,MyBean 被標(biāo)注了 @Lazy玻募,這意味著它只會(huì)在首次被請(qǐng)求時(shí)才會(huì)初始化。MyService 沒有 @Lazy 注解一姿,所以它會(huì)被立即初始化七咧。

@Configuration
public class MyConfiguration {

    @Bean
    @Lazy
    public MyBean myBean(){
        System.out.println("MyBean 初始化了!");
        return new MyBean();
    }

    @Bean
    public MyService myService(){
        return new MyService();
    }
}

MyBean 類定義很簡單。它有一個(gè)構(gòu)造函數(shù)啸蜜,在構(gòu)造函數(shù)中打印了 "MyBean 的構(gòu)造函數(shù)被調(diào)用了!"坑雅,并有一個(gè) show 方法,該方法打印 "hello world!"衬横。

public class MyBean {

    public MyBean() {
        System.out.println("MyBean 的構(gòu)造函數(shù)被調(diào)用了!");
    }

    public void show() {
        System.out.println("hello world!");
    }
}

MyService 類有一個(gè)對(duì) MyBean 的引用裹粤,而該引用通過 @Autowired 進(jìn)行依賴注入。由于我們還在這個(gè)注入點(diǎn)上添加了 @Lazy 注解蜂林,這意味著 myBean 的實(shí)際注入將被延遲遥诉,直到我們首次嘗試訪問它時(shí)。

public class MyService  {

    @Autowired
    @Lazy
    private MyBean myBean;

    public void show() {
        System.out.println("MyBean Class = " + myBean.getClass());
        myBean.show();
    }
}

運(yùn)行結(jié)果發(fā)現(xiàn)

  1. 延遲初始化:
    • 盡管 MyService 在應(yīng)用上下文啟動(dòng)后可用噪叙,但由于 MyBean 上的 @Lazy 注解矮锈,MyBean 在啟動(dòng)時(shí)并未被初始化。
    • 只有在首次訪問或使用 MyBean 時(shí)睁蕾,它才真正被初始化苞笨。這在 "MyBean 初始化了!" 和 "MyBean 的構(gòu)造函數(shù)被調(diào)用了!" 的輸出中得到了體現(xiàn)。
  2. 延遲注入:
    • MyServiceshow 方法首次被調(diào)用時(shí)子眶,由于 @Autowired@Lazy 注解的組合瀑凝,Spring 不是直接注入原始的 MyBean 實(shí)例,而是注入一個(gè)代理對(duì)象臭杰。
    • 這個(gè)代理對(duì)象的類名為 com.xcs.spring.bean.MyBean$$EnhancerBySpringCGLIB$$2a517c55粤咪,表明它是由 Spring 的 CGLIB 動(dòng)態(tài)生成的。
    • 當(dāng)嘗試訪問該代理對(duì)象上的方法(如 show 方法)時(shí)渴杆,代理會(huì)確保真正的 MyBean 被初始化并代理該方法調(diào)用寥枝。
啟動(dòng) Spring ApplicationContext...
啟動(dòng)完成 Spring ApplicationContext...
獲取MyService...
調(diào)用show方法...
MyBean Class = class com.xcs.spring.bean.MyBean$$EnhancerBySpringCGLIB$$2a517c55
MyBean 初始化了!
MyBean 的構(gòu)造函數(shù)被調(diào)用了!
hello world!

六、時(shí)序圖

Bean注冊(cè)時(shí)序圖

sequenceDiagram 
LazyApplication->>AnnotationConfigApplicationContext:AnnotationConfigApplicationContext(componentClasses)<br>創(chuàng)建應(yīng)用上下文
AnnotationConfigApplicationContext->>AbstractApplicationContext:refresh()<br>刷新應(yīng)用上下文
AbstractApplicationContext->>AbstractApplicationContext:invokeBeanFactoryPostProcessors(beanFactory)<br>調(diào)用BFPP方法
AbstractApplicationContext->>PostProcessorRegistrationDelegate:invokeBeanFactoryPostProcessors(beanFactory,beanFactoryPostProcessors)<br>委托BFPP處理
PostProcessorRegistrationDelegate->>PostProcessorRegistrationDelegate:invokeBeanDefinitionRegistryPostProcessors(postProcessors,registry,applicationStartup)<br>調(diào)用BDRPP方法
PostProcessorRegistrationDelegate->>ConfigurationClassPostProcessor:postProcessBeanDefinitionRegistry(registry)<br>處理Bean定義
ConfigurationClassPostProcessor->>ConfigurationClassPostProcessor:processConfigBeanDefinitions(registry)<br>處理配置類Bean
ConfigurationClassPostProcessor->>ConfigurationClassBeanDefinitionReader:loadBeanDefinitions(configurationModel)<br>加載Bean定義
ConfigurationClassBeanDefinitionReader->>ConfigurationClassBeanDefinitionReader:loadBeanDefinitionsForConfigurationClass(configClass,trackedConditionEvaluator)<br>為配置類加載
ConfigurationClassBeanDefinitionReader->>ConfigurationClassBeanDefinitionReader:loadBeanDefinitionsForBeanMethod(beanMethod)<br>為@Bean方法加載
ConfigurationClassBeanDefinitionReader->>AnnotationConfigUtils:processCommonDefinitionAnnotations(abd,metadata)<br>處理注解定義
AnnotationConfigUtils->>AnnotationConfigUtils:attributesFor(metadata, Lazy.class)<br>獲取注解屬性
AnnotationConfigUtils->>AbstractBeanDefinition:setLazyInit(lazyInit)<br>設(shè)置懶加載屬性

Bean延遲創(chuàng)建時(shí)序圖

sequenceDiagram 
DependsOnApplication->>AnnotationConfigApplicationContext:AnnotationConfigApplicationContext(componentClasses)<br>創(chuàng)建應(yīng)用上下文
AnnotationConfigApplicationContext->>AbstractApplicationContext:refresh()<br>刷新應(yīng)用上下文
AbstractApplicationContext->>AbstractApplicationContext:finishBeanFactoryInitialization(beanFactory)<br>完成bean工廠初始化
AbstractApplicationContext->>DefaultListableBeanFactory:preInstantiateSingletons()<br>預(yù)實(shí)例化單例beans
DefaultListableBeanFactory->>AbstractBeanDefinition:isLazyInit()
AbstractBeanDefinition->>DefaultListableBeanFactory:返回Bean是否懶加載
DefaultListableBeanFactory->>AbstractBeanFactory:getBean(beanName)
alt Bean是懶加載
    AbstractBeanFactory->>DefaultListableBeanFactory: 執(zhí)行初始化Bean對(duì)象
else Bean不是懶加載
    AbstractBeanFactory->>DefaultListableBeanFactory: 跳過Bean初始化磁奖,等待真正使用時(shí)在初始化
end

Bean延遲注入時(shí)序圖

sequenceDiagram
Title: InstantiationAwareBeanPostProcessor時(shí)序圖
InstantiationAwareBeanPostProcessorApplication->>AnnotationConfigApplicationContext:AnnotationConfigApplicationContext<br>開始初始化
AnnotationConfigApplicationContext->>AbstractApplicationContext:refresh<br>刷新上下文
AbstractApplicationContext->>AbstractApplicationContext:finishBeanFactoryInitialization<br>實(shí)例化非懶加載的單列Bean
AbstractApplicationContext->>DefaultListableBeanFactory:preInstantiateSingletons<br>預(yù)實(shí)例化Singleton
DefaultListableBeanFactory->>AbstractBeanFactory:getBean<br>根據(jù)beanName獲取對(duì)象
AbstractBeanFactory->>AbstractBeanFactory:doGetBean<br>返回指定bean的實(shí)例
AbstractBeanFactory->>DefaultSingletonBeanRegistry:getSingleton<br>獲取單例對(duì)象
DefaultSingletonBeanRegistry->>AbstractBeanFactory:getObject<br>ObjectFactory接口的工廠方法
AbstractBeanFactory->>AbstractAutowireCapableBeanFactory:createBean<br>創(chuàng)建一個(gè)bean實(shí)例囊拜,填充bean實(shí)例,應(yīng)用后處理器
AbstractAutowireCapableBeanFactory->>AbstractAutowireCapableBeanFactory:doCreateBean(beanName,mbd,args)
AbstractAutowireCapableBeanFactory->>AbstractAutowireCapableBeanFactory:populateBean(beanName,mbd,bw)
AbstractAutowireCapableBeanFactory->>AutowiredAnnotationBeanPostProcessor:postProcessProperties(pvs,bean,beanName) 
AutowiredAnnotationBeanPostProcessor->>AutowiredAnnotationBeanPostProcessor:findAutowiringMetadata(beanName,clazz,pvs)
AutowiredAnnotationBeanPostProcessor->>InjectionMetadata:inject(target,beanName,pvs)
InjectionMetadata->>AutowiredFieldElement:inject(bean,beanName,pvs)
AutowiredFieldElement->>AutowiredFieldElement:resolveFieldValue(field,bean,beanName)
AutowiredFieldElement->>DefaultListableBeanFactory:resolveDependency(descriptor,requestingBeanName,autowiredBeanNames,typeConverter)
DefaultListableBeanFactory->>DefaultListableBeanFactory:getAutowireCandidateResolver()
DefaultListableBeanFactory->>ContextAnnotationAutowireCandidateResolver:getLazyResolutionProxyIfNecessary(descriptor,beanName)
ContextAnnotationAutowireCandidateResolver->>ContextAnnotationAutowireCandidateResolver:isLazy(descriptor)
ContextAnnotationAutowireCandidateResolver->>ContextAnnotationAutowireCandidateResolver:buildLazyResolutionProxy(descriptor, beanName)
ContextAnnotationAutowireCandidateResolver->>ProxyFactory:getProxy(classLoader)
ProxyFactory->>ContextAnnotationAutowireCandidateResolver:返回被代理的對(duì)象
ContextAnnotationAutowireCandidateResolver->>DefaultListableBeanFactory:返回被代理的對(duì)象
DefaultListableBeanFactory->>AutowiredFieldElement:返回被代理的對(duì)象
AutowiredFieldElement->>Field:field.set(bean, value)注入被代理的對(duì)象

七点寥、源碼分析

首先來看看啟動(dòng)類入口艾疟,上下文環(huán)境使用AnnotationConfigApplicationContext(此類是使用Java注解來配置Spring容器的方式),構(gòu)造參數(shù)我們給定了一個(gè)MyConfiguration組件類敢辩,然后從中獲取一個(gè) MyService bean 并調(diào)用其 show 方法蔽莱。

public class LazyApplication {

    public static void main(String[] args) {
        System.out.println("啟動(dòng) Spring ApplicationContext...");
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
        System.out.println("啟動(dòng)完成 Spring ApplicationContext...");

        System.out.println("獲取MyService...");
        MyService myService = context.getBean(MyService.class);

        System.out.println("調(diào)用show方法...");
        myService.show();
    }
}

org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext構(gòu)造函數(shù)中,執(zhí)行了三個(gè)步驟戚长。

public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
    this();
    register(componentClasses);
    refresh();
}

org.springframework.context.support.AbstractApplicationContext#refresh方法中盗冷,我們重點(diǎn)關(guān)注一下finishBeanFactoryInitialization(beanFactory)這方法會(huì)對(duì)實(shí)例化所有剩余非懶加載的單列Bean對(duì)象,其他方法不是本次源碼閱讀的重點(diǎn)暫時(shí)忽略同廉。

@Override
public void refresh() throws BeansException, IllegalStateException {
    // ... [代碼部分省略以簡化]
    // 實(shí)例化所有剩余非懶加載的單列Bean對(duì)象
    finishBeanFactoryInitialization(beanFactory);
    // ... [代碼部分省略以簡化]
}

org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization方法中仪糖,會(huì)繼續(xù)調(diào)用DefaultListableBeanFactory類中的preInstantiateSingletons方法來完成所有剩余非懶加載的單列Bean對(duì)象。

protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
    // ... [代碼部分省略以簡化]
    // 完成所有剩余非懶加載的單列Bean對(duì)象迫肖。
    beanFactory.preInstantiateSingletons();
}

延遲初始化

org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons方法中锅劝,確保所有非懶加載的單例 beans 在容器啟動(dòng)時(shí)都被初始化,除非它們顯式地標(biāo)記為懶加載蟆湖。這也是為什么 @Lazy 注解對(duì)于那些想要推遲 bean 初始化的場景非常有用故爵。

public void preInstantiateSingletons() throws BeansException {
    // ... [代碼部分省略以簡化]
    // 復(fù)制Bean名稱列表
    List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

    // 初始化非懶加載單例
    for (String beanName : beanNames) {
        RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
        // Bean屬性檢查
        if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
            // 是否為工廠Bean
            if (isFactoryBean(beanName)) {
                Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
                // 是否為FactoryBean實(shí)例
                if (bean instanceof FactoryBean) {
                    FactoryBean<?> factory = (FactoryBean<?>) bean;
                    boolean isEagerInit;
                    // 安全權(quán)限檢查
                    if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
                        isEagerInit = AccessController.doPrivileged(
                            (PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit,
                            getAccessControlContext());
                    }
                    else {
                        // 是否立即初始化
                        isEagerInit = (factory instanceof SmartFactoryBean &&
                                       ((SmartFactoryBean<?>) factory).isEagerInit());
                    }
                    // 立即初始化Bean
                    if (isEagerInit) {
                        getBean(beanName);
                    }
                }
            }
            else {
                // 獲取/創(chuàng)建Bean
                getBean(beanName);
            }
        }
    }
    // ... [代碼部分省略以簡化]
}

延遲注入

org.springframework.beans.factory.support.AbstractBeanFactory#getBean()方法中,又調(diào)用了doGetBean方法來實(shí)際執(zhí)行創(chuàng)建Bean的過程隅津,傳遞給它bean的名稱和一些其他默認(rèn)的參數(shù)值诬垂。此處,doGetBean負(fù)責(zé)大部分工作伦仍,如查找bean定義结窘、創(chuàng)建bean(如果尚未創(chuàng)建)、處理依賴關(guān)系等充蓝。

@Override
public Object getBean(String name) throws BeansException {
    return doGetBean(name, null, null, false);
}

org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean方法中隧枫,首先檢查所請(qǐng)求的bean是否是一個(gè)單例并且已經(jīng)創(chuàng)建。如果尚未創(chuàng)建谓苟,它將創(chuàng)建一個(gè)新的實(shí)例官脓。在這個(gè)過程中,它處理可能的異常情況娜谊,如循環(huán)引用确买,并確保返回的bean是正確的類型。這是Spring容器bean生命周期管理的核心部分纱皆。

protected <T> T doGetBean(
        String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
        throws BeansException {
    // ... [代碼部分省略以簡化]

    // 開始創(chuàng)建bean實(shí)例
    if (mbd.isSingleton()) {
        // 如果bean是單例的湾趾,我們會(huì)嘗試從單例緩存中獲取它
        // 如果不存在,則使用lambda創(chuàng)建一個(gè)新的實(shí)例
        sharedInstance = getSingleton(beanName, () -> {
            try {
                // 嘗試創(chuàng)建bean實(shí)例
                return createBean(beanName, mbd, args);
            }
            catch (BeansException ex) {
                // ... [代碼部分省略以簡化]
            }
        });
        // 對(duì)于某些bean(例如FactoryBeans)派草,可能需要進(jìn)一步處理以獲取真正的bean實(shí)例
        beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
    }
    // ... [代碼部分省略以簡化]

    // 確保返回的bean實(shí)例與請(qǐng)求的類型匹配
    return adaptBeanInstance(name, beanInstance, requiredType);
}

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton()方法中搀缠,主要負(fù)責(zé)從單例緩存中獲取一個(gè)已存在的bean實(shí)例,或者使用提供的ObjectFactory創(chuàng)建一個(gè)新的實(shí)例近迁。這是確保bean在Spring容器中作為單例存在的關(guān)鍵部分艺普。

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    // ... [代碼部分省略以簡化]

    // 同步訪問單例對(duì)象緩存,確保線程安全
    synchronized (this.singletonObjects) {
        // 從緩存中獲取單例對(duì)象
        Object singletonObject = this.singletonObjects.get(beanName);

        // 如果緩存中沒有找到
        if (singletonObject == null) {
            // ... [代碼部分省略以簡化]

            try {
                // 使用工廠創(chuàng)建新的單例實(shí)例
                singletonObject = singletonFactory.getObject();
                newSingleton = true;
            }
            catch (IllegalStateException ex) {
                // ... [代碼部分省略以簡化]
            }
            catch (BeanCreationException ex) {
                // ... [代碼部分省略以簡化]
            }
            finally {
                // ... [代碼部分省略以簡化]
            }

            // ... [代碼部分省略以簡化]
        }

        // 返回單例對(duì)象
        return singletonObject;
    }
}

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean()方法中,主要的邏輯是調(diào)用 doCreateBean歧譬,這是真正進(jìn)行 bean 實(shí)例化岸浑、屬性填充和初始化的地方。這個(gè)方法會(huì)返回新創(chuàng)建的 bean 實(shí)例瑰步。

@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
    throws BeanCreationException {
    
    // ... [代碼部分省略以簡化]
    
    try {
        // 正常的bean實(shí)例化矢洲、屬性注入和初始化。
        // 這里是真正進(jìn)行bean創(chuàng)建的部分缩焦。
        Object beanInstance = doCreateBean(beanName, mbdToUse, args);
        // 記錄bean成功創(chuàng)建的日志
        if (logger.isTraceEnabled()) {
            logger.trace("Finished creating instance of bean '" + beanName + "'");
        }
        return beanInstance;
    }
    catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
        // ... [代碼部分省略以簡化]
    }
    catch (Throwable ex) {
        // ... [代碼部分省略以簡化]
    }
}

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean方法中读虏,對(duì) bean 的屬性進(jìn)行注入。

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
        throws BeanCreationException {

    // ... [代碼部分省略以簡化]
    
    try {
        // 屬性注入:這一步將配置中的屬性值注入到bean實(shí)例中袁滥。例如盖桥,XML中定義的屬性或使用@Autowired和@Value注解的屬性都會(huì)在這里被注入
        populateBean(beanName, mbd, instanceWrapper);

        // ... [代碼部分省略以簡化]
    } 
    catch (Throwable ex) {
        // ... [代碼部分省略以簡化]
    }
    
    // ... [代碼部分省略以簡化]
}

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean方法中,如果一個(gè)屬性被標(biāo)記為 @Autowired题翻,并且與 @Lazy 結(jié)合使用揩徊,那么實(shí)際的懶加載邏輯會(huì)在這個(gè)步驟中的其他部分被處理(特別是通過 AutowiredAnnotationBeanPostProcessor)。

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
    // ... [代碼部分省略以簡化]
    // 對(duì)每一個(gè)InstantiationAwareBeanPostProcessor進(jìn)行處理藐握,這些處理器可能會(huì)修改Bean的屬性值靴拱。
    for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
        // 嘗試使用新版本的方法 postProcessProperties
        PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
         // ... [代碼部分省略以簡化]
    }
    // ... [代碼部分省略以簡化]
}

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessProperties方法中,用于處理 @Autowired 注解的依賴注入猾普。

@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
    // 根據(jù)bean的名稱和類查找@Autowired注解元數(shù)據(jù)
    InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
    try {
        // 執(zhí)行實(shí)際的依賴注入
        metadata.inject(bean, beanName, pvs);
    }
    catch (BeanCreationException ex) {
        // 拋出bean創(chuàng)建異常
        throw ex;
    }
    catch (Throwable ex) {
        // 處理其他類型的異常袜炕,轉(zhuǎn)換為Bean創(chuàng)建異常
        throw new BeanCreationException(beanName, "依賴自動(dòng)注入失敗", ex);
    }
    // 返回屬性值
    return pvs;
}

org.springframework.beans.factory.annotation.InjectionMetadata#inject方法中,遍歷所有這些元素并調(diào)用其 inject 方法初家,這樣實(shí)現(xiàn)了對(duì)帶有注解的字段或方法的實(shí)際注入偎窘。

public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
    // 獲取已校驗(yàn)的注入元素
    Collection<InjectedElement> checkedElements = this.checkedElements;
    // 如果沒有已校驗(yàn)的元素,則使用所有注入元素
    Collection<InjectedElement> elementsToIterate =
        (checkedElements != null ? checkedElements : this.injectedElements);
    
    if (!elementsToIterate.isEmpty()) {
        // 遍歷所有待注入的元素(字段或方法)
        for (InjectedElement element : elementsToIterate) {
            // 執(zhí)行實(shí)際的注入操作
            element.inject(target, beanName, pvs);
        }
    }
}

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject方法中溜在,這個(gè) inject 方法的核心邏輯是解析 @Autowired 字段的值(即找到匹配的 bean 依賴)并注入到目標(biāo) bean 中陌知。在這個(gè)過程中,使用緩存以提高性能掖肋。

@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
    // 獲取帶有@Autowired注解的字段
    Field field = (Field) this.member;
    Object value;
    // 如果字段的值已經(jīng)被緩存仆葡,則直接從緩存中獲取
    if (this.cached) {
        try {
            value = resolvedCachedArgument(beanName, this.cachedFieldValue);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // 如果緩存中的bean意外被移除 -> 重新解析
            value = resolveFieldValue(field, bean, beanName);
        }
    }
    else {
        // 解析字段的值(即找到要注入的bean)
        value = resolveFieldValue(field, bean, beanName);
    }
    if (value != null) {
        // 使字段可訪問(例如,如果它是私有的)
        ReflectionUtils.makeAccessible(field);
        // 將解析出的值(bean)注入到目標(biāo)bean的字段中
        field.set(bean, value);
    }
}

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#resolveFieldValue方法中志笼,主要用于解析 @Autowired 注解的字段值沿盅。它確定了應(yīng)該為目標(biāo)字段注入哪個(gè) bean。

@Nullable
private Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) {
    // ... [代碼部分省略以簡化]

    Object value;
    try {
        // 解析依賴:這里的核心邏輯是使用bean工廠去解析字段的依賴纫溃。它會(huì)查找合適的bean來注入到此字段腰涧。
        value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
    }
    catch (BeansException ex) {
        // 當(dāng)無法解析依賴時(shí),拋出異常
        throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
    }
    
    // ... [代碼部分省略以簡化]

    // 返回解析到的值(bean)
    return value; 
}

org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveDependency方法中紊浩,這里的關(guān)鍵邏輯是 getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary窖铡,該方法嘗試為描述的依賴關(guān)系獲取一個(gè)懶加載代理疗锐。如果該依賴關(guān)系標(biāo)記為懶加載 (@Lazy),并且被 @Autowired 引用费彼,那么它將返回一個(gè)懶加載代理滑臊,而不是實(shí)際的 bean。這樣敌买,只有當(dāng)應(yīng)用程序真正訪問該依賴關(guān)系時(shí)简珠,實(shí)際的 bean 才會(huì)被初始化阶界。

@Override
@Nullable
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
                                @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
    // ... [代碼部分省略以簡化]
    
    // 嘗試為描述的依賴關(guān)系獲取一個(gè)懶加載代理虹钮。如果依賴是懶加載的,這將返回一個(gè)代理對(duì)象膘融。
    Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
        descriptor, requestingBeanName);

    // 如果沒有為懶加載的依賴關(guān)系獲取到代理芙粱,則嘗試直接解析依賴關(guān)系
    if (result == null) {
        result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
    }

    return result; // 返回解析得到的bean或者懶加載代理
}

org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver#getLazyResolutionProxyIfNecessary方法中,如果是懶加載氧映,會(huì)調(diào)用 buildLazyResolutionProxy 來創(chuàng)建一個(gè)代理春畔,當(dāng)首次訪問該代理時(shí),代理會(huì)觸發(fā)實(shí)際的 bean 初始化岛都。

@Override
@Nullable
public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) {
    // 判斷依賴描述是否被標(biāo)記為懶加載
    // 如果是懶加載律姨,為其構(gòu)建一個(gè)懶加載代理
    // 如果不是懶加載,則返回null
    return (isLazy(descriptor) ? buildLazyResolutionProxy(descriptor, beanName) : null);
}

org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver#buildLazyResolutionProxy方法中臼疫,核心部分是 TargetSourceProxyFactory择份。當(dāng)我們?cè)L問這個(gè)懶加載代理時(shí),TargetSourcegetTarget 方法會(huì)被調(diào)用烫堤,它會(huì)解析和返回真正的 bean(或其他依賴項(xiàng))荣赶。使用 ProxyFactory,可以為給定的 TargetSource 創(chuàng)建一個(gè)新的代理鸽斟。這是 @Lazy 注解在字段注入時(shí)的實(shí)際實(shí)現(xiàn)拔创,確保在首次訪問字段時(shí)才觸發(fā)依賴的解析和bean的初始化。

protected Object buildLazyResolutionProxy(final DependencyDescriptor descriptor, final @Nullable String beanName) {
    // 獲取當(dāng)前的BeanFactory
    BeanFactory beanFactory = getBeanFactory();
    // 確認(rèn)BeanFactory是DefaultListableBeanFactory的實(shí)例
    Assert.state(beanFactory instanceof DefaultListableBeanFactory,
                 "BeanFactory needs to be a DefaultListableBeanFactory");
    final DefaultListableBeanFactory dlbf = (DefaultListableBeanFactory) beanFactory;

    // 創(chuàng)建一個(gè)目標(biāo)源(TargetSource)用于懶加載代理
    TargetSource ts = new TargetSource() {
        // 獲取依賴的類型
        @Override
        public Class<?> getTargetClass() {
            return descriptor.getDependencyType();
        }
        @Override
        public boolean isStatic() {
            return false;
        }
        // 當(dāng)訪問代理時(shí)富蓄,該方法被調(diào)用來解析實(shí)際的依賴關(guān)系
        @Override
        public Object getTarget() {
            // ... [代碼部分省略以簡化]
            return target; // 返回解析得到的bean
        }
        @Override
        public void releaseTarget(Object target) {
        }
    };

    // 使用Spring的ProxyFactory創(chuàng)建一個(gè)新的代理
    ProxyFactory pf = new ProxyFactory();
    pf.setTargetSource(ts);
    Class<?> dependencyType = descriptor.getDependencyType();
    if (dependencyType.isInterface()) {
        pf.addInterface(dependencyType);
    }
    // 返回懶加載代理
    return pf.getProxy(dlbf.getBeanClassLoader());
}

八剩燥、注意事項(xiàng)

  1. 默認(rèn)行為
    • 如果沒有使用 @Lazy 注解,Spring 容器會(huì)在啟動(dòng)時(shí)立即實(shí)例化單例 bean立倍。通過使用 @Lazy灭红,我們可以改變這個(gè)行為,使得 bean 只在首次請(qǐng)求時(shí)被初始化帐萎。
  2. 構(gòu)造函數(shù)注入
    • 如果一個(gè)懶加載的 bean 依賴于另一個(gè)非懶加載的 bean比伏,那么該懶加載的 bean 會(huì)在容器啟動(dòng)時(shí)被初始化,因?yàn)樗囊蕾囆枰肌_@種情況常常在構(gòu)造函數(shù)注入時(shí)出現(xiàn)赁项。
  3. 上下文的生命周期
    • @Lazy 對(duì)于應(yīng)用上下文的啟動(dòng)速度可能有益,因?yàn)樯倭艘恍┘磿r(shí)初始化的工作。但請(qǐng)注意悠菜,延遲初始化可能會(huì)導(dǎo)致我們?cè)谑状卧L問 bean 時(shí)遇到延遲舰攒。
  4. @Autowired 結(jié)合使用
    • 如果我們?cè)谝粋€(gè)字段或 setter 方法上同時(shí)使用 @Autowired@Lazy,Spring 會(huì)注入一個(gè)代理對(duì)象悔醋。這個(gè)代理對(duì)象會(huì)在我們首次訪問它時(shí)觸發(fā)真正的 bean 初始化摩窃。
  5. 懶加載的代理
    • 當(dāng)與 @Autowired 結(jié)合使用時(shí),要確保 bean 的類型與代理類型兼容芬骄。如果注入的 bean 類型是一個(gè)接口猾愿,Spring 會(huì)創(chuàng)建一個(gè)基于接口的代理。如果是一個(gè)類账阻,Spring 會(huì)創(chuàng)建一個(gè)基于類的代理蒂秘。
  6. 錯(cuò)誤處理
    • 延遲初始化意味著某些錯(cuò)誤可能在應(yīng)用啟動(dòng)時(shí)不會(huì)被立即發(fā)現(xiàn),例如 bean 配置錯(cuò)誤淘太。這樣的錯(cuò)誤只有在實(shí)際嘗試初始化 bean 時(shí)才會(huì)被拋出姻僧。
  7. 在組件類上使用
    • 對(duì)于直接或間接使用 @Component@Service蒲牧、@Repository@Controller 注解的類撇贺,可以使用 @Lazy 注解來使這些組件在首次被注入或查找時(shí)才被初始化。

九冰抢、總結(jié)

最佳實(shí)踐總結(jié)

  1. 上下文初始化:
    • 使用 AnnotationConfigApplicationContext 初始化應(yīng)用上下文是針對(duì)基于Java的配置的推薦做法松嘶。
    • 提供一個(gè)配置類(如 MyConfiguration)作為參數(shù)可以幫助上下文知道如何加載和配置beans。
  2. 懶加載配置:
    • 通過在配置類的 @Bean 方法上添加 @Lazy 注解晒屎,可以確保特定的bean只在首次請(qǐng)求時(shí)被初始化喘蟆,而不是在應(yīng)用上下文啟動(dòng)時(shí)。
    • 這可以提高應(yīng)用啟動(dòng)速度鼓鲁,特別是對(duì)于資源密集型的beans或需要長時(shí)間初始化的beans蕴轨。
  3. 依賴注入:
    • 使用 @Autowired 注解是Spring的核心特性,它可以自動(dòng)注入bean的依賴關(guān)系骇吭。
    • 當(dāng)與 @Lazy 注解組合使用時(shí)橙弱,Spring會(huì)注入一個(gè)代理對(duì)象而不是實(shí)際的bean實(shí)例。這個(gè)代理對(duì)象在首次訪問時(shí)會(huì)觸發(fā)真正的bean初始化燥狰。
  4. 理解代理:
    • 由于延遲注入棘脐,通過 @Autowired@Lazy 注解注入的對(duì)象是一個(gè)由CGLIB生成的代理對(duì)象。
    • 這個(gè)代理對(duì)象負(fù)責(zé)在首次訪問時(shí)初始化真正的bean龙致。
  5. 輸出與驗(yàn)證:
    • 通過在bean的初始化和方法調(diào)用中添加日志或打印語句蛀缝,可以驗(yàn)證和觀察懶加載和代理的行為。
    • 這對(duì)于確保應(yīng)用的預(yù)期行為和性能調(diào)優(yōu)非常有用目代。

源碼分析總結(jié)

  1. 啟動(dòng)及初始化:
    • 使用AnnotationConfigApplicationContext初始化應(yīng)用上下文屈梁。
    • AnnotationConfigApplicationContext的構(gòu)造函數(shù)中嗤练,執(zhí)行了注冊(cè)(register)和刷新(refresh)方法。
  2. Bean的實(shí)例化:
    • 在上下文刷新過程中在讶,finishBeanFactoryInitialization(beanFactory)方法確保所有非懶加載的單例Beans被實(shí)例化煞抬。
    • DefaultListableBeanFactory#preInstantiateSingletons方法確保所有非懶加載的單例Beans在容器啟動(dòng)時(shí)被初始化。
  3. 延遲初始化:
    • 如果Bean被標(biāo)記為@Lazy构哺,它將不會(huì)在容器啟動(dòng)時(shí)被初始化革答,但只在首次請(qǐng)求時(shí)。
    • DefaultListableBeanFactory#preInstantiateSingletons方法中曙强,對(duì)于isLazyInit返回true的Beans残拐,不會(huì)進(jìn)行預(yù)初始化。
  4. Bean的獲取與依賴注入:
    • 使用AbstractBeanFactory#getBean方法獲取Bean實(shí)例旗扑。
    • 如果Bean尚未創(chuàng)建蹦骑,doGetBean方法將執(zhí)行Bean的實(shí)際創(chuàng)建,包括解析依賴關(guān)系臀防、處理循環(huán)引用等。
    • 對(duì)于單例Beans边败,它們將被緩存袱衷,確保每次都返回相同的實(shí)例。
    • 通過AbstractAutowireCapableBeanFactory#createBean來進(jìn)行實(shí)際的Bean創(chuàng)建笑窜,并且將其屬性通過populateBean方法注入致燥。
  5. 延遲注入:
    • 如果一個(gè)字段或?qū)傩员?code>@Autowired注解,并與@Lazy結(jié)合使用排截,實(shí)際的懶加載邏輯會(huì)在屬性填充階段被處理嫌蚤。
    • 使用AutowiredAnnotationBeanPostProcessor來處理帶有@Autowired注解的屬性的注入。
    • AutowiredFieldElement#inject方法中断傲,如果字段被標(biāo)記為@Lazy脱吱,Spring不會(huì)直接注入真實(shí)的Bean,而是注入一個(gè)懶加載代理认罩。
    • 這個(gè)懶加載代理的實(shí)際行為是在首次訪問時(shí)觸發(fā)真正的Bean初始化箱蝠。
  6. 懶加載代理的創(chuàng)建:
    • 使用ContextAnnotationAutowireCandidateResolver來檢查依賴關(guān)系是否需要懶加載。
    • 如果需要懶加載垦垂,它將使用buildLazyResolutionProxy方法來為該依賴關(guān)系創(chuàng)建一個(gè)代理宦搬。
    • 這個(gè)代理的行為是:在首次訪問時(shí),它會(huì)解析和返回真正的Bean或其他依賴項(xiàng)劫拗。
    • 使用Spring的ProxyFactory來為給定的目標(biāo)源創(chuàng)建新的代理间校。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市页慷,隨后出現(xiàn)的幾起案子憔足,更是在濱河造成了極大的恐慌聂渊,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件四瘫,死亡現(xiàn)場離奇詭異汉嗽,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)找蜜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門饼暑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人洗做,你說我怎么就攤上這事弓叛。” “怎么了诚纸?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵撰筷,是天一觀的道長。 經(jīng)常有香客問我畦徘,道長毕籽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任井辆,我火速辦了婚禮关筒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘杯缺。我一直安慰自己蒸播,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布萍肆。 她就那樣靜靜地躺著袍榆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪塘揣。 梳的紋絲不亂的頭發(fā)上包雀,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音勿负,去河邊找鬼馏艾。 笑死,一個(gè)胖子當(dāng)著我的面吹牛奴愉,可吹牛的內(nèi)容都是我干的琅摩。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼锭硼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼房资!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起檀头,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤轰异,失蹤者是張志新(化名)和其女友劉穎岖沛,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體搭独,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡婴削,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了牙肝。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片唉俗。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖配椭,靈堂內(nèi)的尸體忽然破棺而出虫溜,到底是詐尸還是另有隱情,我是刑警寧澤股缸,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布衡楞,位于F島的核電站,受9級(jí)特大地震影響敦姻,放射性物質(zhì)發(fā)生泄漏瘾境。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一替劈、第九天 我趴在偏房一處隱蔽的房頂上張望寄雀。 院中可真熱鬧,春花似錦陨献、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至沮协,卻和暖如春龄捡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背慷暂。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來泰國打工聘殖, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人行瑞。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓奸腺,卻偏偏與公主長得像,于是被迫代替她去往敵國和親血久。 傳聞我的和親對(duì)象是個(gè)殘疾皇子突照,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容