Spring bean生命週期的源碼分析(超級詳細)

一. 問題背景

遇到面試題“Spring Bean的生命週期是怎麼樣?”,很多網上的博客博文都有總結性的闡述,但是筆者想知道是從哪裏開始切入到Spring Bean的生命週期,從什麼時候開始,從哪句代碼開始研究。因此今天研究一下Spring源碼分析Spring Bean的生命週期。

此筆記僅供自己參考,如有錯誤請指正

參考自:

  1. 請別再問Spring Bean的生命週期了!
  2. 【spring源碼全集】B站唯一阿里P8級別的架構師教程

筆者建議:若是第一次接觸Spring Bean的生命週期,可以先參考第一篇文章請別再問Spring Bean的生命週期了!。然後想要繼續深入研究可以參考【spring源碼全集】B站唯一阿里P8級別的架構師教程

二. 儲備知識

在此之前我們需要對spring bean的生命週期有一個大概瞭解,後面源碼分析時才進行細緻分類,現在粗略地認知它有4個階段:(大概瞭解有初步認識即可,無需背下來。這是方便後面理解源碼作準備

  1. 實例化(Instantiation)
  2. 屬性賦值(Populate)
  3. 初始化(Initialization)
  4. 銷燬(Destruction)

三. 問題引入

從循環依賴情景引入Spring Bean的生命週期。首先搭建循環依賴的場景。搭建好後再作分析以及小總結。

3.1 循環依賴

情景:2個類分別爲IndexService和UserService。IndexService類中有屬性UserService;UserService類中有屬性IndexService。 這種彼此之間都有彼此的屬性屬於循環依賴情況

搭建循環依賴場景,工程目錄結構如下:
在這裏插入圖片描述
工程pom文件依賴(主要是spring的一些核心依賴:core、aop、context、txt)如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.luban</groupId>
    <artifactId>spring-luban</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>4.0.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.0.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>4.0.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>4.0.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.0.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>4.0.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>4.3.5.RELEASE</version>
        </dependency>


    </dependencies>
</project>

代碼如下:
UserService.java

package com.luban.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class UserService {

    @Autowired
    IndexService indexService;

    public UserService(){
        System.out.println("Constructor from userService");
    }
}

IndexService.java

package com.luban.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class IndexService {


    @Autowired
    UserService userService;

    public IndexService(){
        System.out.println("Constructor from indexService");
    }

    public void getService(){ //爲了測試是否裝配了userService
        System.out.println(userService);
    }
}

Appconfig.java

package com.luban.app;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.luban")
public class Appconfig {

}

Test.java

package com.luban.test;

import com.luban.app.Appconfig;
import com.luban.service.IndexService;
import com.luban.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Test {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac =
                new AnnotationConfigApplicationContext(Appconfig.class);
        ac.getBean(IndexService.class).getService();
    }

}

結果如下:
在這裏插入圖片描述

3.2 對循環依賴的分析

上面代碼中,IndexService中有UserService屬性,UserService中有IndexService屬性,2者互相依賴,但是Spring卻沒有報錯,能正常將屬性裝配好並實例化對象。即spring循環依賴,也就是屬性注入的問題,即依賴注入(DI,Denpendency Injection)。

因此研究Spring Bean生命週期的切入點分析:spring是怎麼解決循環依賴的?->即需要知道spring怎麼實現依賴注入的?->即首先要知道依賴注入是什麼時候發生的->就要知道spring bean的生命週期->就要知道spring是怎麼實例化一個對象的

3.3 兩個小結論

爲了能更加容易理解,在此首先給出結論,讓大家有一個總體宏觀的架構印象。後面會進行驗證

結論1:依賴注入是在spring bean生命週期的其中一個步驟完成的。而spring bean生命週期是在IOC容器初始化的時候完成的。比如spring bean生命週期有1,2,3,…,N,N個步驟。依賴注入在其中的一個步驟(比如在第3步,只是打個比方)。

結論2:初始化的順序是,IOC容器首先會被spring內部初始化,IOC容器初始化的過程中,會有spring bean的初始化(即spring bean的生命週期),依賴注入就在spring bean生命週期的一個步驟。

3.4 驗證兩個小結論

代碼如下:
UserService.java

package com.luban.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class UserService {

    @Autowired
    IndexService indexService;

    public UserService(){
        System.out.println("Constructor from userService");
    }
}

IndexService.java

package com.luban.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class IndexService {


    @Autowired
    UserService userService;

    public IndexService(){
        System.out.println("Constructor from indexService");
    }

    public void getService(){ //爲了測試是否裝配了userService
        System.out.println(userService);
    }
}

Appconfig.java

package com.luban.app;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.luban")
public class Appconfig {

}

Test.java

package com.luban.test;

import com.luban.app.Appconfig;
import com.luban.service.IndexService;
import com.luban.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Test {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac =
                new AnnotationConfigApplicationContext(Appconfig.class);
       
    }

}

注意:這次代碼在main方法裏面並沒有使用IOC容器(即代碼中的ac變量)獲取Bean

