一、IOC收叶,inversion of control骄呼,控制反轉(zhuǎn)
1. 如何理解IOC
以前對(duì)象的創(chuàng)建由程序員控制,什么時(shí)候創(chuàng)建判没,用哪個(gè)構(gòu)造函數(shù)蜓萄;控制反轉(zhuǎn)把控制權(quán)交給容器。Spring通過(guò)DI實(shí)現(xiàn)控制反轉(zhuǎn)澄峰。把Spring IoC容器比作一間餐館嫉沽,來(lái)到餐館時(shí)直接招呼服務(wù)員:點(diǎn)菜!并不關(guān)心菜的原料是什么俏竞、如何把菜做出來(lái)绸硕。IoC容器也一樣,你只需要告訴它需要某個(gè)bean魂毁,它就把對(duì)應(yīng)的實(shí)例扔給你玻佩,無(wú)需關(guān)心bean是否依賴(lài)其他組件、如何初始化席楚。
就像做菜需要菜譜和原材料咬崔,容器也需要記錄各個(gè)對(duì)象以及依賴(lài)關(guān)系;BeanDefinition記錄該信息烦秩;每個(gè)bean都有對(duì)應(yīng)的BeanDefinition垮斯,保存包括class類(lèi)型、是否是抽象類(lèi)只祠、構(gòu)造方法和參數(shù)兜蠕、其它屬性等信息。向容器請(qǐng)求對(duì)象時(shí)抛寝,容器基于beanDefinition利用反射機(jī)制構(gòu)建bean實(shí)例并返回牺氨。
BeanFactory和BeanDefinitionRegistry接口基于BeanDefinition完成bean的創(chuàng)建和管理狡耻,就像餐館上菜的過(guò)程。BeanFactory包含getBean等管理bean的方法猴凹,BeanDefinitionRegistry包含 registerBeanDefinition夷狰、removeBeanDefinition、getBeanDefinition等管理BeanDefinition的方法郊霎。簡(jiǎn)單來(lái)說(shuō)沼头,bean管理就是創(chuàng)建beanDefinition并放入beanRegistry,使用bean的時(shí)候則調(diào)用beanFactory的getBean方法獲得bean實(shí)例书劝。
容器的工作分為容器啟動(dòng)階段與bean實(shí)例化階段进倍;前者通過(guò)xml、注解加載bean的定義购对,構(gòu)造為beanDefinition并放到BeanRegistry猾昆。后者在調(diào)用getBean或由于bean的依賴(lài)關(guān)系需要隱式調(diào)用getBean時(shí)觸發(fā),容器檢查該bean是否實(shí)例化完成骡苞,完成則返回bean垂蜗,否則根據(jù)beanDefinition進(jìn)行初始化并返回。
我們通常使用的容器ApplicationContext解幽,構(gòu)建在BeanFactory之上贴见,除了具有BeanFactory的能力外,還提供其他支持等躲株。它在容器啟動(dòng)時(shí)全部完成所有bean的初始化和依賴(lài)注入操作片部。
2. 依賴(lài)注入DI
DI已經(jīng)成為一個(gè)設(shè)計(jì)模式理念,讓代碼變得容易理解和測(cè)試霜定。傳統(tǒng)的做法是每個(gè)對(duì)象負(fù)責(zé)管理與自己協(xié)作的對(duì)象引用档悠。DI的做法是由第三方(容器)負(fù)責(zé)管理對(duì)象的依賴(lài)關(guān)系,對(duì)象無(wú)需自行創(chuàng)建或者管理他們的依賴(lài)關(guān)系望浩。
DI帶來(lái)的最大收益就是松耦合站粟。如果一個(gè)對(duì)象只通過(guò)接口來(lái)表明依賴(lài)關(guān)系,這種依賴(lài)就能在對(duì)象本身毫不知情的情況下曾雕,用不同的具體實(shí)現(xiàn)進(jìn)行替換奴烙。這是是面向接口編程的好處。對(duì)依賴(lài)進(jìn)行替換的最常用方法就是在測(cè)試的時(shí)候是用mock來(lái)實(shí)現(xiàn)剖张。
3. 特殊情形
日常編碼中可能會(huì)用到beanFactory的情形:BeanFactory可以動(dòng)態(tài)的往spring容器中注入bean切诀;假設(shè)我一個(gè)bean最開(kāi)始不要求被spring實(shí)例化,在某個(gè)時(shí)機(jī)需要自己實(shí)例化搔弄,實(shí)例化好之后通過(guò)BeanFactory添加到spring容器當(dāng)中:applicationContext.getBeanFactory().registerSingleton("bname",你的對(duì)象);
如何在工具類(lèi)中使用bean幅虑?首先spring的DI限于在bean中進(jìn)行依賴(lài)注入,進(jìn)行bean的裝配顾犹;其次倒庵,工具類(lèi)通常不是bean褒墨,我們通常會(huì)使用其靜態(tài)方法,而無(wú)需對(duì)工具類(lèi)進(jìn)行實(shí)例化擎宝;第三郁妈,非bean的類(lèi)中使用其他bean可以通過(guò)applicationContext.getBean("beanName")的方式。
如果有多個(gè)bean可以注入如何選擇绍申,autowired首先選擇類(lèi)型一致的bean噩咪,如果有多個(gè)類(lèi)型一致的bean則進(jìn)一步使用qualifier通過(guò)bean的名字進(jìn)行選擇。但是現(xiàn)在好像已經(jīng)不流行autowired了极阅。(Q)
二胃碾、bean
1. bean的作用域scope
spring支持6種作用域,并支持自定義作用域筋搏,默認(rèn)為singleton仆百。prototype;request表示每個(gè)請(qǐng)求創(chuàng)建一個(gè)實(shí)例奔脐;session是在一個(gè)會(huì)話周期內(nèi)有一個(gè)實(shí)例俄周;application是指在一個(gè)servletContext中只有一個(gè)實(shí)例;此外還有websocket帖族。prototype為多例模式栈源,即每次從beanFactory獲取bean都會(huì)創(chuàng)建一個(gè)新的bean挡爵。
1.1 單例
在Spring出現(xiàn)之前竖般,我們需要用到某個(gè)實(shí)例就需要自己去調(diào)用構(gòu)造函數(shù)把它構(gòu)造出來(lái),如果我們需要在多個(gè)地方使用該實(shí)例茶鹃,要么多次構(gòu)造涣雕,要么通過(guò)參數(shù)傳遞。造成大量的實(shí)例化對(duì)象闭翩,并且他們的生命周期可能就是從方法的調(diào)用開(kāi)始到方法的調(diào)用結(jié)束為止挣郭,加大了GC回收的壓力。了解設(shè)計(jì)模式的會(huì)想到使用單例模式的方式來(lái)解決問(wèn)題疗韵。
采用單例模式的問(wèn)題兑障,第一是大量的代碼需要改造、大量的重復(fù)單例模版代碼蕉汪;其次是單例模式需要把單例模版代碼和業(yè)務(wù)代碼放在一起流译。
解決方案類(lèi)似于連接池,我們把連接放在一個(gè)池子里者疤,需要建立連接則直接在池子中找空閑連接資源福澡。這個(gè)池子的思想就是spring容器的原型。
2. bean的生命周期與擴(kuò)展點(diǎn)
IoC 容器管理bean的生命周期驹马,在不同階段革砸,Spring 提供了不同的擴(kuò)展點(diǎn)來(lái)改變bean的命運(yùn)除秀。
1)容器啟動(dòng)階段,BeanFactoryPostProcessor可以在實(shí)例化對(duì)象前算利,對(duì)注冊(cè)到容器的BeanDefinition進(jìn)行操作册踩,如修改bean的屬性等。有自定義需求需要實(shí)現(xiàn)BeanFactoryPostProcessor接口笔时;另外棍好,如果容器中有多個(gè)BeanFactoryPostProcessor,需實(shí)現(xiàn)Ordered接口允耿,以保證BeanFactoryPostProcessor按序執(zhí)行借笙。
??:spring支持把配置屬性統(tǒng)一放在properties文件,bean中用${}引用较锡,從而方便配置屬性的統(tǒng)一管理业稼、及運(yùn)維人員維護(hù)不同環(huán)境的配置,該功能通過(guò)PropertyPlaceholderConfigurer實(shí)現(xiàn)蚂蕴,該類(lèi)實(shí)現(xiàn)BeanFactoryPostProcessor接口低散。容器啟動(dòng)階段完成后,BeanDefinition保存的對(duì)象屬性是占位符骡楼;執(zhí)行PropertyPlaceholderConfigurer將占位符替換為properties文件中的屬性值熔号。第二階段實(shí)例化bean時(shí),bean定義中的屬性值已完成替換鸟整。
2)對(duì)象實(shí)例化階段引镊,BeanPostProcessor處理容器內(nèi)所有符合條件且已經(jīng)實(shí)例化的對(duì)象。BeanFactoryPostProcessor處理beanDefinition篮条;BeanPostProcessor處理bean實(shí)例弟头。該接口提供兩個(gè)方法:postProcessBefore/AfterInitialization。注解涉茧、AOP等功能的實(shí)現(xiàn)大量使用BeanPostProcessor赴恨,比如我可以自定義一個(gè)注解,同時(shí)實(shí)現(xiàn)BeanPostProcessor接口伴栓,在實(shí)現(xiàn)類(lèi)中判斷bean實(shí)例是否有該注解伦连,如果有,則對(duì)bean實(shí)例執(zhí)行響應(yīng)操作钳垮。
Spring中有很多Aware接口惑淳,其作用就是在對(duì)象實(shí)例化完成后將Aware接口中規(guī)定的依賴(lài)注入到當(dāng)前實(shí)例中。比如最常見(jiàn)的ApplicationContextAware接口扔枫,實(shí)現(xiàn)了這個(gè)接口的類(lèi)都可以獲取到一個(gè)ApplicationContext對(duì)象汛聚。當(dāng)容器中每個(gè)對(duì)象的實(shí)例化過(guò)程走到BeanPostProcessor前置處理這一步時(shí),容器會(huì)檢測(cè)到之前注冊(cè)到容器的ApplicationContextAwareProcessor短荐,然后就會(huì)調(diào)用其postProcessBeforeInitialization()方法倚舀,檢查并設(shè)置Aware相關(guān)依賴(lài)叹哭。(Q)
三、JavaConfig與注解
最初Spring使用XML配置bean的定義與依賴(lài)關(guān)系痕貌,缺點(diǎn)是大量的XML文件使項(xiàng)目變得復(fù)雜且難以管理风罩。后來(lái),基于純Java Annotation依賴(lài)注入框架?Guice出世舵稠,其性能明顯優(yōu)于采用XML方式的Spring超升,甚至有人認(rèn)為,?Guice可以取代Spring哺徊。正是這樣的危機(jī)感室琢,促使Spring推出JavaConfig項(xiàng)目,基于Java代碼和Annotation注解來(lái)描述bean之間的依賴(lài)綁定關(guān)系落追。
@ComponentScan表示Spring會(huì)自動(dòng)掃描所有通過(guò)注解配置的bean盈滴,并注冊(cè)到IOC容器中。basePackages屬性可指定掃描范圍轿钠,不指定則默認(rèn)從聲明?@ComponentScan所在類(lèi)的package進(jìn)行掃描巢钓。正因?yàn)槿绱耍琒pringBoot的啟動(dòng)類(lèi)都默認(rèn)在?src/main/java下疗垛。
@Conditional表示僅當(dāng)指定條件成立時(shí)才會(huì)創(chuàng)建對(duì)應(yīng)bean症汹,否則忽略bean的聲明。
四贷腕、自動(dòng)配置原理
Spring Boot根據(jù)依賴(lài)背镇、自定義的bean、classpath下有沒(méi)有某個(gè)類(lèi)等因素來(lái)猜測(cè)你需要的bean花履,然后注冊(cè)到IOC容器中芽世。猜測(cè)的動(dòng)作由EnableAutoConfigurationImportSelector進(jìn)行挚赊,這個(gè)bean通過(guò)@EnableAutoConfiguration注入到容器诡壁。
SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class,this.beanClassLoader)));
上述代碼中,SpringFactoriesLoader搜索Jar包中的META-INF/spring.factories文件如下荠割,找到指定名稱(chēng)的@Configuration類(lèi)妹卿, 然后通過(guò)反射實(shí)例化這些類(lèi)并注入到IOC容器。
以?DataSourceAutoConfiguration為例:
@ConditionalOnClass({DataSource.class,EmbeddedDatabaseType.class})表示僅當(dāng)Classpath中存在DataSource或者EmbeddedDatabaseType類(lèi)時(shí)才啟用這個(gè)配置蔑鹦。@EnableConfigurationProperties(DataSourceProperties.class)將DataSource的默認(rèn)配置類(lèi)注入到IOC容器中.
回顧一下夺克,@EnableAutoConfiguration中導(dǎo)入了EnableAutoConfigurationImportSelector類(lèi),而這個(gè)類(lèi)的selectImports()通過(guò)SpringFactoriesLoader得到了大量的配置類(lèi)嚎朽,而每一個(gè)配置類(lèi)則根據(jù)條件化配置來(lái)做出決策铺纽,以實(shí)現(xiàn)自動(dòng)配置。
五哟忍、啟動(dòng)引導(dǎo):spring boot應(yīng)用啟動(dòng)的秘密
1. SpringBoot整個(gè)啟動(dòng)流程的兩個(gè)步驟
1)初始化一個(gè)SpringApplication對(duì)象狡门;通過(guò)SpringFactoriesLoader找到?spring.factories文件中配置的?ApplicationContextInitializer和?ApplicationListener兩個(gè)接口的實(shí)現(xiàn)類(lèi)名稱(chēng)陷寝,以便后期構(gòu)造相應(yīng)的實(shí)例。?ApplicationContextInitializer的主要目的是在?ConfigurableApplicationContext做refresh之前其馏,對(duì)ConfigurableApplicationContext實(shí)例做進(jìn)一步的設(shè)置或處理凤跑。ConfigurableApplicationContext繼承自ApplicationContext,其主要提供了對(duì)ApplicationContext進(jìn)行設(shè)置的能力叛复。
2)執(zhí)行該對(duì)象的run方法
(1)通過(guò)SpringFactoriesLoader查找并加載所有的?SpringApplicationRunListeners仔引;SpringApplicationRunListeners本質(zhì)上是事件發(fā)布者,它在SpringBoot應(yīng)用啟動(dòng)的不同時(shí)間點(diǎn)發(fā)布不同應(yīng)用事件類(lèi)型(ApplicationEvent)褐奥,如果有哪些事件監(jiān)聽(tīng)者(ApplicationListener)對(duì)這些事件感興趣咖耘,則可以接收并且處理。
(2)創(chuàng)建并配置當(dāng)前應(yīng)用將要使用的Environment撬码,如果是web項(xiàng)目就創(chuàng)建?StandardServletEnvironment鲤看,描述應(yīng)用程序當(dāng)前的運(yùn)行環(huán)境,包括profile耍群、properties义桂,不同的環(huán)境(eg:生產(chǎn)環(huán)境、預(yù)發(fā)布環(huán)境)使用不同的配置文件蹈垢,屬性則從配置文件慷吊、環(huán)境變量、命令行參數(shù)獲取曹抬。Environment準(zhǔn)備好后溉瓶,調(diào)用SpringApplicationRunListener的?environmentPrepared()方法,通知事件監(jiān)聽(tīng)者:在整個(gè)應(yīng)用的任何時(shí)候谤民,都可以從Environment中獲取資源堰酿。
(3)打印spring banner
(4)初始化ApplicationContext,將準(zhǔn)備好的Environment設(shè)置給ApplicationContext张足;遍歷所有ApplicationContextInitializer對(duì)已經(jīng)創(chuàng)建好的ApplicationContext進(jìn)一步處理触创;調(diào)用SpringApplicationRunListener的contextPrepared()方法,通知所有的監(jiān)聽(tīng)者ApplicationContext已經(jīng)準(zhǔn)備完畢为牍;將所有的bean加載到容器中哼绑;調(diào)用SpringApplicationRunListener的?contextLoaded()方法,通知所有的監(jiān)聽(tīng)者ApplicationContext已經(jīng)裝載完畢碉咆。
(5)調(diào)用ApplicationContext的?refresh()方法抖韩,完成IoC容器可用的最后一道工序:獲取所有BeanFactoryPostProcessor在容器實(shí)例化對(duì)象前,對(duì)BeanDefinition進(jìn)行額外操作疫铜。
這就是Spring Boot的整個(gè)啟動(dòng)流程茂浮,其核心就是在Spring容器初始化并啟動(dòng)的基礎(chǔ)上加入各種擴(kuò)展點(diǎn),這些擴(kuò)展點(diǎn)包括:ApplicationContextInitializer、ApplicationListener以及各種BeanFactoryPostProcessor等等席揽。
2. Spring的事件監(jiān)聽(tīng)機(jī)制
過(guò)去佃乘,事件監(jiān)聽(tīng)機(jī)制多用于圖形界面編程,比如點(diǎn)擊按鈕等操作被稱(chēng)為事件驹尼,而當(dāng)事件觸發(fā)時(shí)趣避,應(yīng)用程序作出一定的響應(yīng)則表示應(yīng)用監(jiān)聽(tīng)了這個(gè)事件。服務(wù)器端新翎,事件的監(jiān)聽(tīng)機(jī)制更多的用于異步通知以及監(jiān)控和異常處理程帕。Java提供了實(shí)現(xiàn)事件監(jiān)聽(tīng)機(jī)制的兩個(gè)基礎(chǔ)類(lèi):自定義事件類(lèi)型EventObject、事件的監(jiān)聽(tīng)器EventListener地啰。EventListener有兩個(gè)方法:onMethodBegin/End愁拭。事件監(jiān)聽(tīng)中涉及事件發(fā)布者、事件亏吝、事件監(jiān)聽(tīng)者岭埠。
Spring的ApplicationContext容器內(nèi)部中的所有事件類(lèi)型均繼承自 AppliationEvent(EventObject子類(lèi)),所有監(jiān)聽(tīng)器都實(shí)現(xiàn)ApplicationListener接口(EventListener)蔚鸥。一旦在容器內(nèi)發(fā)布ApplicationEvent惜论,注冊(cè)到容器的ApplicationListener就會(huì)對(duì)這些事件進(jìn)行處理。Spring提供默認(rèn)實(shí)現(xiàn)如:?ContextClosedEvent表示容器在即將關(guān)閉時(shí)發(fā)布的事件止喷,?ContextRefreshedEvent表示容器在初始化或者刷新的時(shí)候發(fā)布的事件馆类。
六、DispatchServlet
DispatcherServlet的init方法里面load了springmvc的配置信息弹谁,然后初始化了spring容器(調(diào)用了refresh方法)岔冀,把controller的信息緩存了候学,比如映射信息;然后DispatcherServlet會(huì)攔截所有的請(qǐng)求沾谓,根據(jù)用戶(hù)的請(qǐng)求信息通過(guò)緩存的映射信息找到對(duì)應(yīng)的controller的對(duì)應(yīng)方法计螺,然后反射調(diào)用(其實(shí)底層的源碼就是反射調(diào)用controller的方法)啰脚,然后視圖裁決效扫、解析等等工作看幼。