必會技能之Spring篇

spring是一個開源開發框架,用于簡化java開發。

Spring中的組件稱為bean,可以是任何形式的普通簡單Java對象(Plain Ordinary Java Object,POJO)。

Spring框架的組成結構圖如下所示:

Spring的核心機制

管理Bean

程序主要是通過Spring容器來訪問容器中的Bean,ApplicationContext是Spring容器最常用的接口,該接口有如下兩個實現類:

ClassPathXmlApplicationContext: 從類加載路徑下搜索配置文件,并根據配置文件來創建Spring容器。

FileSystemXmlApplicationContext: 從文件系統的相對路徑或絕對路徑下去搜索配置文件,并根據配置文件來創建Spring容器。

依賴注入

Spring框架的核心功能有兩個:

Spring容器作為超級大工廠,負責創建、管理所有的Java對象,這些Java對象被稱為Bean。

Spring容器管理容器中Bean之間的依賴關系,Spring使用一種被稱為"依賴注入"的方式來管理Bean之間的依賴關系。

使用依賴注入,不僅可以為Bean注入普通的屬性值,還可以注入其他Bean的引用。依賴注入是一種優秀的解耦方式,其可以讓Bean以配置文件組織在一起,而不是以硬編碼的方式耦合在一起。

理解依賴注入

控制反轉(Inverse of Control,IoC)又稱為依賴注入(Dependency Injection),因此不管是依賴注入,還是控制反轉,其含義完全相同。當某個Java對象(調用者)需要調用另一個Java對象(被依賴對象)的方法時,在傳統模式下通常有兩種做法:

原始做法: 調用者主動創建被依賴對象,然后再調用被依賴對象的方法。

簡單工廠模式: 調用者先找到被依賴對象的工廠,然后主動通過工廠去獲取被依賴對象,最后再調用被依賴對象的方法。

注意上面的主動二字,這必然會導致調用者與被依賴對象實現類的硬編碼耦合,非常不利于項目升級的維護。使用Spring框架之后,調用者無需主動獲取被依賴對象,調用者只要被動接受Spring容器為調用者的成員變量賦值即可,由此可見,使用Spring后,調用者獲取被依賴對象的方式由原來的主動獲取,變成了被動接受——所以稱之為控制反轉。

另外從Spring容器的角度來看,Spring容器負責將被依賴對象賦值給調用者的成員變量——相當于為調用者注入它依賴的實例,因此又稱之為依賴注入。

設值注入

設值注入是指IoC容器通過成員變量的setter方法來注入被依賴對象。這種注入方式簡單、直觀,因而在Spring的依賴注入里大量使用。

構造注入

利用構造器來設置依賴關系的方式,被稱為構造注入。通俗來說,就是驅動Spring在底層以反射方式執行帶指定參數的構造器,當執行帶參數的構造器時,就可利用構造器參數對成員變量執行初始化——這就是構造注入的本質。

兩種注入方式的對比

設值注入有如下優點:

與傳統的JavaBean的寫法更相似,程序開發人員更容易理解、接受。通過setter方法設定依賴關系顯得更加直觀、自然。

對于復雜的依賴關系,如果采用構造注入,會導致構造器過于臃腫,難以閱讀。Spring在創建Bean實例時,需要同時實例化其依賴的全部實例,因而導致性能下降。而使用設值注入,則能避免這些問題。

尤其在某些成員變量可選的情況下,多參數的構造器更加笨重。

構造注入優勢如下:

構造注入可以在構造器中決定依賴關系的注入順序,優先依賴的優先注入。

對于依賴關系無需變化的Bean,構造注入更有用處。因為沒有setter方法,所有的依賴關系全部在構造器內設定,無須擔心后續的代碼對依賴關系產生破壞。

依賴關系只能在構造器中設定,則只有組件的創建者才能改變組件的依賴關系,對組件的調用者而言,組件內部的依賴關系完全透明,更符合高內聚的原則。

注意:

建議采用設值注入為主,構造注入為輔的注入策略。對于依賴關系無須變化的注入,盡量采用構造注入;而其他依賴關系的注入,則考慮采用設值注入。

Spring容器中的Bean

對于開發者來說,開發者使用Spring框架主要是做兩件事:①開發Bean;②配置Bean。對于Spring框架來說,它要做的就是根據配置文件來創建Bean實例,并調用Bean實例的方法完成"依賴注入"——這就是所謂IoC的本質。

容器中Bean的作用域

當通過Spring容器創建一個Bean實例時,不僅可以完成Bean實例的實例化,還可以為Bean指定特定的作用域。Spring支持如下五種作用域:

singleton: 單例模式,在整個Spring IoC容器中,singleton作用域的Bean將只生成一個實例。

prototype: 每次通過容器的getBean()方法獲取prototype作用域的Bean時,都將產生一個新的Bean實例。

request: 對于一次HTTP請求,request作用域的Bean將只生成一個實例,這意味著,在同一次HTTP請求內,程序每次請求該Bean,得到的總是同一個實例。只有在Web應用中使用Spring時,該作用域才真正有效。

對于一次HTTP會話,session作用域的Bean將只生成一個實例,這意味著,在同一次HTTP會話內,程序每次請求該Bean,得到的總是同一個實例。只有在Web應用中使用Spring時,該作用域才真正有效。

global session: 每個全局的HTTP Session對應一個Bean實例。在典型的情況下,僅在使用portlet context的時候有效,同樣只在Web應用中有效。

如果不指定Bean的作用域,Spring默認使用singleton作用域。prototype作用域的Bean的創建、銷毀代價比較大。而singleton作用域的Bean實例一旦創建成果,就可以重復使用。因此,應該盡量避免將Bean設置成prototype作用域。

使用自動裝配注入合作者Bean

Spring能自動裝配Bean與Bean之間的依賴關系,即無須使用ref顯式指定依賴Bean,而是由Spring容器檢查XML配置文件內容,根據某種規則,為調用者Bean注入被依賴的Bean。

Spring自動裝配可通過元素的default-autowire屬性指定,該屬性對配置文件中所有的Bean起作用;也可通過對元素的autowire屬性指定,該屬性只對該Bean起作用。

autowire和default-autowire可以接受如下值:

no: 不使用自動裝配。Bean依賴必須通過ref元素定義。這是默認配置,在較大的部署環境中不鼓勵改變這個配置,顯式配置合作者能夠得到更清晰的依賴關系。

byName: 根據setter方法名進行自動裝配。Spring容器查找容器中全部Bean,找出其id與setter方法名去掉set前綴,并小寫首字母后同名的Bean來完成注入。如果沒有找到匹配的Bean實例,則Spring不會進行任何注入。

byType: 根據setter方法的形參類型來自動裝配。Spring容器查找容器中的全部Bean,如果正好有一個Bean類型與setter方法的形參類型匹配,就自動注入這個Bean;如果找到多個這樣的Bean,就拋出一個異常;如果沒有找到這樣的Bean,則什么都不會發生,setter方法不會被調用。

constructor: 與byType類似,區別是用于自動匹配構造器的參數。如果容器不能恰好找到一個與構造器參數類型匹配的Bean,則會拋出一個異常。

autodetect: Spring容器根據Bean內部結構,自行決定使用constructor或byType策略。如果找到一個默認的構造函數,那么就會應用byType策略。

創建Bean的3種方式

使用構造器創建Bean實例

使用構造器來創建Bean實例是最常見的情況,如果不采用構造注入,Spring底層會調用Bean類的無參數構造器來創建實例,因此要求該Bean類提供無參數的構造器。

采用默認的構造器創建Bean實例,Spring對Bean實例的所有屬性執行默認初始化,即所有的基本類型的值初始化為0或false;所有的引用類型的值初始化為null。

使用靜態工廠方法創建Bean

使用靜態工廠方法創建Bean實例時,class屬性也必須指定,但此時class屬性并不是指定Bean實例的實現類,而是靜態工廠類,Spring通過該屬性知道由哪個工廠類來創建Bean實例。

除此之外,還需要使用factory-method屬性來指定靜態工廠方法,Spring將調用靜態工廠方法返回一個Bean實例,一旦獲得了指定Bean實例,Spring后面的處理步驟與采用普通方法創建Bean實例完全一樣。如果靜態工廠方法需要參數,則使用元素指定靜態工廠方法的參數。

調用實例工廠方法創建Bean

實例工廠方法與靜態工廠方法只有一個不同:調用靜態工廠方法只需使用工廠類即可,而調用實例工廠方法則需要工廠實例。使用實例工廠方法時,配置Bean實例的元素無須class屬性,配置實例工廠方法使用factory-bean指定工廠實例。

采用實例工廠方法創建Bean的元素時需要指定如下兩個屬性:

factory-bean: 該屬性的值為工廠Bean的id。

factory-method: 該屬性指定實例工廠的工廠方法。

若調用實例工廠方法時需要傳入參數,則使用元素確定參數值。

協調作用域不同步的Bean

當singleton作用域的Bean依賴于prototype作用域的Bean時,會產生不同步的現象,原因是因為當Spring容器初始化時,容器會預初始化容器中所有的singleton Bean,由于singleton Bean依賴于prototype Bean,因此Spring在初始化singleton Bean之前,會先創建prototypeBean——然后才創建singleton Bean,接下里將prototype Bean注入singleton Bean。

解決不同步的方法有兩種:

放棄依賴注入: singleton作用域的Bean每次需要prototype作用域的Bean時,主動向容器請求新的Bean實例,即可保證每次注入的prototype Bean實例都是最新的實例。

利用方法注入: 方法注入通常使用lookup方法注入,使用lookup方法注入可以讓Spring容器重寫容器中Bean的抽象或具體方法,返回查找容器中其他Bean的結果,被查找的Bean通常是一個non-singleton Bean。Spring通過使用JDK動態代理或cglib庫修改客戶端的二進制碼,從而實現上述要求。

建議采用第二種方法,使用方法注入。為了使用lookup方法注入,大致需要如下兩步:

將調用者Bean的實現類定義為抽象類,并定義一個抽象方法來獲取被依賴的Bean。

在元素中添加子元素讓Spring為調用者Bean的實現類實現指定的抽象方法。

兩種后處理器

Spring提供了兩種常用的后處理器:

Bean后處理器: 這種后處理器會對容器中Bean進行后處理,對Bean進行額外加強。

容器后處理器: 這種后處理器會對IoC容器進行后處理,用于增強容器功能。

Spring的"零配置"支持

搜索Bean類

Spring提供如下幾個Annotation來標注Spring Bean:

@Component: 標注一個普通的Spring Bean類

@Controller: 標注一個控制器組件類

@Service: 標注一個業務邏輯組件類

@Repository: 標注一個DAO組件類

在Spring配置文件中做如下配置,指定自動掃描的包:

Spring的AOP

使用AspectJ實現AOP

AspectJ是一個基于Java語言的AOP框架,提供了強大的AOP功能,其他很多AOP框架都借鑒或采納其中的一些思想。其主要包括兩個部分:一個部分定義了如何表達、定義AOP編程中的語法規范,通過這套語法規范,可以方便地用AOP來解決Java語言中存在的交叉關注點的問題;另一個部分是工具部分,包括編譯、調試工具等。

AOP實現可分為兩類:

靜態AOP實現: AOP框架在編譯階段對程序進行修改,即實現對目標類的增強,生成靜態的AOP代理類,以AspectJ為代表。

動態AOP實現: AOP框架在運行階段動態生成AOP代理,以實現對目標對象的增強,以Spring AOP為代表。

一般來說,靜態AOP實現具有較好的性能,但需要使用特殊的編譯器。動態AOP實現是純Java實現,因此無須特殊的編譯器,但是通常性能略差。

AOP的基本概念

關于面向切面編程的一些術語:

切面(Aspect): 切面用于組織多個Advice,Advice放在切面中定義。

連接點(Joinpoint): 程序執行過程中明確的點,如方法的調用,或者異常的拋出。在Spring AOP中,連接點總是方法的調用。

增強處理(Advice): AOP框架在特定的切入點執行的增強處理。處理有"around"、"before"和"after"等類型

切入點(Pointcut): 可以插入增強處理的連接點。簡而言之,當某個連接點滿足指定要求時,該連接點將被添加增強處理,該連接點也就變成了切入點。

依賴注入(Dependency Injection,DI)

