Spring in action(Spring實戰) 第四版中文翻譯


第一部分 Spring核心
Spring提供了非常多功能,可是全部這些功能的基礎是是依賴注入(DI)和面向方面編程(AOP)。

第一章 Springing into action

本章包含:
Spring的bean容器
探索Spring的核心模塊
強大的Spring生態系統
Spring的新特性

       如今是java程序猿的好時代。在長達20年的發展過程中,java經歷了一些好時光,也經歷了一些壞時光。

雖然有一些粗糙的地方,比如applet,Enterprise javabean(EJB),Java數據對象(JDO),和無數的日志框架,Java已經成為非常多企業軟件的開發平台。Spring是java成為開發平台這個故事的重要組成部分。

       早些時候,Spring僅僅是重量級企業java框架的替代品,尤其是EJB。

和EJB相比,Spring提供了一個更輕量級、更精簡編程模型。它增強了POJO的能力,這樣的能力曾經僅僅在EJB或者其它java規范中才具備。

隨着時間的推移。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開發的?


為了應對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測試。


DI是怎樣工作的
不論什么重要的應用程序(比Hello World演示樣例更復雜的應用)都是由兩個或兩個以上的類相互協作,運行一些業務邏輯的。傳統方式,每一個對象負責獲取自己合作對象的引用(這是依賴關系)。這可能會導致代碼高度耦合而且難以測試。
比如,看下 Knight類,例如以下所看到的
代碼清單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、組件中到處充斥着與核心功能無關的代碼。一個方法用來將一個條目加入到地址簿,它應用僅僅關注怎樣加入這個功能。不應該關注這個功能是否安全或者事務等方面。


圖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文件:













免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM