第一部分 Spring核心
Spring提供了很是多功能,但是所有這些功能的基礎是是依賴注入(DI)和麪向方面編程(AOP)。
第一章 Springing into action
本章包含:
Spring的bean容器
探索Spring的核心模塊
強大的Spring生態系統
Spring的新特性
現在是java程序猿的好時代。在長達20年的發展過程當中,java經歷了一些好時光,也經歷了一些壞時光。
雖然有一些粗糙的地方,好比applet,Enterprise javabean(EJB),Java數據對象(JDO),和無數的日誌框架,Java已經成爲很是多企業軟件的開發平臺。Spring是java成爲開發平臺這個故事的重要組成部分。java
早些時候,Spring僅僅是重量級企業java框架的替代品,尤爲是EJB。和EJB相比,Spring提供了一個更輕量級、更精簡編程模型。它加強了POJO的能力,這樣的能力曾經僅僅在EJB或者其它java規範中才具有。spring
隨着時間的推移。EJB和J2EE也不斷髮展。EJB開始提供一個簡單的面向POJO的編程模型。
現在EJB使用的思想如依賴注入(DI)和麪向方面編程(AOP),可以說是來自Spring成功的靈感。數據庫
雖然J2EE(現在被稱爲JEE)的發展可以遇上Spring,但是Spring從未中止前進。Spring持續進步的領域,即便是現在,JEE剛剛纔開始探索,有的甚至是從未涉及。如移動開發,社交API的集成。NoSQL數據庫,雲計算和大數據。
正如我所說的。現在是java程序猿的好時代。
這本書是Spring的一個探索。
在本章。咱們從一個較高的高度看一下Spring,讓你初步感覺下Spring的味道。這一章將向你介紹Spring解決類型問題的好方法。本書其他部分並將環繞這種方法進行。編程
1.1 簡化Java開發
Spring是一個開源框架,最初由
Rod Johnson建立,並在其寫的《
Expert One-on-One: J2EE Design and Development》一書中作了描寫敘述。Spring建立的目的是解決企業應用開發的複雜性,使曾經僅僅能使用EJB解決的問題,現在可以使用普通javabean實現。但是Spring的功能並不侷限於server端的開發。不論什麼Java應用程序都可以受益於Spring的簡單、可測試性和鬆耦合等特性。儘管Spring經常使用bean和JavaBean來表示應用組件,但是這並不意味着一個Spring組件必須遵循JavaBean規範。
一個Spring組件可以是不論什麼類型的POJO。在本書中,我採用了一個鬆散的JavaBean的定義,是POJO的同義詞。設計模式
在本書中你會看到,Spring作了很是多事情。
但Spring提供的所有功能的根源是基於一些主要的想法,所有想法都關注於Spring的基本任務:Spring簡化Java開發。安全
這是一個大膽的聲明!很是多框架都聲明簡化某些東西。但是Spring旨在簡化Java開發這個普遍的主題。
這就需要不少其它的解釋。Spring是怎樣簡化Java開發的?app
爲了應對java的複雜性,Spring採用了四個關鍵策略:
- 輕量級的、微侵入性的POJO開發
- 使用DI實現鬆耦合、面向接口編程
- 使用切面和約定實現聲明式編程
- 使用切面和模板下降樣板代碼
Spring所作的差點兒所有事情都可以追溯到上述四個策略上。在本章的其他部分,我會具體介紹每一個策略。經過具體的樣例來展現Spring是怎樣實現其承諾的:簡化java開發。首先來看下Spring是怎樣實現輕量級的、微侵入性的POJO開發的。
1.1.1 釋放POJO的威力
假設你有幾年的java開發經驗,你可能會遇到這樣一些框架。他們要求你繼承框架的某個類或者實現框架的某個接口。侵入性編程模型的典型樣例是EJB2的無狀態Bean。除此以外,在Struts、WebWork、Tapestry的早期版本號中,和無數其它Java規範和框架中,都可以很是easy看到侵入性編程的樣例。
Spring儘量避免你的應用程序與其API耦合。Spring歷來不要求你實現其一個特定的接口或者繼承其一個特定的類。
相反,在基於Spring的應用程序中的類一般沒有跡象代表他們正在使用Spring。框架
在最壞的狀況下,一個類可能會使用Spring註解,但它仍然是一個POJO。模塊化
舉例說明,看如下代碼清單中的HelloWorldBean類:函數
代碼清單1.1 Spring對HelloWorldBean並不作不論什麼不合理的要求。
package com.habuma.spring;
public class HelloWorldBean {
public String sayHello() {
return "Hello World";
}
}
正如您可以看到的,這是一個簡單的,普通的Java類----一個POJO。沒有什麼特別的地方代表它是一個Spring組件。Spring的非入侵編程模型意味着一個類在Spring應用程序中具備的功能。在非Spring應用程序相同具有。POJO的形式很easy。但是其功能可以很強大。Spring添加POJO功能的一種方式是經過DI(譯者注:依賴注入)將它們組裝起來。讓咱們看一下DI是怎樣實現應用中對象之間的鬆耦合的。
1.1.2 依賴注入
依賴注入(
dependency injection)一詞聽起來可能有些嚇人,好像是一種複雜的編程技術或者設計模式。但事實證實,DI並不像聽起來的那樣複雜。經過在你的項目中應用DI,你會發現你的代碼將變得更簡單,更easy理解,更easy測試。
不論什麼重要的應用程序(比Hello World演示樣例更復雜的應用)都是由兩個或兩個以上的類相互協做,運行一些業務邏輯的。傳統方式,每個對象負責獲取本身合做對象的引用(這是依賴關係)。這可能會致使代碼高度耦合並且難以測試。
代碼清單1.2
package com.springinaction.knights;
public class DamselRescuingKnight implements Knight {
private RescueDamselQuest quest;
public DamselRescuingKnight() {
this.quest = new RescueDamselQuest();
}
public void embarkOnQuest() {
quest.embark();
}
}
正如你所示,DamselRescuingKnight(挽救少女的騎士。譯者注)類在構造函數中建立了本身的quest:RescueDamselQuest(挽救少女任務。譯者注)。
這使得DamselRescuingKnight類與RescueDamselQuest類緊密耦合,嚴重限制了騎士所能運行的任務。假設一個少女需要挽救,此騎士可以辦到。但是假設此時需要殺死一條龍或者需要一個圓桌,那麼這個騎士僅僅能袖手旁觀了。
更重要的是,爲
DamselRescuingKnight編寫一個單元測試將會很難。而且在單元測試中,你要能推斷
embarkOnQuest()調用了
embark()方法。
耦合是一把雙刃劍。
一方面,緊密耦合的代碼難以測試,很是難重用,難以理解,修復一個bug可能致使出現多個新的bug。還有一方面,必定的耦合又是必要的----全然非耦合的代碼作不了不論什麼事情。
爲了能作一些實用的事情,類之間需要了解彼此。
耦合是必需的,但應該當心地管理。
使用DI,系統中對象間的依賴有第三方來管理。對象不需要建立或獲取它們的依賴項。
如圖1.1所看到的,依賴在需要的時候被注入到對象中。
圖1.1 依賴注入意思是:將依賴給一個對象,而不是一個對象本身得到這些依賴
爲了說明這一點。讓咱們看一下如下代碼清單中的BraveKnight類:騎士不只勇敢,而且能夠完畢不論什麼形式的任務。
如代碼所看到的。與
DamselRescuingKnight類不一樣的是。
BraveKnight類沒有建立本身的quest。相反,在其構造函數中加入了一個quest參數。這樣的注入類型叫:構造函數注入。更重要的是。參數類型爲Quest,這是一個"任務"接口,所有的任務都實現這個接口。因此
BraveKnight可以完畢
RescueDamselQuest(挽救少女任務)
,
SlayDragonQuest(
殺死一條龍任務
)
,
MakeRoundTableRounderQuest(製造圓桌任務)三個任務,或者其它實現了
Quest接口的任務。
關鍵點在於
BraveKnight沒有與Quest接口的詳細實現耦合。其被安排什麼任務都沒問題,僅僅要任務實現了Quest接口就能夠。這是DI最基本的益處---解耦和。假設一個對象僅僅知道他所依賴的接口(而不是接口的詳細實現)。那麼依賴就可以有不一樣的實現。依賴對象可以不知道各個實現間的差異。
依賴替換最多見的使用方法之中的一個是在測試過程當中使用模擬實現來替換真實實現。由於緊密耦合,你沒法充分測試DamselRescuingKnight類,但是您可以很是easy地測試BraveKnight類,經過給它任務的模擬實現,如圖所看到的。
在這裏你使用模擬對象框架
Mockito建立了一個模擬實現的Quest。利用這個模擬對象,你建立了
BraveKnight對象。經過構造函數注入了模擬實現的Quest。調用過
embarkOnQuest()方法以後,使用
Mockito框架來驗證模擬實現對象Quest的embark()方式是否僅僅被調用了一次 。
向KNIGHT注入一個QUEST
使用BraveKnight類。你可以給一個騎士安排不論什麼任務,那麼怎樣安排任務給騎士呢?好比,若是你想讓
BraveKnight騎士運行任務:消滅一條龍。那麼先要建立
SlayDragonQuest任務,例如如下代碼清單:
如你所見,SlayDragonQuest類實現了Quest接口,使它適合BraveKnight。
您可能還注意到,類中輸出信息不是依靠system.out.println(),SlayDragonQuest採用了一種更通用的實現,經過其構造函數引用了PrintStream對象。
這裏的主要問題是,怎樣將SlayDragonQuest給BraveKnight?怎樣將PrintStream給SlayDragonQuest?
應用程序組件之間建立關聯的行爲一般被稱爲裝配。在Spring中,將組建裝配到一塊兒有很是多中方式,但一種常用的方式是經過XML裝配。
如下的代碼清單展現了一個簡單的Spring配置文件:knights.xml。文件裏將BraveKnight、SlayDragonQuest和PrintStream裝配到了一塊兒。
代碼中,BraveKnight和 SlayDragonQuest都被聲明爲Spring的一個bean。在BraveKnight中。經過構造函數注入了SlayDragonQuest Bean。同一時候,SlayDragonQuest Bean使用Spring表達式語言將System.out(PrintStream類型)注入到了SlayDragonQuest的構造函數中。
假設XML配置文件不適合你的口味,Spring還贊成你使用java高速配置。好比。如下是與XML配置一樣的基於java的配置:
無論你是使用基於xml的配置仍是使用基於java的配置,DI的優勢是相同的。儘管BraveKnight依賴一個Quest。但他不知道注入的Quest類型。不知道Quest來自哪裏。
SlayDragonQuest相同。僅僅有Spring,經過其配置,知道怎樣將所有的組件關聯到一塊兒。這可以實現在改變這些依賴項的時候,不需要改變依賴類。
這個樣例展現了一個簡單的方法來在Spring中裝配bean。
現在,不需要對不少其它的細節過多操心,第二章咱們將學習不少其它Spring配置。
咱們也會看看其它Bean裝配的方式,包含讓Spring本身主動發現bean並建立它們之間的關係的方式。
現在您已經聲明瞭BraveKnight和Quest之間的關係。你需要載入XML配置文件並啓動應用程序。
在Spring應用程序中。經過應用程序上下文載入bean定義和將他們裝配到一塊兒。Spring應用程序上下文全然負責應用程序中對象的建立和裝配。
Spring有幾個應用程序上下文的實現。各個實現之間的差異在於載入配置的方式不一樣。
knights.xml使用XML文件來聲明Bean,選擇比較合適的應用程序上下文是ClassPathXmlApplicationContext(對於基於java的配置,Spring所提供AnnotationConfigApplicationContext應用程序上下文.)。這個Spring上下文實現從應用程序的類路徑中的一個或多個位於XML文件裏載入Spring上下文。
如下代碼清單中的main( )方法使用了ClassPathXmlApplicationContext來載入knights.xml,並獲取Knight對象的引用。
main()方法中使用knights.xml文件建立了Spring應用程序上下文。接着,使用這個應用程序上下文做爲一個工廠來獲取id爲knight的Bean。利用Knight對象的引用調用其embarkOnQuest()方法。注意。這個Knight類並不知道其所運行的任務(Quest)類型是什麼。僅僅有
knights.xml文件知道其詳細類型。
如下讓咱們看看Spring簡化java開發的還有一個策略:利用切面實現聲明式編程。
1.1.3 使用切面
雖然DI能夠以鬆耦合的方式將軟件組件組合在一塊兒,但是面向切面編程(AOP)使你能夠捕獲應用中使用的可重用組件的功能。
AOP是一般被看作一種技術,一種促進軟件系統的關注點分離的技術。
軟件系統是由多個組件組成的,每個組件負責一個特定的功能。
但每每這些組件還承擔了其核心功能以外的責任。如日誌、事務管理和安全性等。
這些系統服務一般稱爲橫切關注點,因爲他們每每跨越一個系統中的多個組件。
在多個組件中傳播這些關注點,致使代碼引入了兩個級別的複雜性:
一、系統級別的關注點的代碼實現在多個組件中反覆出現。
這意味着,假設你要改動這些關注點的工做方式,您將需要改動多個組件。即便你將關注點抽取出一個單獨的模塊,在每個組件中調用這個組件的一個方法。那麼這種方法在多個組件中都會出現。
二、組件中處處充斥着與核心功能無關的代碼。一個方法用來將一個條目加入到地址簿,它應用僅僅關注怎樣加入這個功能。不該該關注這個功能是否安全或者事務等方面。
圖1.2說明了這樣的複雜性。左邊的業務對象都密切參與了右邊的系統服務。不只每個對象知道它在作日誌、安全和參與事務上下文等工做,而且他們也是本身負責運行這些服務。
AOP可以將這些服務模塊化,而後以聲明的方式將這些服務應用到對應的組件上面。這使組件,更有凝聚力,更專一於本身的詳細問題,全然不用關注可能涉及的系統服務。簡而言之,切面確保了pojo是簡單的。
可以將切面看作毯子。覆蓋應用程序的不少組件。
如圖1.3所看到的。在其核心,應用程序包括了實現業務功能的模塊。
利用AOP,您可以使用功能層覆蓋你的核心應用程序。
這些層可以靈活聲明的方式應用在您的應用程序,你的核心應用程序甚至不需要知道它們的存在。這是一個強大的概念,因爲其組織了安全、事務、日誌等關注點污染應用程序的核心業務邏輯。
爲了演示Spring中的切面是怎樣工做的,讓咱們重溫前面的騎士樣例,給其加入一個主要的Spring切面。
正如您可以看到的,
Minstrel是一個簡單的類,有兩個方法。
singBeforeQuest()方法將在騎士運行任務以前被調用,
singAfterQuest()方法將在騎士完畢任務以後被調用。在兩個方法中,
Minstrel都使用了注入的PrintStream來記錄騎士的事蹟。
將
Minstrel類應用到
BraveKnight
代碼中是很是easy的----可以注入到
BraveKnight中,對嗎?那就讓咱們使用這樣的方式。如下代碼清單顯示了把BraveKnight和Minstrel組織在一塊兒的第一次嘗試。
人們大可能是經過說書人描寫敘述的騎士的事蹟來了解騎士的。
若是你想使用說書人這個服務來記錄BraveKnight的一些事蹟。
如下的代碼清單列出了你可能會用到的說書人(Minstrel)類。
這應該足夠了。
現在你需要作的就是回到你的Spring配置文件,在當中聲明一個Minstrel Bean並將其注入到BraveKnight Bean的構造方法中。但是請等一下......
事情彷佛並不對。騎士的職責範圍中是否應該關注他的說書人呢?在我看來。說書人應該本身作本身的工做,而不該該被要求來作工做。總之。這是一個說書人的工做----用來記錄傳播騎士的事蹟。
爲何騎士老是要提醒說書人呢?
此外,由於騎士需要知道說書人,你被迫將Minstrel注入到BraveKnight中。這不只使BraveKnight代碼變得複雜。而且讓我產生了疑問:你是否會想要一個沒有說書人的騎士?假設Minstrel爲null。你是否應該爲這個樣例引入檢查null的業務邏輯呢?
簡單的BraveKnight類開始變得複雜。假設你處理
nullMinstrel的狀況。將會變得更加複雜。但是使用AOP,你可以經過聲明的方式實現說書人記錄騎士的事蹟。使騎士再也不直接調用Minstrel方法。
將
Minstrel變成一個切面,你所需要作的就是在Spring配置文件裏將其聲明爲一個切面。如下是更新後的knight.xml文件: