頭條一面:Spring IOC容器中只存放單例Bean嗎?
最近,很多小伙伴出去面試,感覺自己面的不是很理想,回來后,不少小伙伴把面試題做了記錄發(fā)給我,讓我給大家解析下,然后發(fā)出來。當我看到這些面試題時,快速在腦海中構建起了整個知識體系,從基礎到框架、從分布式到微服務,從數據結構到算法,從虛擬化到云原生,從大數據到云計算,從實戰(zhàn)項目到性能調優(yōu)。其實,這些面試本質上不難,很多都是對于基礎知識的考察。
今天開始,我們就來一一突破這些大廠的面試題,好了,開始今天的正文。
問題:
正如題目所說:Spring IOC容器中只存放單例Bean嗎?
先給出結論吧
這里,想來想去,我還是直接了當的說吧:是的,Spring IOC容器中只存放單例Bean。接下來,且聽我細細道來為哈只存放單例Bean。
問題分析
既然,我們已經知道Spring IOC容器中只存放單例Bean,但是在面試的時候不能只說這一句話呀,否則,面試官就會把你直接Pass掉。為啥?如果你只說這一句話,面試官可能就會認為你是懵的,而且懵對的概率為50%,如果你懵錯了,面試官認為你不會,如果你懵對了,面試官有可能也會認為你不會。所以,除了答對結論之外,還要清晰的說出Spring IOC容器中為啥只存放單例Bean。
好了,我們正式開始分析這個問題。
IOC容器初始化的時候,會將所有的bean初始化在singletonObjects這個ConcurrentHashMap中, bean是單例的。
在獲取bean的時候,首先會從singletonObjects去取,通過debug,發(fā)現如果scope是單例,則可以獲取到bean,如果scope是多例,則獲取不到bean,需要從一個叫mergedBeanDefinitions的ConcurrentHashMap中去獲取bean的定義,然后再根據bean的scope去決定如何創(chuàng)建bean,如果scope=prototype,則每次都會創(chuàng)建一個新的實例。
這里,我們可以大概得出這樣的結論:
IOC在初始化時,只會將scope= singleton(單例)的對象進行實例化,而不會去實例化scope=prototype的對象(多例)。
接下來,我們就來debug一下Spring的源碼。
首先,我們創(chuàng)建一個用于測試作用域為多例,獲取不同實例的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
- }
接下來,創(chuàng)建一個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返回。
通過調試源碼,可以發(fā)現,當bean是多例時,每次都會從一個叫做 mergedBeanDefinitions 的HashMap中獲取一個RootBeanDefinition對象,里面包含了bean的一些基礎信息,如下所示。
接下來,再根據bean的scope屬性來做處理,如果作用域是單例,則直接從容器中獲取,如果作用域是多例,則每次會創(chuàng)建一個實例。
此時,命令行會打印出不同的實例。
說明多例作用域下,每次都會創(chuàng)建一個bean實例并返回。
綜上所述:Spring IOC容器中只存放單例Bean。
本文轉載自微信公眾號「冰河技術」,可以通過以下二維碼關注。轉載本文請聯系冰河技術公眾號。