內(nèi)容概覽
- 簡化Java開發(fā)
- Spring Bean
- Spring Bean生命周期
- 裝配Bean的可選方案
- 自動化裝配Bean
- 通過Java配置類裝配Bean
- 通過XML裝配Bean
- 導(dǎo)入和混合配置
- 總結(jié)
1. 簡化Java開發(fā)
Spring框架是為了解決Java開發(fā)的復(fù)雜性而創(chuàng)建的,相對于EJB的復(fù)雜,Spring只用簡單的JavaBean(或者Bean)組件就能實現(xiàn)EJB所完成的事情。而且因為Spring的簡單,松耦合等特性,使它能服務(wù)但是不限于服務(wù)端開發(fā),因此努力學(xué)習(xí)Spring是一個Java程序員正確的選擇。
2. Spring Bean
Spring有跟多概念,其中最基本的一個就是bean,那到底spring bean是什么?
我們知道,Spring最核心的兩個概念就是IOC和AOP,而IOC容器管理的對象就是一個個Bean,說直白些就是一個個Java類對象。
概念簡單明了,我們提取處關(guān)鍵的信息:
- bean是對象,一個或者多個不限定
- bean由Spring中一個叫IoC的東西管理
- 我們的應(yīng)用程序由一個個bean構(gòu)成
那么IoC又是什么東西?
控制反轉(zhuǎn)英文全稱:Inversion of Control,簡稱就是IoC??刂品崔D(zhuǎn)通過依賴注入(DI)方式實現(xiàn)對象之間的松耦合關(guān)系。在普通Java代碼中,我們一個個去new對象是很麻煩的,如果一個類中的屬性類型是其它類,那么它們之間就有依賴,這樣的依賴對于一個類來說可能是一個或者多個。而Spring中,簡而言之,就是:IoC就是一個對象定義其依賴關(guān)系而不創(chuàng)建它們的過程。
從上面的過程,可以想象一個代碼從復(fù)雜到簡單的過程,是如何實現(xiàn)了松耦合。
3. Spring Bean生命周期
在傳統(tǒng)Java項目中,一個對象的生命周期很簡單,先使用new進行實例化,然后使用,一旦不再使用,就被垃圾回收。
相比之下,Spring中的Bean對象的生命周期要復(fù)雜的多,正確了解Spring Bean的生命周期非常重要,不但是面試可能問到,還有可能需要利用Spring提供的擴展點來自定義Bean的創(chuàng)建過程。如下圖,展示了Bean裝載到Spring應(yīng)用上下文(就是項目運行環(huán)境中)的一個典型的生命周期過程。

上面的內(nèi)容可以詳細(xì)描述為以下步驟:
- Spring 對Bean進行實例化
- Spring將值和Bean的引用注入到bean對應(yīng)的屬性中
- 如果bean實現(xiàn)了BeanNameAware接口,Spring將bean的id傳遞給setBeanName方法
- 如果bean實現(xiàn)了BeanFactoryAware接口,Spring將調(diào)用setBeanFactory方法,將BeanFactory容器實例傳入
- 如果bean實現(xiàn)了ApplicationContextAware接口,Spring將調(diào)用setApplicationContext方法,將bean所在的應(yīng)用上下文的引用傳入進來
- 如果bean實現(xiàn)了BeanPostProcessor接口,Spring將調(diào)用它的postProcessBeforeInitialization方法
- 如果bean實現(xiàn)了InitializingBean接口,Spring將調(diào)用它的afterPropertiesSet方法,類似的,如果bean使用init-method生命了初始化方法,該方法也會被調(diào)用
- 如果bean實現(xiàn)了BeanPostProcessor接口,Spring將調(diào)用它的 postProcessAfterInitialization方法
- 此時,bean已經(jīng)準(zhǔn)備就緒,會一直存在于上下文中,直到該程序應(yīng)用上下文環(huán)境被銷毀
- 最后是銷毀,如果bean實現(xiàn)了DisposableBean接口,Spring將調(diào)用它的destory方法,如果bean使用了destory-method生命了銷毀的方法,該方法也會被調(diào)用。
上面的過程對于使用過Spring的人來說,應(yīng)該會熟悉部分或者全部,但是對于初學(xué)者來說可能有點暈。不過這個過程很重要,從面試經(jīng)驗來看,能記住最好記住,最起碼多看幾遍熟悉起來,后面的學(xué)習(xí)中會一點點去了解,到時候再記起來就容易了。
4. 裝配Bean的可選方案
在Spring中,對象無需自己查找和創(chuàng)建與其相關(guān)聯(lián)的其他對象,相反,Spring容器會負(fù)責(zé)把相互有關(guān)系,需要合作的對象引用賦予各個對象中,這樣才能實現(xiàn)控制翻轉(zhuǎn)Ioc。
創(chuàng)建各個對象,也就是各個bean直接的協(xié)作關(guān)系的行為叫做裝配。這也是依賴注入的本質(zhì)。在Spring中,裝配Bean有很多種方式,下面介紹常見的三種方式。
- 在XML中進行顯式裝配
- 在Java配置類代碼中進行顯式裝配
- 隱式的bean發(fā)現(xiàn)機制和自動裝配
多種方式雖然會讓spring使用變得復(fù)雜,而且功能上有重疊,但是大部分情況只要根據(jù)自己的喜好使用就行,特殊情況,可能只有其中一種情況合適。三種方式推薦的是,能用隱式就用隱式最好,如果需要寫配置,Java配置類比XML更好一些。
5. 自動化裝配Bean
下圖是一張Spring官網(wǎng)的圖片,從上面可以看出Spring Framework的結(jié)構(gòu):