但是測試結果仍然打印了構造器裏面的信息,測試結果如下:
在這裏插入圖片描述

四. Spring如何創建一個Bean

上面講到了要理解Spring的循環依賴,即要理解依賴注入(DI),即要了解Spring bean生命週期,即要知道Spring bean的產生過程(bean是由什麼產生的)

4.1 預備知識

爲了後面更加容易理解Spring bean產生的過程,在這裏給出2個非官方的概念:

  1. Java對象(簡稱它對象):當在java中使用new關鍵字,就能把一個對象new出來,隨後就可以使用這個對象了。
  2. bean:Bean,即Spring bean。bean不僅需要經歷new的過程,還需要經歷一系列過程,才能成爲一個Spring bean,纔可以正常地使用它。

注意:再次強調這2個概念只是爲了更加容易理解後面的分析,它們並不是官方給出的概念。

總結:Java對象不是一個bean,而bean是一個Java對象。只有經歷過一系列的過程,才能成爲bean。

4.2 Java對象和Spring對象的產生過程的區別:

區別在於多了一個BeanDefinition對象,如下:
在這裏插入圖片描述

如上圖可見,最大的區別就是bean的產生過程有一個BeanDefinition對象。這個BeanDefiniton是非常重要的!!!它在spring源碼中起到了很大的作用!!!

4.3 BeanDefinition對象是什麼

上面闡述了bean的產生過程最重要的一環是BeanDefinition對象。現在瞭解這個BeanDefinition對象到底是什麼。

源碼中對BeanDefinition的定義如下:

/**
 * A BeanDefinition describes a bean instance, which has property values,
 * constructor argument values, and further information supplied by
 * concrete implementations.
 * 大概意思:一個BeanDefinition描述一個bean實例,記錄了該bean實例的屬性值、構造器參數值、以及
 * 該實例具體實現的更多信息
 *
 * <p>This is just a minimal interface: The main intention is to allow a
 * {@link BeanFactoryPostProcessor} such as {@link PropertyPlaceholderConfigurer}
 * to introspect and modify property values and other bean metadata.
 * 大概意思:這只是一個小型接口:該接口的目的是允許一個BeanFactoryPostProcessor
 * (比如PropertyPlaceholderConfigurer)去反省(筆者認爲這裏反省的意思是回調的意思)以及修改屬性值
 * 和其他的bean元數據(配合前面出現的Configurer,元數據大概是指註解,比如@Lazy、@DependsOn)。
 * 
 * @author Juergen Hoeller
 * @author Rob Harrop
 * @since 19.03.2004
 * @see ConfigurableListableBeanFactory#getBeanDefinition
 * @see org.springframework.beans.factory.support.RootBeanDefinition
 * @see org.springframework.beans.factory.support.ChildBeanDefinition
 */
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement

BeanDefinition是一個接口,說明它可以有很多實現類。它繼承了AttributeAccessor, BeanMetadataElement。再來看看它的父接口。

源碼中對AttributeAccessor的定義如下:

/**
 * Interface defining a generic contract for attaching and accessing metadata
 * to/from arbitrary objects.
 * 大概意思是:該接口定義一個總的協議去設置以及訪問元數據(metadata,即那些標註在類、方法、屬性上的註解)
 * 
 * @author Rob Harrop
 * @since 2.0
 */
public interface AttributeAccessor

AttributeAccessor裏面也有很多getAttribute()方法、setAttribute()方法,可見它確實是設置、訪問元數據的。如下:
在這裏插入圖片描述

源碼中對BeanMetadataElement的定義如下:

/**
 * Interface to be implemented by bean metadata elements
 * that carry a configuration source object.
 * 大概意思是:該接口是被那些bean的元數據們實現的,功能是攜帶一個配置源的對象。
 * 也就是獲取標註在類、方法、屬性上的註解。
 *
 * @author Juergen Hoeller
 * @since 2.0
 */
public interface BeanMetadataElement

該接口裏面只有一個getSource()方法,如下:

/**
	 * Return the configuration source {@code Object} for this metadata element
	 * (may be {@code null}).
	 * 大概意思是:返回該元數據的配置源對象(即用一個配置源對象描述(或者可以理解爲代替)該元數據)
	 */
	Object getSource();

4.3.1 BeanDefinition對象存了什麼

