給學(xué)妹看的SpringIOC面試題(上)
前段時間是校招的高峰期啊,很多學(xué)弟,學(xué)妹們出去面試的時候都會被問到一個問題,談?wù)勀銓pring的理解?
很多同學(xué)都是會說一些IOC,AOP等,但是聊到一些細(xì)節(jié)IOC里面的細(xì)節(jié)點,就不知怎么接著和面試官怎么聊了。
所以今天我就跟大家具體詳細(xì)聊聊SpringIOC 那些事!!!
什么是Spring
spring 首先它是一個框架,在我們的開發(fā)工作的環(huán)境中,所有的其他的框架基本都依賴Spring,spring起著一個容器的作用,用來承載我們整體的bean對象。它幫我們整理了整個bean的從創(chuàng)建到銷毀的管理。
IOC控制反轉(zhuǎn)是啥?
類的創(chuàng)建、銷毀都由 Spring 來控制,也就是說控制對象生存周期的不再是引用它的對象,而是 Spring來控制整個過程。對于某個具體的對象而言,以前是它控制其他對象,現(xiàn)在是所有對象都被 Spring 控制。
看到這里其實這都是一些簡單的理解,以及一些官方的說法,為了真正的搞懂什么是SpringIoc,就上面的這些東西是遠(yuǎn)遠(yuǎn)不夠的,所以我給大家畫了一個流程圖,跟著這個流程圖,我們一步一步來解析IOC。
只有解析完了流程,我們才能有一個整體的架構(gòu)的脈絡(luò)思路,后面我們再聊DI(依賴注入)以及怎么處理的緩存依賴。
這里跟大家分享一個知識點,在看一些架構(gòu)的源碼的時候,大家一定要先理清整體架構(gòu)的脈絡(luò),這樣才能方便我們理解整個架構(gòu),否則就是一面茫然,不知道寫的是啥!!!
話不多說了,還是直接來看下這個整體流程圖!!!
從這個圖,我們還是從上到下,從左到右的順序來講解哈。
- public static void main(String[] args) {
- ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
- }
我們以開始啟動spring容器開始,常見配置bean有 XML 配置文件的形式或者注解形式等還有一些其他的方式。
不管哪種方式,spring考慮到擴(kuò)展性問題,會通過BeanDefinitionReader,來加載bean的配置信息,然后生成一個BeanDefinition(bean的定義信息,用來存儲 bean 的所有屬性方法定義)。
BeanDefinitionReader 只是接口約束一些定義信息,常見的實現(xiàn)類 XmlBeanDefinitionReader(xml形式),PropertiesBeanDefinitionReader(Properties配置文件),AbstractBeanDefinitionReader (相關(guān)一些環(huán)境信息)等。
BeanFactoryPostProcesser
說完了BeanDefinition那么接下來就是走到BeanFactoryPostProcessor。
BeanFactoryPostProcessor 接口是 Spring 初始化 BeanFactory 時對外暴露的擴(kuò)展點,其實就是在bean的實例化之前,可以獲取bean的定義信息,以及修改相關(guān)信息。
比如說我們現(xiàn)在常見的注解方式來加載bean信息,里面其實就是也是用的BeanFactoryPostProcessor的子類實現(xiàn)的。
我們常見的 @Service、@Controller、@Repository等注解其實都是組合注解,里面里面都是包含Component注解實現(xiàn)的,如下GIF動圖所示:
ps:太大了加載可能會出問題。
從這個動圖中大家可以發(fā)現(xiàn)BeanFactoryPostProcessor有一堆的實現(xiàn)子類,因此當(dāng)我們有自己的業(yè)務(wù)邏輯實現(xiàn)的時候也只需要實現(xiàn)BeanFactoryPostProcessor就可以了,然后加上@Component注解就可以了。
BeanFactory
BeanFactory,從名字上也很好理解,生產(chǎn) bean 的工廠,它負(fù)責(zé)生產(chǎn)和管理各個 bean 實例。同時也是Spring容器暴露在外獲取bean的入口
BeanFactory的生產(chǎn)過程其實是利用反射機(jī)制實現(xiàn)的。
接下來我們再來看一下BeanFactory的繼承關(guān)系。
這張關(guān)系圖我們只要了解的幾個關(guān)鍵點:
- HierarchicalBeanFactory:提供父容器的訪問功能
- ListableBeanFactory:提供了批量獲取Bean的方法
- AutowireCapableBeanFactory:在BeanFactory基礎(chǔ)上實現(xiàn)對已存在實例的管理
- ConfigurableBeanFactory:單例bean的注冊以及生成實例,統(tǒng)計單例bean等信息
- ConfigurableListableBeanFactory:增加了一些其他功能:類加載器、類型轉(zhuǎn)化、屬性編輯器、BeanPostProcessor、bean定義、處理bean依賴關(guān)系、 bean如何銷毀等等一些還有其他的功能
- DefaultListableBeanFactory:實現(xiàn)BeanFactory所有功能同時也能注冊BeanDefinition
可能有人要問了,ApplicationContext和BeanFactory是不是只是繼承關(guān)系?
- ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
- BeanFactory factory = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
BeanFactory是一個底層的IOC容器,而ApplicationContext是在其基礎(chǔ)上增加了一些它的特性的同時同時增加了一些其他的整合特性比如:更好的整合SpringAOP、國際化消息、以及事務(wù)的發(fā)布、資源訪問等這些新的特性。
所以BeanFactory和ApplicationContext不是同一個東西,是兩個不同的對象,想要獲取BeanFactory可以通過applicationContext.getParentBeanFactory()獲取。
所以當(dāng)通過XML來配置bean的信息的時候我們就可以使用BeanFactory作為容器,因為我們不需要有那么多其他的額外的一些特性。當(dāng)我們通過注解的形式來注冊bean信息的時候,我們就可以使用ApplicationContext來作為容器。當(dāng)然這個只是作為了解,在我們的業(yè)務(wù)代碼中基本是可以不用關(guān)心這一點的。
Bean的生命周期
Spring Bean的生命周期在spring的面試題中這其實是非常常見的一道面試題,其實并不用去背那么多流程,在Spring的源碼中其實已經(jīng)寫好了bean的完整生命流程,上面的BeanFactory中已經(jīng)表明。
- BeanNameAware#setBeanName:在創(chuàng)建此bean的bean工廠中設(shè)置bean的名稱,在普通屬性設(shè)置之后調(diào)用,在InitializinngBean.afterPropertiesSet()方法之前調(diào)用
- BeanClassLoaderAware#setBeanClassLoader:將 bean ClassLoaderr 提供給 bean 實例的回調(diào)
- BeanFactoryAware#setBeanFactory:回調(diào)提供了自己的bean實例工廠,在普通屬性設(shè)置之后,在InitializingBean.afterPropertiesSet()或者自定義初始化方法之前調(diào)用
- org.springframework.context.ResourceLoaderAware#setResourceLoader:在普通bean對象之后調(diào)用,在afterPropertiesSet 或者自定義的init-method 之前調(diào)用,在 ApplicationContextAware 之前調(diào)用。
- org.springframework.context.ApplicationEventPublisherAware#setApplicationEventPublisher:在普通bean屬性之后調(diào)用,在初始化調(diào)用afterPropertiesSet 或者自定義初始化方法之前調(diào)用。在 ApplicationContextAware 之前調(diào)用。
- org.springframework.context.MessageSourceAware#setMessageSource:在普通bean屬性之后調(diào)用,在初始化調(diào)用afterPropertiesSet 或者自定義初始化方法之前調(diào)用,在 ApplicationContextAware 之前調(diào)用。
- org.springframework.context.ApplicationContextAware#setApplicationContext:在普通Bean對象生成之后調(diào)用,在InitializingBean.afterPropertiesSet之前調(diào)用或者用戶自定義初始化方法之前。在ResourceLoaderAware.setResourceLoader,ApplicationEventPublisherAware.setApplicationEventPublisher,MessageSourceAware之后調(diào)用
- org.springframework.web.context.ServletContextAware#setServletContext:運行時設(shè)置ServletContext,在普通bean初始化后調(diào)用
- org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization:將此BeanPostProcessor 應(yīng)用于給定的新bean實例
- InitializingBean#afterPropertiesSet:在設(shè)置所有 bean 屬性后由包含的 BeanFactory調(diào)用
- org.springframework.beans.factory.support.RootBeanDefinition#getInitMethodName:獲取InitMethodName名稱,并且運行初始化方法
- org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization
- DisposableBean#destroy:銷毀
- org.springframework.beans.factory.support.RootBeanDefinition#getDestroyMethodName:返回被銷毀的bean名稱
這其實就是bean的整個生命周期過程,其實這里面注視大家都是可以自己查看的,每一個方法上面都是很詳細(xì)注釋,我也只是根據(jù)注視簡單的翻譯了一下。
整個過程bean的生命周期可以縮短理解為:
但是要完全理解Spring,那肯定就要說Spring里面的一個非常重要的方法 **ApplicationContext.refresh()**這其中的包含了13個子方法:
- public void refresh() throws BeansException, IllegalStateException {
- // 添加一個synchronized 防止出現(xiàn)refresh還沒有完成出現(xiàn)其他的操作(啟動,或者銷毀)
- synchronized (this.startupShutdownMonitor) {
- // 1.準(zhǔn)備工作
- // 記錄下容器的啟動時間、
- // 標(biāo)記“已啟動”狀態(tài),關(guān)閉狀態(tài)為false、
- // 加載當(dāng)前系統(tǒng)屬性到環(huán)境對象中
- // 準(zhǔn)備一系列監(jiān)聽器以及事件集合對象
- prepareRefresh();
- // 2. 創(chuàng)建容器對象:DefaultListableBeanFactory,加載XML配置文件的屬性到當(dāng)前的工廠中(默認(rèn)用命名空間來解析),就是上面說的BeanDefinition(bean的定義信息)這里還沒有初始化,只是配置信息都提取出來了,(包含里面的value值其實都只是占位符)
- ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
- // 3. BeanFactory的準(zhǔn)備工作,設(shè)置BeanFactory的類加載器,添加幾個BeanPostProcessor,手動注冊幾個特殊的bean等
- prepareBeanFactory(beanFactory);
- try {
- // 4.子類的覆蓋方法做額外的處理,就是我們剛開始說的 BeanFactoryPostProcessor ,具體的子類可以在這步的時候添加一些特殊的BeanFactoryPostProcessor完成對beanFactory修改或者擴(kuò)展。
- // 到這里的時候,所有的Bean都加載、注冊完成了,但是都還沒有初始化
- postProcessBeanFactory(beanFactory);
- // 5.調(diào)用 BeanFactoryPostProcessor 各個實現(xiàn)類的 postProcessBeanFactory(factory) 方法
- invokeBeanFactoryPostProcessors(beanFactory);
- // 6.注冊 BeanPostProcessor 處理器 這里只是注冊功能,真正的調(diào)用的是getBean方法
- registerBeanPostProcessors(beanFactory);
- // 7.初始化當(dāng)前 ApplicationContext 的 MessageSource,即國際化處理
- initMessageSource();
- // 8.初始化當(dāng)前 ApplicationContext 的事件廣播器,
- initApplicationEventMulticaster();
- // 9.從方法名就可以知道,典型的模板方法(鉤子方法),感興趣的同學(xué)還可以再去復(fù)習(xí)一下之前寫的設(shè)計模式中的-模版方法模式
- // 具體的子類可以在這里初始化一些特殊的Bean(在初始化 singleton beans 之前)
- onRefresh();
- // 10.注冊事件監(jiān)聽器,監(jiān)聽器需要實現(xiàn) ApplicationListener 接口。這也不是我們的重點,過
- registerListeners();
- // 11.初始化所有的 singleton beans(lazy-init 的除外),重點關(guān)注
- finishBeanFactoryInitialization(beanFactory);
- // 12.廣播事件,ApplicationContext 初始化完成
- finishRefresh();
- }
- catch (BeansException ex) {
- if (logger.isWarnEnabled()) {
- logger.warn("Exception encountered during context initialization - " +
- "cancelling refresh attempt: " + ex);
- }
- // 13.銷毀已經(jīng)初始化的 singleton 的 Beans,以免有些 bean 會一直占用資源
- destroyBeans();
- cancelRefresh(ex);
- // 把異常往外拋
- throw ex;
- }
- finally {
- // Reset common introspection caches in Spring's core, since we
- // might not ever need metadata for singleton beans anymore...
- resetCommonCaches();
- }
- }
- }
這里只是大致的說明一下這里的每個方法的用途,如果還想要了解的更深,就需要大家自己再去看這里面的更深成次的代碼了,這個大家可以自己嘗試的斷點試一下?;蛘吆竺嬖賳为毥o大家寫一篇這里面的細(xì)節(jié)流程。
斷點看源碼不必要每個方法都去看,先了解一個大概,然后再多斷點幾次,每次斷點都相對上一次進(jìn)入的更深成次一點,滿滿的你就能全部理解了。這是一個漫長的過程。
總結(jié)
Spring IOC整個啟動過程我們就先講到這里,由于篇幅問題一下子寫的太長怕看起來有點難受,后面再接著跟大家分享怎么處理循環(huán)依賴問題,以及DI依賴注入等源碼分析
看到這里給大家整理了幾個比較常見的面試來加深一下鞏固:
BeanFactory和ApplicationContext的區(qū)別?
BeanFactory是一個底層的IOC容器,而ApplicationContext是在其基礎(chǔ)上增加了一些它的特性的同時同時增加了一些其他的整合特性比如:更好的整合SpringAOP、國際化消息、以及事務(wù)的發(fā)布、資源訪問等這些新的特性
BeanFactory 與 FactoryBean的區(qū)別?
BeanFactory 是 IoC 底層容器 ,FactoryBean 是 創(chuàng)建 Bean 的一種方式,幫助實現(xiàn)復(fù)雜的初始化邏輯
Spring IoC 容器的啟動過程?
這個問題只要看懂了第一張流程圖,以及最后的ApplicationContext.refresh()方法中的內(nèi)部13個子方法,再回答這個問題應(yīng)該問題不大,面試官應(yīng)該會眼前一亮,ho,有點東西!!!