Spring框架重要的思想核心是 ,在代碼編寫時使用接口定義類和其他對象的依賴關系,而非直接引用類的具體對象,在運行時通過配置,將引用的接口的具體實現類注入其中。

這里通過一個例子解釋什么叫代碼間的耦合,以及如何通過依賴注入DI降低代碼間的耦合度。

下面一個例子:我們需要創建一個類,它叫Worker,維護對象需要完成的工作(work),需要完成工作(執行doWork)。工作的類型有設計(Design),編碼(Coding),測試(Test),由于直接引用類的對象,它們的完成實現是各自定義的。這些工作定義如下:

public class Design(){

? ? public String doDesign(){

return "Doing work design";

? ? }

}

public class Coding(){

? ? public String doCoding(){

return "Doing work coding";

? ? }

}

public class Test(){

? ? public String doTest(){

return "Doing work test";

? ? }

}

通過直接引用對象來編寫Worker類,代碼如下:

//通過new創建工作對象的Worker

public class Worker(){

? ? Design design = new Design();

? ? public doWork(){

? System.out.println(design.doDesign());

? ? }

}

//運行

public class WorkMain{

public static void main(String[] args) throws Exception{

? ? ? ? Worker worker = new Worker();

? ? ? ? worker.doWork();

? ? ? ? //顯然不更改代碼它一次只能完成一種工作,有點蠢

? ? }

}

上述代碼有個明顯的問題:如果下一次Worker要去做Coding工作,則必須把引用的類,以及doWork方法全部改寫。這大大增加了代碼維護量。

DI的思路是,定義接口,編寫代碼時引用接口,而非具體的實現類,在運行中將具體的實現注入其中。

剛才所述三種工作,我們可以抽象出一個統一的接口IWork,定義統一的完成工作方法doWork

//接口定義

public interface IWork{

? ? //定義統一的接口方法

? ? public String doWork();

}

三種對象改為接口的實現。各自提供的完成工作方法統一為接口定義的方法的實現。

public class Design() implements IWork{

? //@Override,重寫接口方法注釋

? @Override

? public String doWork(){

return "Doing work design";

? ? }

}

public class Coding() implements IWork{

? ? @Override

? ? public String doWork(){

return "Doing work coding";

? ? }

}

public class Test() implements IWork{

? ? @Override

? ? public String doWork(){

return "Doing work test";

? ? }

}

引用這些類的Worker,改為引用接口,不明確定義該類的實現。

public class Worker(){

? ? //引用接口定義,而非具體的類。

? ? private IWork work;

? ? //定義設置方法,提供注入入口

? ? public void setWork(IWork work){

? ? ? this.work = work;

? ? }

? ? public doWork(){

? ? ? ? //直接調用接口的方法

? System.out.println(work.doWork());

? ? }

}

Work類中IWork接口的對象,在代碼運行時通過注入的方式定義。

//運行時注入實現

public class WorkMain{

public static void main(String[] args) throws Exception{

? ? ? ? Worker worker = new Worker();

? ? ? ? //在運行時候綁定實現的具體類,實際開發過程中,這些由Bean容器進行創建和托管

? ? ? ? woker.setWork(new Design());

? ? ? ? //完成design的工作

? ? ? ? worker.doWork();

? ? ? ? woker.setWork(new Coding());

? ? ? ? //完成coding的工作

? ? ? ? worker.doWork();

? ? ? ? woker.setWork(new Test());

? ? ? ? //完成test的工作

? ? ? ? worker.doWork();

? ? }

}

上面的例子是我們自己來注入對應的Bean。在Spring中框架中,提供了創建、維護,使用這些Bean的方法,我們可以使用Spring來配置運行時需要的類。下一節將說明如何在Spring中配置Bean。

Bean容器(container)管理及應用上下文(context),以及裝配和使用

Spring框架中,Bean組件通過Spring容器進行管理。它管理所有用到依賴注入的組件。

容器中Bean的創建有兩類創建方式:工廠模式創建,上下文創建。

工廠模式創建通過Baen工廠BeanFactory接口的實現進行創建。

在實際項目中,一般通過xml的形式注冊bean。對于項目工程而言,bean的定義為applicationContext-*.xml,*為自定義字符。

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

上下文模式使用ApplicationContext獲取。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容