前面說了BeanDefinition是描述一個bean實例的接口(雖然這裏用了“bean實例”,但實際上它仍未被實例化,只是描述了它被實例化時要用到的屬性,後面有一個驗證階段根據這些屬性決定是否實例化該bean),描述了該bean實例的屬性值、構造器參數值、以及該實例具體實現的更多信息。那麼再來結合方法看看它具體存了什麼信息?,如下:

  1. 記錄該bean是否懶加載(即如果是懶加載,那麼容器初始化時將不會初始化這個bean。只有當bean是單例(singleton)時,纔會在容器初始化時就進行初始化。
/**
	 * Return whether this bean should be lazily initialized, i.e. not
	 * eagerly instantiated on startup. Only applicable to a singleton bean.
	 */
	boolean isLazyInit();
  1. 記錄該bean是否自動裝配
/**
	 * Return whether this bean is a candidate for getting autowired into some other bean.
	 */
	boolean isAutowireCandidate();
  1. 記錄該bean是否自動裝配的主要候選者
/**
	 * Return whether this bean is a primary autowire candidate.
	 * If this value is true for exactly one bean among multiple
	 * matching candidates, it will serve as a tie-breaker.
	 */
	boolean isPrimary();
  1. 記錄該bean是否單例(singleton)。容器初始化就必將該單例bean初始化,而且僅初始化一次。後面從容器獲取該bean都是同一個bean
/**
	 * Return whether this a <b>Singleton</b>, with a single, shared instance
	 * returned on all calls.
	 * @see #SCOPE_SINGLETON
	 */
	boolean isSingleton();
  1. 記錄該bean是否原型(prototype)。該bean不在容器初始化時進行初始化。每次獲取該bean都是重新從容器中獲取,都不是同一個bean。
/**
	 * Return whether this a <b>Prototype</b>, with an independent instance
	 * returned for each call.
	 * @see #SCOPE_PROTOTYPE
	 */
	boolean isPrototype();
  1. 記錄該bean是否抽象的。如果是抽象的,則意味着沒必要實例化
/**
	 * Return whether this bean is "abstract", that is, not meant to be instantiated.
	 */
	boolean isAbstract();

除了記錄上面這些信息,BeanDefinition還記錄了很多信息,比如beanName、parentName、beanClassName、factoryBeanName、factoryMethodName、scope、dependsOn、description等等。如下:
在這裏插入圖片描述
在這裏插入圖片描述

總結:每一個class對象都對應着一個BeanDefinition。BeanDefinition存儲了一些該class的信息,這些信息是標註在該class中的一些註解,比如@Lazy、@DependsOn、@Autowired、@Scope等等

4.4 Spring bean的實例化過程(是實例化的過程,不是生命週期的過程)

是實例化的過程(即new),不是生命週期的過程,因爲沒有涉及到屬性注入等等階段。

先給出大概流程使我們更容易理解源碼,後面從源碼分析驗證。(圖片模糊可右擊圖片選擇”在新標籤頁打開“)

4.4.1 總體概覽圖

在這裏插入圖片描述

4.4.2 細節過程圖

在這裏插入圖片描述

4.5 實現簡單的Spring擴展

再次強調上面的過程是實例化的過程,而非生命週期,因爲沒有涉及到屬性注入等各個階段。

從上面的過程中知道BeanDefition會被放入map中,只要我們創建一個類並實現BeanPostProcessor接口即可做spring的擴展,現在來試試效果。

需求:修改BeanDefinition的beanClass屬性的內容,看看在main方法獲取該bean會有什麼效果

代碼如下:
IndexService.java

package com.luban.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class IndexService {


    @Autowired
    UserService userService;

    public IndexService(){
        System.out.println("Constructor from indexService");
    }

    public void getService(){ //爲了測試是否裝配了userService
        System.out.println(userService);
    }
}

UserService.java

package com.luban.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;


public class UserService {


    IndexService indexService;

    public UserService(){
        System.out.println("Constructor from userService");
    }
}

AppConfig.java

package com.luban.app;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.luban")
public class Appconfig {

}

ZiluBeanFactoryPostProcessor.java

package com.luban.mapper;

import com.luban.service.UserService;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.stereotype.Component;

@Component
public class ZiluBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        //獲取indexService的BeanDfinition
        GenericBeanDefinition indexService =
                (GenericBeanDefinition) beanFactory.getBeanDefinition("indexService");
        //修改它的beanClass,看看在main方法中獲取indexService有什麼效果
        indexService.setBeanClass(UserService.class);
    }
}

Main.java

package com.luban.test;

import com.luban.app.Appconfig;
import com.luban.service.IndexService;
import com.luban.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Test {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac =
                new AnnotationConfigApplicationContext(Appconfig.class);
        System.out.println(ac.getBean(IndexService.class));//根據類型獲取bean

    }

}

測試結果:
在這裏插入圖片描述
可以看到,經過BeanPostProcessor處理,我們修改了indexService的BeanDefinition裏面的beanClass屬性,在main方法中就獲取不到indexService這個bean了。而且打印出來的是UserService的信息,而不是IndexService的信息(即調用了UserService的構造器,沒有調用IndexService的構造器)。

得出結論:jvm加載進去的類,與spring產生出來的bean是沒有關係的,而是與BeanDefinition有關,我們修改了BeanDefiniton的信息,得到的spring bean也會發生改變


在main方法中我們使用根據beanName獲取bean的方法試試有什麼效果:

package com.luban.test;

import com.luban.app.Appconfig;
import com.luban.service.IndexService;
import com.luban.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Test {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac =
                new AnnotationConfigApplicationContext(Appconfig.class);
        System.out.println(ac.getBean("indexService"));

    }

}

測試結果:
在這裏插入圖片描述
這個例子驗證了spring是從單例池(即map)中拿bean的,因爲即使我修改了BeanDefinition,但是BeanDefinition對應的key沒有改變,我在main方法中仍可以根據原本的key獲取到bean。