其中最下面基礎(chǔ)的組成就是beans,core,context,spel幾個部分,因此我們在開發(fā)spring時,最起碼要引入的依賴如下:

也就是說,我們使用spring ioc開發(fā),引入上面五個就可以了。下面就用上面這些來開發(fā)第一個spring程序。
Spring可以使用XML和Java代碼來進行裝配,但是最強大的還是自動化裝配,其從兩個角度實現(xiàn)自動化裝配:
- 組件掃描:Spring會自動發(fā)現(xiàn)應(yīng)用上下文中的bean
- 自動裝配:Spring自動滿足bean之間的依賴
先來寫一個基礎(chǔ)的Java類:

這個類只有兩個基礎(chǔ)的屬性,為了方便,我們直接賦上了初始值。特殊的地方是,類上面有一個@Component注解,這個簡單的注解表示該類會作為一個組件類,并告知Spring要為這個類創(chuàng)建bean。這個注解只是一個基礎(chǔ)注解,衍生出來的還有很多特殊用途的,像@Service,@Controller等,后面會一一介紹。同時在注解上面還可以給對應(yīng)的bean命名,如:

上面表示為該bean設(shè)置了一個id=helloUser005,如果不寫,默認(rèn)的id就是類名,只不過首字母要小寫。
然后在這個類的同一目錄下,創(chuàng)建一個配置類:

@Configuration注解表示這是一個配置類,@ComponentScan注解表示配置類會掃描該配置類所在的包以及所有子包,尋找所有帶@Component注解的類。
@ComponentScan后面并沒有配置屬性,就表示掃描配置類所在的包以及子包,這里還可以手動配置從哪個包開始掃描:

或者為了更清楚,這樣寫:@ComponentScan(basePackages = "ioc0051"),這樣就表示掃描 ioc0051 包以及其子包。不僅如此,還可以配置掃描多個包:

因為包屬性值是字符串,有出錯的可能,也可以指定包中所含的類或者接口:

這樣就能保住正確性。總之各種方法任你挑選。這樣通過配置類實現(xiàn)簡單的自動裝配就完成了。下面我們來測試一下,首先是傳統(tǒng)的方法,從Spring上下文啟動,因為這里使用的是類配置,所以使用 AnnotationConfigApplicationContext 應(yīng)用上下文類。具體測試方法如下:

通過getBean方法可以獲取Spring為我們創(chuàng)建的對象實例,來看運行結(jié)果:

可以看到,我們并沒有手動new對象,但是Spring為我們創(chuàng)建的對象實例,就是bean,獲取bean就能獲取。自動裝配不僅可以使用Java配置類,還可以使用XML配置文件實現(xiàn):

配置文件是以beans標(biāo)簽開始的,可見這里就是和所有bean相關(guān)的定義。<context:component-scan 元素是與注解@ComponentScan現(xiàn)對于的,這里可以按照需求定義需要的掃描位置。
測試xml方式,需要使用 ClassPathXmlApplicationContext 應(yīng)用上下文類,具體代碼如下:

和前面的配置類方法類似,這里不再說明。Spring還有另一種更加友好的方法加載應(yīng)用上下文進行測試,就是使用測試組件,需要加入兩個依賴:

junit不用描述,spring-test是為測試spring代碼的測試組件,上面的配置類啟動測試可以寫成:

這里使用了Spring的SpringJUnit4ClassRunner,可以在測試的時候自動創(chuàng)建應(yīng)用上下文。注解@ContextConfiguration用來加載配置,classes表示加載一個或者多個配置類,如果是xml的方式可以使用屬性:
- @ContextConfiguration(locations = {"classpath:spring-ioc004.xml"})
注解@Autowired表示將一個bean,也就是類的對象實例注入到此處,這里對象之間的引用就這樣就可以,不用手動new一個。這種使用方式是最簡單的方式,還有一些其它方式,如果類作為入?yún)魅氲椒椒ㄖ校敲捶椒ň蜕厦婢涂梢约由献⒔釦Autowired,最經(jīng)典的比如構(gòu)造方法和setter方法:


另外,如果在啟動時,不確定bean是否創(chuàng)建了,那么可以加上屬性:

這樣就不會報錯了。不過要小心這個屬性,如果沒有進行null檢測就使用,有可能出現(xiàn)空指針異常。上面幾種可以選擇自己喜歡的方式使用。后面會繼續(xù)討論自動裝配的復(fù)雜性。@Component和@Autowired都是Spring的注解,如果更喜歡Java的依賴注入,可以使用@Named和@Inject,不過使用這兩個注解的還是少數(shù),因此這里主要學(xué)習(xí)Spring的注解。
6. 通過Java配置類裝配Bean
盡管很多情況下,通過組件掃描和自動裝配實現(xiàn)實現(xiàn)自動化配置是推薦的使用方式,但是也有不少的情況下,需要顯示的配置bean,比如不是自己寫的類,沒法在代碼中加Spring的注解等情況。這種情況下可以通過配置類或者xml文件來手動配置Spring,這里先來討論配置類的方式。
前面的自動裝配中,我們已經(jīng)寫過Java配置類,配置類需要在類上面加上@Configuration注解,因為這里不需要自動掃描,需要顯式的進行配置Bean,所以@ComponentScan注解就不用寫了。如下:

下面來定義一個普通的類,這里是顯示裝配,因此@Component注解不用寫:

配置類中的配置如下:

方法內(nèi)容就是如果創(chuàng)建并返回一個新的對象,方法上面加了一個@Bean注解,這個注解會告訴Spring這里將返回一個對象,該對象會注冊到Spring的應(yīng)用上下文中,這樣就顯式定義好了一個簡單的bean。
默認(rèn)情況bean的id就是帶有@Bean注解的方法名,如果想指定一個不同的名字可以像下面這樣配置:

可以看到通過配置類配置一個bean也是非常簡單的。下面我們再來配置一個類,這個類的創(chuàng)建過程需要依賴上面的bean,如果在配置類中裝配它們呢?先來新建一個類:

然后在配置類中配置:

這里也是返回一個new對象,雖然看上去構(gòu)造方法的參數(shù)是通過調(diào)用user0071()方法得到的,但是其實并非如此 ,因為user0071()方法上面已經(jīng)加了@Bean注解,因此Spring會攔截所有對它的調(diào)用,直接返回該方法創(chuàng)建的bean。這樣就能保證每個使用user0071()方法的地方不會每次都new一個新的對象。
上面的方式可能會有點不好理解,其實還有一個更容易理解的方法:

這里user0071直接作為方法的一個參數(shù)傳入,Spring在創(chuàng)建Role0072的bean的時候,會自動裝配一個user0071的bean到方法中,然后就可以使用它。
上面這種方式通常是裝配bean的最佳選擇,因為不僅可以裝配同一個配置類中的bean,其它類中,甚至xml文件中配置的bean,自動裝配里配置的bean等都可以裝配到這里,最大實現(xiàn)了靈活性。
這里的裝配都是通過構(gòu)造方法,當(dāng)然還可以通過其它比如setter方法:


根據(jù)實際情況,靈活編寫代碼即可。
7. 通過XML裝配Bean
下面來看通過xml配置的方式裝配。這種方式有很長的歷史,spring開始的時候,xml是最主要的方法,但是現(xiàn)在不是了。
使用xml與Java配置類不同,配置類需要在類上面加上@Configuration注解,xml的方式,首先要創(chuàng)建一個xml文件,并且要以<beans>為根元素:

可以看到,xml方式確實很麻煩,不如Java配置類的方式簡單。用來裝配bean的最近本的xml元素包含在spring-beans模式中,在上面這個xml文件中,它被定義為根命名空間,<beans>是該模式中的一個元素,它是所有Spring配置文件的根元素?,F(xiàn)在我們有了一個基本的配置文件,就像一個空配置類一樣,它現(xiàn)在沒有配置任何內(nèi)容。
在xml文件中配置bean需要使用<bean>元素,類似配置類中的@Bean注解,下面是一個最簡單的bean:

里面通過class屬性指定了創(chuàng)建bean的類,這里需要使用全限定的類名,因為沒有配置id,所以這里會根據(jù)全限定名命名一個id,這里的id就是 “ioc008.User0081#0” ,其中#0是一種計數(shù)形式,用來相同類型的其它bean,比如下一個就是#1。雖然自動命名很方便,但是引用起來卻并不方便,因此更好的做法是明確給id賦值:

注意,這里只需要在用到id屬性的時候再配置即可,不用給每個都配置。把上面這個<bean>標(biāo)簽來和配置類中的@Bean對比,可以發(fā)現(xiàn)幾個特點,在xml中配置bean后,你不再需要手動new一個對象,當(dāng)Spring發(fā)現(xiàn)這個<bean>時,它會調(diào)用該類的默認(rèn)構(gòu)造器,不過它沒有Java配置類的形式強大,在配置類中,你可以通過任意方式創(chuàng)建一個對象。class屬性的值也是一個字符串的形式,這就要求我們自己要保證把類的全限定名寫正確,這是一個隱患。而且在類改名字的時候,這里也不會提示修改,不過現(xiàn)在很多IDE會在編譯期進行提示,要充分利用這一點。
下面來看一下如何在xml中配置構(gòu)造方法的依賴注入,這里有兩種風(fēng)格,也就是兩類標(biāo)簽可以選擇,一個是<constructor-arg>,另一個是 以 c: 開頭的標(biāo)簽,使用這種標(biāo)簽需要引入c-命名空間:
- xmlns:c="http://www.springframework.org/schema/c"
使用<constructor-arg>比c標(biāo)簽要冗長一些,但是<constructor-arg>標(biāo)簽有些注入是c標(biāo)簽無法實現(xiàn)的,下面來舉例說明各種情況。
首先來看一個最簡單的構(gòu)造方法注入:

在xml中配置就是:

<constructor-arg>元素會告知Spring將一個id為user0082的bean引用傳遞到Role8801的構(gòu)造方法中。作為替代方案,下面來看如何使用c命名空間的寫法,首先在頂部聲明其模式:

然后看一下具體寫法:

它作為bean標(biāo)簽的一個屬性,屬性名以“c:”開頭,也就是命名空間的前綴,接下來就是構(gòu)造方法中參數(shù)的名字(user0082),在此之后是“-ref”,這是一種約定命名,它告訴Spring正在裝配的是一個bean的引用,這個bean的id是user0082。顯然,使用c-命名空間屬性比<constructor-arg>標(biāo)簽要簡潔一些,不過有一個問題是參數(shù)的名字也包含在其中,這樣修改參數(shù)名字的時候,xml中的配置屬性名也要跟著改,因此還有一種替代方案,是使用參數(shù)的位置:

這個屬性名看上去更奇怪一些,把參數(shù)的名字改成了0,也就是參數(shù)的位置索引,0表示第一個參數(shù),xml中不允許屬性名以數(shù)字開頭,因此前面加了下劃線。上面的幾種方法就是如何將一個bean裝配到另一個bean中,下面來看如何裝配字面量。假設(shè)構(gòu)造方法中的兩個參數(shù)是String類型:

來看一下xml中的裝配配置:

在bean標(biāo)簽中,定義了兩個<constructor-arg>元素,每個元素都是直接賦值,并沒有應(yīng)用其它bean,因此不使用ref,直接使用value屬性,該屬性表示給定的值以字面量的形式賦值到構(gòu)造方法的參數(shù)中。除了這種方法,來看一下使用c-命名空間屬性的寫法:

可以看到,屬性的名字如果后面是引用其它bean就加-ref,如果是直接賦值就去掉-ref,同理,使用參數(shù)位置索引的方式如下:

在普通的裝配引用和字母量值方面,<constructor-arg>元素和c-命名空間屬性兩種方法都可以,但是有一種情況是只有<constructor-arg>才能實現(xiàn)的,就是參數(shù)中有集合類型,如下:

首先可以將集合的值直接設(shè)置為null,這樣也算一種賦值方式:

賦值為null直接在<constructor-arg>元素內(nèi)使用<null>元素即可,當(dāng)然大部分情況下是需要賦值一些實際的值,這種情況因為參數(shù)是List類型,可以使用<list>標(biāo)簽聲明一個列表:

其中<list>是<constructor-arg>的子元素,這樣配置會將一個包含值的list列表傳遞到構(gòu)造方法中,<list>下用<value>元素代表List中每個元素的值。還有一種略微麻煩的方法,可以使用<ref>元素代替<value>元素,當(dāng)然有ref就表示要引用其它bean,這里先配置幾個String類型的bean:

接下來引用這些bean:

參數(shù)是List集合,使用<list>元素,如何是Set集合,就使用<set>元素,

上面的幾個例子中,都是只有一個帶參數(shù)的構(gòu)造方法,因此我們只能強制使用構(gòu)造器注入的,在正常有選擇的情況下,還可以使用setter方法注入,來看一個類:

如果注入不成功,say方法就無法運行,這個類既可以使用構(gòu)造函數(shù)注入,也可以使用setter方法注入,下面來看setter的注入,對一個屬性的注入,需要使用<property>元素,

<property>元素中,name屬性表示參數(shù)的名字,ref表示引用的哪個bean,這里同樣寫bean的id值。我們知道,<constructor-arg>元素提供了c-命名空間屬性作為替代方案,屬性的注入同樣如此,提供了p-命名空間作為替代方案,不過同樣需要在xml中進行聲明:

把上面的例子修改一下:

p-命名空間屬性規(guī)則與c-命名空間十分類似,以p:開頭,后面跟的是屬性參數(shù)的名字加上-ref,表示引用,值填寫引用的bean的id。與c-命名空間屬性類似,p-命名空間屬性也同樣無法表示list的賦值,下面先看一個用<property>元素表示的例子,先看類:

<bean>配置如下:

與<constructor-arg>元素類似,name屬性都表示參數(shù)的名字,ref表示引用其它bean,value表示直接賦值,<property>元素直接同樣可以添加<list>等表示集合的元素,并對集合賦值。上面的例子使用p-命名空間屬性的寫法只能這樣:

可見集合類型裝配起來確實很別扭。這里還有一種方案,就是使用util-命名空間元素標(biāo)簽首先配置集合,然后再用p-命名空間屬性引用,同樣新的命名空間需要在xml中聲明:

來看一下如何定義:

定義好以后,就可以使用:

<util:list>只是util-命名空間中的眾多元素之一, 下面列出來其它元素:

8. 導(dǎo)入和混合配置
上面的各種方式肯定是自動化裝配最受歡迎,不過在必須寫配置的時候,JavaConfig類比XML更好一些,不過實際項目中并不總是按照自己想的發(fā)展,很多時候,我們寫的配置類不僅可能要整合別人的jar包里寫的配置類,還看需要整合別的xml配置。幸好在Spring中,這幾種方案并不是互斥的,可以混合使用。來看一個例子。
首先新建一個package,然后創(chuàng)建一個類,并配置到Java配置類中,


然后在上面這個package同級的目錄下再創(chuàng)建一個package(這樣互不影響,可以理解為多個項目),同樣創(chuàng)建一個類和一個配置類:


然后再創(chuàng)建一個同級的package,這次只創(chuàng)建一個普通類:

將這個類配置到xml文件中:

假設(shè)上面三種配置我們要一起使用,但是卻無法修改,那么最好的辦法就是,我們創(chuàng)建一個自己的JavaConfig類,將這三種配置都引入進來,由于我們新創(chuàng)建的是一個配置類,第一步要做的就是引入兩個其它配置類,配置類之間的需要用到@Import注解,因此引入方法是

這樣就可以將一個或者多個配置類引入到自己的配置類中,然后是引入一個xml配置,這里需要使用注解@ImportResource,配置方法如下:

這樣就在我們自己的配置類中引入了其它的配置類和xml配置。定義好的bean也可以直接使用:

上面就是以JavaConfig類為主,引入其它配置類和xml文件的方式,下面來看以xml為主,引入其它xml和JavaConfig類。
首先創(chuàng)建一個普通的類和配置類:


然后在上面的類的package的同級package下,創(chuàng)建另一個類,并配置在xml中:


在第三個同級package下,新建一個引用它倆的類:

然后新建一個xml文件,將上面兩個配置整合,并注入到類Role0101中,首先來看,在xml中引入其它xml,需要使用元素<import>:

在xml中引入一個Java配置類,引入這個的方式雖然我們熟悉,但是并不只管,就是直接創(chuàng)建一個bean:

通過上面兩種方式,可以在xml中引入其它xml和JavaConfig配置類。我們討論了很多混合配置,在實際項目中,如果配置很多且分散的話,最好的做法就是創(chuàng)建一個自己的總的配置類,然后將其它都引入進來,這樣方便管理,如果單個的配置類或者配置文件過長,也可以根據(jù)組件拆分。
9. 總結(jié)
上面初步介紹了Spring的Bean,以及Bean的簡單裝配和混合裝配,除了裝配,就是Spring Bean的生命周期,這些是重點。
代碼地址:https://gitee.com/blueses/spring-framework-demo
1-6:自動裝配
7:通過Java類裝配
8:通過XML配置裝配
9-10:混合裝配