頭條一面:Spring IOC 容器中只存放單例 Bean 嗎?

最近,很多小夥伴出去面試,感覺自己面的不是很理想,回來後,不少小夥伴把面試題做了記錄發給我,讓我給大家解析下,然後發出來。當我看到這些面試題時,快速在腦海中構建起了整個知識體系,從基礎到框架、從分佈式到微服務,從數據結構到算法,從虛擬化到雲原生,從大數據到雲計算,從實戰項目到性能調優。其實,這些面試本質上不難,很多都是對於基礎知識的考察。

今天開始,我們就來一一突破這些大廠的面試題,好了,開始今天的正文。

問題:

正如題目所說:Spring IOC 容器中只存放單例 Bean 嗎?

先給出結論吧

這裏,想來想去,我還是直接了當的說吧:是的,Spring IOC 容器中只存放單例 Bean。接下來,且聽我細細道來爲哈只存放單例 Bean。

問題分析

既然,我們已經知道 Spring IOC 容器中只存放單例 Bean,但是在面試的時候不能只說這一句話呀,否則,面試官就會把你直接 Pass 掉。爲啥?如果你只說這一句話,面試官可能就會認爲你是懵的,而且懵對的概率爲 50%,如果你懵錯了,面試官認爲你不會,如果你懵對了,面試官有可能也會認爲你不會。所以,除了答對結論之外,還要清晰的說出 Spring IOC 容器中爲啥只存放單例 Bean。

好了,我們正式開始分析這個問題。

IOC 容器初始化的時候,會將所有的 bean 初始化在 singletonObjects 這個 ConcurrentHashMap 中, bean 是單例的。

在獲取 bean 的時候,首先會從 singletonObjects 去取,通過 debug,發現如果 scope 是單例,則可以獲取到 bean,如果 scope 是多例,則獲取不到 bean,需要從一個叫 mergedBeanDefinitions 的 ConcurrentHashMap 中去獲取 bean 的定義,然後再根據 bean 的 scope 去決定如何創建 bean,如果 scope=prototype,則每次都會創建一個新的實例。

這裏,我們可以大概得出這樣的結論:

IOC 在初始化時,只會將 scope= singleton(單例)的對象進行實例化,而不會去實例化 scope=prototype 的對象(多例)。

接下來,我們就來 debug 一下 Spring 的源碼。

首先,我們創建一個用於測試作用域爲多例,獲取不同實例的 Person 類,如下所示。

public class Person {

    @Value("張三")
    private String name;

    @Value("#{20-2}")
    private Integer age;

    @Value("${person.nickName}")
    private String nickName;

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    //省略get/set
}

接下來,創建一個 MainConfig 類,如下所示。

@Configuration
public class MainConfig {
    @Bean("person")
    @Scope("prototype")
    public Person person(){
        System.out.println("給容器中添加Person...");
        return new Person("張三", 25);
    }
}

可以看到,此時 MainConfig 測試的是作用域爲多例,獲取不同實例的場景。而如果要想測試作用域爲單例,獲取相同實例的場景,則只需要將 MainConfig 類中的 person() 方法上的 @Scope("prototype") 註解去掉即可,如下所示。

@Configuration
public class MainConfig {
    @Bean("person")
    public Person person(){
        System.out.println("給容器中添加Person...");
        return new Person("張三", 25);
    }
}

接下來,再編寫一個 main 方法用於啓動測試程序。

public static void  main(String[] args){
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
    Person person = applicationContext.getBean(Person.class);
    Person person2 = applicationContext.getBean(Person.class);
    if(person.equals(person2)){
        System.out.println("同一個實例");
    }else{
        System.out.println("不同的實例");
    }
}

啓動程序,開始 debug 測試單例情況。

調試單例作用域

經過 debug 調試,在單例情況下,首次從 singletonObjects 這個 Map 中獲取的 bean 爲空,以後每次獲取時,從 singletonObjects 這個 Map 中獲取的 bean 就不爲空了,會直接返回從這個 Map 中獲取的值。

第一次從 singletonObjects 中獲取值的情況如下所示。

第二次再從 singletonObjects 這個 Map 中獲取的 bean 就不爲空了。

此時,命令行會打印同一個實例。

說明單例作用域下,每次共用一個 bean 實例,並且這個 bean 實例是被保存到容器中的。

調試多例作用域

如果是多例情況,則外界無論獲取多少個 bean,從 singletonObjects 這個 Map 中都獲取不到對應的 bean 實例,每次都需要新建一個 bean 返回。

通過調試源碼,可以發現,當 bean 是多例時,每次都會從一個叫做 mergedBeanDefinitions 的 HashMap 中獲取一個 RootBeanDefinition 對象,裏面包含了 bean 的一些基礎信息,如下所示。

接下來,再根據 bean 的 scope 屬性來做處理,如果作用域是單例,則直接從容器中獲取,如果作用域是多例,則每次會創建一個實例。

此時,命令行會打印出不同的實例。

說明多例作用域下,每次都會創建一個 bean 實例並返回。

綜上所述:Spring IOC 容器中只存放單例 Bean。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/g6HiN2V6t4aH7t2EXRyn7g