得出結論:spring是從單例池(map)中拿bean的。即使我們修改了BeanDefinition,其對應的key沒有改變,我們仍可以根據原本的key獲取bean

4.6 初步debug分析spring實例化bean的步驟在哪裏

從上面spring實例化對象的細節過程圖中初步瞭解了大概過程。現在開始debug一下代碼,看看是不是真的這樣。debug的過程中還會驗證某些地方是不是與前面給出的結論一致。如果忘了,需要時不時看一下前面給出的bean實例化細節過程圖。

首先貼出debug的代碼:
IndexService.java

package com.luban.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component
public class IndexService {


    @Autowired
    UserService userService;

    public IndexService(){
        System.out.println("Constructor from indexService");
    }

    public void getService(){ //爲了測試是否裝配了userService
        System.out.println(userService);
    }

    @PostConstruct
    public void aa(){
        System.out.println("init.");
    }
}

UserService.java

package com.luban.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class UserService {

    public UserService(){
        System.out.println("Constructor from userService");
    }
}

Appconfig.java

package com.luban.app;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.luban")
public class Appconfig {

}

Test.java

package com.luban.test;

import com.luban.app.Appconfig;
import com.luban.service.IndexService;
import com.luban.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Test {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac =
                new AnnotationConfigApplicationContext(Appconfig.class);

    }

}

分析:前面已經事先給出了spring bean實例化是發生在spring bean生命週期中,而spring bean生命週期是發生在spring容器初始化中,因此測試方法main()只給出了初始化容器的方法。因此,如果當console打印了IndexService以及UserService的構造方法裏面信息(即“Constructor from indexServiceConstructor from userService”),則代表IndexService以及UserService已經被實例化了。因此,我們debug通過判斷何時打印了構造器信息來定位出實例化的步驟到底在哪裏。

現在開始debug,首先在下面打斷點:
在這裏插入圖片描述
點擊綠色按鈕,選擇Debug ‘Test.main()’,如下:
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
控制檯console沒有打印信息,所以繼續執行:
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

上面發現控制檯打印信息了,定位到實例化階段發生在構造方法的refresh()中

接下來重新啓動debug模式,把它執行到上圖的狀態,然後點擊進入方法裏面執行,如下:
在這裏插入圖片描述
在這裏插入圖片描述
定位到實例化階段在finishBeanFactoryInitialization(beanFactory)裏面。

重新啓動debug模式,然後調試到上圖的狀態,然後點擊如下:
在這裏插入圖片描述
在這裏插入圖片描述
定位到實例化階段發生在preInstantiateSingletons()中。

重新啓動debug模式,調試到上圖的狀態,然後點擊如下:
在這裏插入圖片描述
點擊後,如下圖:
在這裏插入圖片描述
點進入看看getMergedLocalBeanDefinition()這個方法,如下:
在這裏插入圖片描述
如下圖:從map中拿到BeanDefinition後,開始驗證信息,符合驗證判斷拿到的這個BeanDefinition工廠bean實例化還是普通bean實例化。我們知道只要我們創建一個類並實現BeanFactory接口並實現對應方法,在裏面寫自己的邏輯即可參與bean的初始化過程。所以它就是在preInstantiateSingletons()方法裏面執行的。
在這裏插入圖片描述
繼續定位實例化階段發生在哪裏,重啓debug模式,調試到如下狀態:
在這裏插入圖片描述
定位到實例化階段是在getBean()裏面完成的。

重啓debug模式,調試到上圖的狀態,點擊如下:
在這裏插入圖片描述
在這裏插入圖片描述

定位到實例化階段是在doGetBean()中完成的。

在這裏插入圖片描述
getSingleton()方法裏面如下:
在這裏插入圖片描述
getSingleton(beanName, true)方法如下:
在這裏插入圖片描述
點擊singletonObjects看到它是一個map
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
由於當前只是從map中拿出BeanDefinition而已,仍未創建bean(實例化都還沒開始,肯定不是正在創建bean),所以singletonsCurrentlyInCreation裏面是沒有正在創建的bean的beanName的,所以singletonsCurrentlyInCreation.contains(beanName)返回false。

繼續分析這個getSingleton()方法:
在這裏插入圖片描述
在這裏插入圖片描述
bean都還沒有創建,爲什麼就已經從單例池map中獲取bean呢?作者有什麼用意?

解釋:首先明白這個getSingleton()方法是在doGetBean()方法裏面的。spring的作者爲什麼這麼做?其中緣由很複雜。與這部分知識有關的便是爲了解決循環依賴。無論是創建Bean還是getBean()都會調用這個doGetBean()方法,而getSingleton()方法在doGetBean()裏面。即無論前者還是後者這2種情況都會調用getSingleton()。由於循環引用需要在創建Bean的時候去獲取被引用的那個類,而被引用的那個類如果沒有被創建成Bean,則會調用createBean來創建這個bean,在創建這個被引用的bean的過程中會判斷這個bean的對象有沒有被實例化。

上圖中的sharedInstance什麼時候不爲null?
答:當容器初始化完成,程序員直接使用getBean()的時候不爲null

在這裏插入圖片描述
在這裏插入圖片描述
isPrototypeCurrentlyInCreation()方法後面的幾個if都是驗證類是否有dependsOn之類的,都沒有。直到遇到isSingleton()方法。進入它的if。如下:
在這裏插入圖片描述
在這裏插入圖片描述

在這裏插入圖片描述
在這裏插入圖片描述
從上圖看到,定位到實例化就是在createBean()中完成的,這裏不再作定位驗證了,繼續往下分析createBean()裏面有什麼。

在這裏插入圖片描述

在這裏插入圖片描述
在createBean()裏面,doCreateBean()的前面,resolveBeforeInstantiation()是第一次調用了後置處理器(spring初始化過程中一共調用了9次後置處理器,而且每次的內容以及位置都不同)

在這裏插入圖片描述
執行完doCreateBean,控制檯有信息打印出來了。定位到實例化階段是在createBean()中的doCreateBean()中完成的。 我們繼續看看doCreateBean()裏面有什麼。

如下:
在這裏插入圖片描述
定位到實例化階段是在createBeanInstance()裏面,進入看看具體實現:

在這裏插入圖片描述
在createBeanInstance()裏面第二次調用後置處理器。第一次是在createBean裏面(即doCreateBean()前面)。

在這裏插入圖片描述
在這裏插入圖片描述

在這裏插入圖片描述
如上圖,第三次調用後置處理器發生在doCreateBean()裏面(生命週期中一共有9次調用後置處理器。第一次在createBean()裏面,叫做resolveBeforeInstantiation(),發生在doCreateBean()前面;第二次是在createBeanInstance()裏面。)

繼續往下看:
在這裏插入圖片描述
上圖的allowCircularReferences就是spring默認支持循環依賴的原因。它的默認值是true。 如果想關閉循環依賴,可以通過在容器的構造器中設置它爲false(需對源碼修改,一般只能從github上fork spring源碼纔可以修改源碼),或在main中使用提供的api設置它爲false。

在這裏插入圖片描述
第四次調用後置處理器也是在doCreateBean()中。

在這裏插入圖片描述

最後作出總結:
要研究spring的循環依賴->就要研究spring bean的生命週期(因爲循環依賴是發生在生命週期中的)->就要研究spring如何實例化一個bean(因爲研究生命週期,就要實例化bean)

4.7 總結spring bean生命週期過程大概過程

從以上所有的debug分析,總結如下:
在這裏插入圖片描述

解釋:

AnnotationConfigApplicationContext的構造方法裏面的

==refresh()==方法,裏面的

==finshBeanFactoryInitialization()==方法,裏面的

==preInstantiateSingletons()==方法,裏面的

==getBean()==方法,裏面的

==doGetBean()==方法,裏面的

==第二次的getSingleton()==方法,裏面的

getObject()【lambda表達式實現的方法】方法,裏面的

==createBean()==方法,裏面的

doCreateBean()【裏面有完成實例化createBeanInstance()、屬性注入populateBean()、初始化initializeBean()這個3個階段。屬性注入populateBean()之前還有判斷是否需要循環依賴allowCircularReferences】方法,裏面的

createBeanInstance()【就是在這裏面完成實例化】

4.8 屬性注入

4.8.1 回顧

在本博文最前面一開始就拋出問題——循環依賴。spring是默認支持循環依賴,那麼那的原理機制是怎麼樣的?

要研究循環依賴->就得研究spring bean的生命週期(因爲循環依賴是發生在spring生命週期裏面的)->就得研究spring如何實例化一個對象(因爲研究生命週期就肯定要實例化對象)

前面幾個小節對實例化的過程做了debug分析,4.7小節對整個生命週期流程做了總結。爲了後面更加容易、更加方便理解spring bean的屬性注入階段,筆者建議把4.7小節中的圖背下來,因爲後面debug代碼的跨度非常大必須對整個生命週期哪個階段發生在哪裏有一個大致瞭解才能更加容易理解後面的屬性注入分析。

4.8.2 搭建代碼

後面將對屬性注入階段作出debug分析,先了解屬性注入,再去了解循環依賴是怎麼解決的,這樣比較清晰

工程目錄:
在這裏插入圖片描述

爲了能讓後面的出現的情況和大家一致,這裏給出debug的代碼:

IndexService.java

package com.luban.service;

        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.stereotype.Component;

        import javax.annotation.PostConstruct;

@Component
public class IndexService {


    @Autowired
    UserService userService;

    public IndexService(){
        System.out.println("Constructor from indexService");
    }

    public void getService(){ //爲了測試是否裝配了userService
        System.out.println(userService);
    }

    @PostConstruct
    public void aa(){
        System.out.println("init.");
    }
}

UserService.java

package com.luban.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class UserService {

    @Autowired
    IndexService indexService;

    public UserService(){
        System.out.println("Constructor from userService");
    }
}

AppConfig.java

package com.luban.app;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.luban")
public class Appconfig {

}

Test.java

package com.luban.test;

import com.luban.app.Appconfig;
import com.luban.service.IndexService;
import com.luban.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Test {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac =
                new AnnotationConfigApplicationContext(Appconfig.class);
    }

}

4.8.3 debug分析屬性注入

這裏是對屬性注入階段作出debug分析,先了解屬性注入,再去了解循環依賴是怎麼解決的,這樣比較清晰

首先給出結論bean的屬性注入是發生在populateBean()中的。在這裏打一個斷點,進入該方法裏面看看:

在這裏插入圖片描述

接下來拿到的後置處理器是CommonAnnotationBeanPostProcessor,再然後拿到的是AutowiredAnnotationBeanPostProcessor。
在這裏插入圖片描述
在這裏插入圖片描述

面試官經常會問到“@Resource和@Autowired的區別是什麼?
這裏就可以回答:區別是他們的類不一樣。@Resource的類是CommonAnnotationBeanPostProcessor,而@Autowired的類是AutowiredAnnotationBeanPostProcessor。

因爲屬性注入在postProcessPropertyValues(),debug點進去看看:
在這裏插入圖片描述
定位到postProcessPropertyValues()中的inject()完成屬性注入

在這裏插入圖片描述
定位到inject()裏面還有一個重載的inject()方法,屬性注入在裏面完成。

由於參考的b站的spring源碼視頻沒有更加深入第二個inject(),筆者這裏也直接給出第二個inject()方法到底是實現了什麼,如下圖: 從圖中瞭解個大概,後面就能更容易理解循環依賴的解決方案。

在這裏插入圖片描述

解釋:當前beanName是indexService,完成了實例化,成爲了一個indexService對象。實例化後就是進行屬性注入,進入populateBean()。獲取所有的後置處理器,遍歷每個後置處理器,符合條件(當拿到的後置處理器是InstantiationAwareBeanPostProcessor的實例對象時,會進入if判斷,因爲indexService是用@Autowired註解自動裝配的,所以拿到後置處理器爲AutowiredAnnotationBeanPostProcessor),就會執行postProcessPropertyValues()->inject()->重載的inject()進行屬性注入。因爲屬性注入就是要獲取到userService這個bean,然後注入進indexService的userService屬性。所以進重載的inject()會調用getBean(“userService”)去getBean(),進而現在就變成執行userService的生命週期。getBean(“userService”)->doGetBean()->第一次getSingleton()->第二次getSingleton()->getObject()->createBean->doCreateBean()->createBeanInstance()->populateBean,現在就又變成了userService需要注入屬性indexService,所以又像上面那樣執行getBean(“indexService”)->doGetBean()->…一直循環,所以第二inject()後面發生的總體調用就是這樣。那麼這個循環什麼時候結束呢?

4.9 Spring如何解決循環依賴的?

4.9.1 回顧

4.8.3的圖中粗略地寫出了屬性注入會從inject()->調用到getSingleton()方法。

解決循環依賴最重要的方法就是getSingleton()方法。 同樣以debug分析源碼

4.9.2 代碼

代碼同樣是使用上面4.8節的代碼:
IndexService.java

package com.luban.service;

        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.stereotype.Component;

        import javax.annotation.PostConstruct;

@Component
public class IndexService {


    @Autowired
    UserService userService;

    public IndexService(){
        System.out.println("Constructor from indexService");
    }

    public void getService(){ //爲了測試是否裝配了userService
        System.out.println(userService);
    }

    @PostConstruct
    public void aa(){
        System.out.println("init.");
    }
}

UserService.java

package com.luban.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class UserService {

    @Autowired
    IndexService indexService;

    public UserService(){
        System.out.println("Constructor from userService");
    }
}

AppConfig.java

package com.luban.app;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.luban")
public class Appconfig {

}

Test.java

package com.luban.test;

import com.luban.app.Appconfig;
import com.luban.service.IndexService;
import com.luban.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Test {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac =
                new AnnotationConfigApplicationContext(Appconfig.class);
    }

}

4.9.3 debug分析Spring如何解決循環依賴

在如下打斷點並step into:
在這裏插入圖片描述

進入第一次的getSingleton()方法,從單例池拿bean,拿不到。Set集合singelCurrentlyCreation裏面也沒有indexService(因爲當前indexService連實例化都沒有進行,所以不屬於正在創建的單例bean),所以爲空,所以最終返回的singletonObject爲null。
在這裏插入圖片描述

在這裏插入圖片描述
接下來在第二個getSingleton()中會遇到解決循環依賴的關鍵:beforeSingletonCreation()
在這裏插入圖片描述

原來doGetBean()中第一次getSingleton()裏面的Set集合singletonsCurrentlyInCreation 是在 doGetBean()中第二次的getSingleton()中的beforeSingletonCreation()中添加“indexService”的。
在這裏插入圖片描述

在這裏插入圖片描述
顯然上上圖中因爲if條件判斷不成立,所以不會進入if語句,但是if條件判斷的時候已經將“indexService”添加進入Set集合singletonsCurrentlyInCreation。

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

在doCreateBean()中,populateBean之前,又有一個循壞依賴解決的關鍵!!!,如下:

在這裏插入圖片描述
在這裏插入圖片描述
addSingletonFactory()方法中,出現了2個map。Spring中用3個map來緩存單例。第一個map,是getSingleton()中出現的singletonObjects。第二個map是addSingletonFactory()中的singletonFactories。第三個map也是addSingletonFactory()中出現的earlySingletonObjects。其實這三個map均同時出現在doGetBean()中的第一次getSingleton()方法裏面 以及 doCreatebean()中的addSingleFactory()裏面。這三個map是解決循環依賴的關鍵
在這裏插入圖片描述
執行完畢後,開始執行populateBean,進入循環依賴屬性注入了。
在這裏插入圖片描述

來到postProcessPropertyvalues():
在這裏插入圖片描述

來到第一個inject():

在這裏插入圖片描述

來到第二個inject():

在這裏插入圖片描述

執行到第二次inject(),然後再去doGetBean()中第一次getSingleton()打斷點,添加調試條件爲beanName.equals("userService")依據上面給出的屬性注入流程,當執行到第二次inject()後,就會執行getBean(), 所以接下來我們點擊執行到下一個斷點就會跳到如下界面
在這裏插入圖片描述

在這裏插入圖片描述

在這裏插入圖片描述

接下來的方法調用鏈完全是在進行userService的生命週期,直到執行到doGetBean()的第二次getSingleton()中的beforeSingletonCreation():也是將userService添加進Set集合
在這裏插入圖片描述

後面的過程就是執行userService的生命週期,直到執行到createBeanInstance()實例化userService對象,接着判斷是否支持循環依賴,進入addSingletonFactory(),如下:
在這裏插入圖片描述

在這裏插入圖片描述

在這裏插入圖片描述

執行到第二次inject(),然後再去doGetBean()中第一次getSingleton()打斷點,添加調試條件爲beanName.equals("indexService")依據上面給出的屬性注入流程,當執行到第二次inject()後,就會執行getBean(), 所以接下來我們點擊執行到下一個斷點就會跳到如下界面
在這裏插入圖片描述

最最最關鍵解決循環依賴在下圖 :getSingleton()
在這裏插入圖片描述

後面的調用也是像上面這樣,最終在getSingleton()中得到單例bean,成功完成循環依賴的屬性注入!!!

4.9.4 總結

在這裏插入圖片描述

4.10 解決循環依賴的3個map

這三個map分別是singletonObjects;singletonFactories;earlySingletonObjects。前面在研究spring如何解決循環依賴的過程中,核心方法getSingleton()中出現了這3個map。

4.10.1 3個map的作用

首先給出結論,後面再探討爲什麼這些map對應的功能要這麼做?

三個map的作用如下:

  1. singletonObjects:第一個map,作用是存放已經完成整個生命週期的bean。專業術語叫作單例池
  2. singletonFactories:第二個map,作用是存放beanFactory,通過這個factory,可以拿到想要的bean。
  3. earlySingletonObjects:第三個map,作用是存放未走完完整生命週期的bean。常稱它爲第三級緩存。

注:其實這3個map都可以叫作緩存,源碼的註解也是用到了cache這個單詞。

4.10.2 爲什麼要有三級緩存?(即第三個map存在的原因是什麼)

這3個map出現在doGetBean()的第一次getSingleton()方法中,getSingleton()又調用了一次 getSingleton(beanName, allowCircularReference:true);重載方法。這裏第二個入參被spring寫死爲true了。大家必須知道這一點。

首先闡述當前情況(看不懂可以查看上面4.9.4的總結圖),後面再列出源碼:indexService、userService都已經存在Set集合singletonsCurrentlyInCreation中了。此時userService要注入indexService,所以getBean(“indexService”),一直運行到getSingleton。即下面貼出的代碼處。

然後開始看getSingleton()的源碼:
在這裏插入圖片描述
如上圖中的藍色字,爲什麼呢?因爲可能存在衝入創建的情況,有第三個map,可以提高性能。

試想假如有indexService,indexService2,indexService3,indexService4,indexService5,他們都有屬性userService。如果每次注入userService都要從第二個map中拿factory再拿bean,這得耗費多少時間?假如有1000個要注入userService呢?而且,通過factory拿bean的過程(即singletonFactories.getObject())是非常耗時間的,因爲這個過程有一個for循環。這個for循環在哪裏呢?如下圖:
在這裏插入圖片描述

在這裏插入圖片描述
在這裏插入圖片描述

所以如果每次都從第二個map拿factory再拿到bean,會調用for循環,假如一個for循環要10ms,那麼如果有10個對象要注入該屬性,就得用10*10ms=100ms。非常耗費性能。這時如果我從factory拿到bean後,把它存入第三個map中,那麼後面要拿該bean就只需直接從第2個map拿即可,無需經歷for循環。

注:第一個map存的是完整的bean,第二個map存的是factory,第三個map存的是半成品bean

4.10.3 爲什麼第二個map存的是factory(即存工廠,而不是對象)?

這裏涉及到aop。我們先提前給出結論,再進行驗證分析。

答案是:存factory,我們可以對bean做另外一些操作,比如代理,使我們從factory拿到的bean是一個代理bean對象。總之我們使用第二個map存工廠,可以做一些我們自己想做的東西。如果第二個map直接存對象,那麼就只能直接拿到這個bean對象了,無法做出其他處理。

4.10.3.1 引入後面驗證分析需要用到的知識(儲備知識)

這些儲備知識大致瞭解即可,無需背下來。在這裏引入,只是爲了方便後面講解驗證分析。

我們對生命週期的過程已經有所瞭解,過程如下:

  1. 實例化(new一個對象)
  2. 屬性注入(populate)
  3. init初始化(生命週期的回調方法,比如執行@PostConstruct的方法)
  4. 代理(aop)
  5. 放入單例池(put singletonObjects)

4.10.3.2 代碼

爲了闡述爲什麼第二個map放factory,我們需要搭建情景,其中涉及aop。

如果忘記了怎麼寫aop,可以看如下 一些步驟快速搭建aop:

在這裏插入圖片描述

在這裏插入圖片描述
在這裏插入圖片描述

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

工程目錄如下:
在這裏插入圖片描述

IndexService.java

package com.luban.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;


@Component
public class IndexService {

    public IndexService(){
        System.out.println("Constructor from indexService");
    }

    public UserService getUserService(){ //爲了測試是否裝配了userService
        System.out.println("service logic");
        return null;
    }

}

UserService.java

package com.luban.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class UserService {

    @Autowired
    IndexService indexService;

    public UserService(){
        System.out.println("Constructor from userService");
    }

    public IndexService getIndexService() {
        return indexService;
    }
}

Appconfig.java

package com.luban.app;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan("com.luban")
@EnableAspectJAutoProxy
public class Appconfig {

}

NotVeryUsefulAspect.java

package com.luban.app;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class NotVeryUsefulAspect {

    @Pointcut("execution(* com.luban.service.IndexService.*(..))")
    public void anyPublicMethod(){

    }


    @Before("anyPublicMethod();")
    public void before(){
        System.out.println("----------------------aop-------------------");
    }
}

Test.java

package com.luban.test;

import com.luban.app.Appconfig;
import com.luban.service.IndexService;
import com.luban.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Test {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac =
                new AnnotationConfigApplicationContext(Appconfig.class);
        IndexService bean = ac.getBean(IndexService.class);
        System.out.println(bean.getClass());
        bean.getUserService();
    }

}

4.10.3.3 分析以及驗證

上述代碼測試結果如下:
在這裏插入圖片描述

分析:得到的結果是從userService中得到的indexService,已經被代理了。如果按照前面闡述的生命週期過程,userService注入屬性indexService,然後getBean("indexService“)->…->getSingleton()。然後從第二個map(即singletonFactories)拿factory,再通過factory拿到indexService。再結合上圖的測試結果,indexService已經被代理了,不是原本的indexService。所以結論就是,通過factory拿indexService的時候,進行了aop處理。使得從factory拿到的是代理bean。所以這就是爲什麼第二個map存factory而不是對象。我存factory可以對bean做處理再返回結果出去。factory對bean起到了昇華的作用。

如下圖:
在這裏插入圖片描述

4.11 再次分析屬性注入

從上面的一些列分析直到屬性注入發生在populateBean()中。下面再次分析屬性注入的源碼,發現新的知識點。
下面爲源碼分析:

進入populateBean()方法,首先是進行判斷是否繼續進行屬性注入,有如下:
在這裏插入圖片描述
往下執行,來到真正進行屬性注入的地方:
在這裏插入圖片描述

在這裏插入圖片描述

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

在這裏插入圖片描述
在這裏插入圖片描述

在這裏插入圖片描述

最後完成屬性注入。
總結:屬性注入的階段,裏面很多地方都用到了策略設計模式。

4.12 分析InitializeBean()(即分析生命週期的回調方法)

4.12.1 生命週期的回調方法的三種方式

註解方式;實現接口方式;xml方式

官方文檔的介紹如下:

在這裏插入圖片描述

在這裏插入圖片描述

在這裏插入圖片描述

在這裏插入圖片描述
總結:首先執行的是註解方式,其次是實現接口方式,最後是xml方式。

4.12.2 InitializeBean()裏面的實現

生命週期的回調方法都在InitializeBean()裏面。
在這裏插入圖片描述
在這裏插入圖片描述

4.13 spring bean完整生命週期總結

如下圖,有點長,可以右擊“在新建標籤頁打開”:
在這裏插入圖片描述

4.14 面試題:爲什麼需要3級緩存?

總結式回答:

  • singletonObjects:單例池。單例對象只會實例化一次,所以需要一個單例池來緩存,原型(prototype)就不需要這個緩存
  • singletonFactories:緩存的是一個工廠。主要爲了解決循環依賴,利用工廠設計模式、策略設計模式生成一個合格的bean
  • earlySingletonObjects:提高性能,解決重複創建的問題。

然後再根據本博客分析的詳細闡述一下。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章