电竞比分网-中国电竞赛事及体育赛事平台

分享

SpringInAction讀書(shū)筆記

 lylf615 2010-08-26

序言
         軟件開(kāi)發(fā)者具有高超的手藝必須具備幾個(gè)特征。首先,他們必需具有良好的分析思考能力和問(wèn)題解決能力。開(kāi)發(fā)者的主要角色是創(chuàng)造軟件來(lái)解決業(yè)務(wù)問(wèn)題。這要求分析客戶(hù)需要和提出成功的具有創(chuàng)新性的解決方案。

         軟件開(kāi)發(fā)者還必須具有好奇心。軟件業(yè)中的開(kāi)發(fā)總是不斷更換目標(biāo),不斷進(jìn)化。新框架,新技術(shù),新語(yǔ)言和新方法學(xué)總是不斷地出現(xiàn)。每一樣都是新工具,需要我們掌握和加入到我們的工具箱中,使我們能夠更好更快地完成我們的工作。

         最后一個(gè)特征,也是最受珍愛(ài)的特征就是“惰性”。這種惰性刺激開(kāi)發(fā)者努力工作來(lái)尋找需要最少工作量的解決方案。正是有了這種好奇心,惰性和我們擁有的所有的分析能力,四年前我們倆開(kāi)始行動(dòng)起來(lái),尋找開(kāi)發(fā)軟件的新方法。

         當(dāng)今時(shí)代,Java社區(qū)的開(kāi)源軟件已經(jīng)達(dá)到了一個(gè)關(guān)鍵的數(shù)量。無(wú)數(shù)的開(kāi)源框架在Java世界遍地開(kāi)花。為決定選用一種框架,它必需能極大滿(mǎn)足我們的需要—它必需能滿(mǎn)足我們80%的需求。對(duì)于任何不能滿(mǎn)足我們的功能,框架必需容易地?cái)U(kuò)展來(lái)包含這些功能。擴(kuò)展并不意味著隨便組裝一下,這樣以后會(huì)感到惡心,而是意味著以?xún)?yōu)雅的方式來(lái)擴(kuò)展。這樣的要求有點(diǎn)過(guò)分,哈?

         我們立即采用的這樣的框架之一就是ant。從一開(kāi)始我們就可以看出,ant是由一個(gè)能理解我們構(gòu)建Java應(yīng)用的痛楚的開(kāi)發(fā)者創(chuàng)建的。從從選用ant的那一刻起,不再有javac,不再有CLASSPATH。所需的一切只是直觀(guān)的(即使有時(shí)候比較繁瑣)XML配置文件。生活(和構(gòu)建)變得簡(jiǎn)單。

         隨著我們的進(jìn)展,我們開(kāi)始采用越來(lái)越多的工具。Eclipse成為我們的IDE選擇。Log4J成為我們的(也是所有其他人的)缺省的日志工具。Lucene替代了我們的商業(yè)搜索解決方案。所有這些工具都滿(mǎn)足我們的準(zhǔn)則:滿(mǎn)足需求的同時(shí),易用,易理解,易擴(kuò)展。

         但仍然缺少一些東西,這些偉大的工具如ant和Eclipse,被設(shè)計(jì)用來(lái)輔助開(kāi)發(fā)軟件,或者滿(mǎn)足某一特定應(yīng)用需要,如使用Lucene進(jìn)行搜索和使用log4J來(lái)進(jìn)行記錄日志。他們都沒(méi)有解決企業(yè)應(yīng)用的核心:持久化,事務(wù)和與其他企業(yè)資源集成。

         直到去年我們發(fā)現(xiàn)了令人驚奇的兩大企業(yè)武器Spring和Hibernate。這兩個(gè)框架滿(mǎn)足了幾乎所有的中間層及數(shù)據(jù)層的需求。

         我們首先采用了Hibernate。它是最直觀(guān)的特性最多的ORM工具。但直到采用了Spring我們才使代碼看上去更加優(yōu)雅。使用Spring的控制反轉(zhuǎn)(Inversion of Control)我們可以?huà)仐壦械淖远x工廠(chǎng)及配置器。事實(shí)上這也是我們將Spring集成到我們應(yīng)用中的最初原因。它的wiring使我們的應(yīng)用程序更加合理化,移除了我們自制的解決方案。(每一個(gè)開(kāi)發(fā)者都會(huì)編寫(xiě)自己的框架,但是有時(shí)候必需得放棄)。

         我們很快發(fā)現(xiàn)了一個(gè)額外的好處,Spring提供了對(duì)Hibernate的很容易地繼承。這使我們不必自制對(duì)Hibernate的繼承類(lèi),可以直接使用Spring的支持。這引領(lǐng)我們走進(jìn)Spring對(duì)透明持久化的支持。

         如果你稍加注意的話(huà)你應(yīng)該發(fā)現(xiàn)了一個(gè)模式。越多地使用Spring,你就能發(fā)現(xiàn)越多的Spring的特性。而且我們發(fā)現(xiàn)的每一個(gè)特性都讓我們感受到樂(lè)趣。它的Web MVC框架在好幾個(gè)應(yīng)用中很好地工作。它的AOP支持在好幾個(gè)地方,特別是安全方面對(duì)我們很有幫助。它的JDBC支持對(duì)于小型程序很有幫助。對(duì)了,我們還用它來(lái)做日程。還有JNDI訪(fǎng)問(wèn)。還有email集成。談及能不能滿(mǎn)足開(kāi)發(fā)的需要,Spring可謂是雪中送炭。

         我們非常喜歡Spring,并且決定應(yīng)該寫(xiě)一本關(guān)于它的書(shū)。幸運(yùn)地是,我們之中已經(jīng)有人為manning寫(xiě)過(guò)一本書(shū),知道寫(xiě)書(shū)是怎么回事。很快“應(yīng)該寫(xiě)這樣一本書(shū)的人”成為了我們。我們寫(xiě)這本書(shū)的目的是傳遞Spring的精神。Spring框架是我們工作的樂(lè)趣—我們預(yù)測(cè)也會(huì)成為您的樂(lè)趣。最后,我們希望這本書(shū)能成為你到達(dá)終點(diǎn)的快樂(lè)的交通工具。

第一部分 Spring基礎(chǔ)
         介紹Spring框架的兩個(gè)核心特性:控制反轉(zhuǎn)和面向方面編程。

第一章 快速進(jìn)入Spring世界
         一切都起源于Bean。然而復(fù)雜的應(yīng)用通常需要諸如事務(wù)支持,安全和分布式計(jì)算之類(lèi)的服務(wù),JavaBean規(guī)范并沒(méi)有直接支持這些服務(wù)。因此Sun發(fā)布了EJB規(guī)范。盡管有很多基于EJB的成功的應(yīng)用,然而EJB并沒(méi)有取得其預(yù)期的目的:簡(jiǎn)化企業(yè)應(yīng)用開(kāi)發(fā)。新技術(shù),如AOP和IOC使JavaBean同樣可以具有EJB的強(qiáng)大功能,這就是Spring出現(xiàn)的背景。

1.1 為什么選擇Spring?
簡(jiǎn)而言之,Spring使開(kāi)發(fā)企業(yè)應(yīng)用更容易。

1.1.1 J2EE開(kāi)發(fā)者的一天
         為開(kāi)發(fā)一個(gè)組件(component)必需編寫(xiě)幾個(gè)類(lèi),還要為JavaBean創(chuàng)建部署描述子。為節(jié)省力氣,引入XDoclet。XDoclet是一個(gè)代碼生成工具,可以從一個(gè)源文件生成所有需要的EJB文件。為驗(yàn)證代碼邏輯的正確性,需要書(shū)寫(xiě)幾個(gè)測(cè)試用例,問(wèn)題是測(cè)試代碼必需運(yùn)行在EJB容器中。為解決這個(gè)問(wèn)題,創(chuàng)建一個(gè)Servlet來(lái)運(yùn)行這些測(cè)試用例。因?yàn)樗械腏2EE容器都支持Servlet,所以測(cè)試代碼可以與EJB運(yùn)行于同一個(gè)容器。但是如果測(cè)試用例沒(méi)有通過(guò),必需修改代碼,重新啟動(dòng)容器,再次測(cè)試,重啟容器的時(shí)間就被浪費(fèi)掉了。

1.1.2 Spring的承諾
         EJB之所以復(fù)雜是因?yàn)樗怯脕?lái)解決復(fù)雜問(wèn)題的,如分布式對(duì)象和遠(yuǎn)程事務(wù)。不幸的是,很多企業(yè)級(jí)項(xiàng)目并沒(méi)有達(dá)到這種的復(fù)雜程度,但仍然要承擔(dān)EJB的多個(gè)Java文件和部署描述子,以及重量級(jí)容器。使用EJB,無(wú)論你要解決的問(wèn)題是否復(fù)雜,你的應(yīng)用程序都很復(fù)雜,然而對(duì)于Spring來(lái)說(shuō),應(yīng)用程序的復(fù)雜程度是與要解決的問(wèn)題的復(fù)雜程度成正比的。Spring遵循的邏輯是:J2EE應(yīng)該易于使用,為保持這種邏輯,Spring的設(shè)計(jì)遵循以下信念:

<!--[if !supportLists]-->n  <!--[endif]-->好的設(shè)計(jì)比起底層的實(shí)現(xiàn)技術(shù)更重要。

<!--[if !supportLists]-->n  <!--[endif]-->通過(guò)接口松散耦合的JavaBean是很好的模型。

<!--[if !supportLists]-->n  <!--[endif]-->代碼應(yīng)該易于測(cè)試。

好的設(shè)計(jì)比起底層的實(shí)現(xiàn)技術(shù)更重要。

         不必為了使用EJB技術(shù)而使用它,真正需要它提供的服務(wù)才去使用它。

通過(guò)接口松散耦合的JavaBean是很好的模型

         使用Spring,JavaBean通過(guò)接口依賴(lài)于協(xié)作。你所需要做的就是創(chuàng)建彼此通過(guò)接口通信的類(lèi),其他的都交給Spring來(lái)做。

代碼應(yīng)該易于測(cè)試

         測(cè)試不需啟動(dòng)J2EE容器,因?yàn)槟銣y(cè)試的是POJO。

1.2 Spring是什么?
         Spring是一個(gè)開(kāi)源的框架,由Rod Johnson創(chuàng)建。Spring的創(chuàng)建用來(lái)解決企業(yè)級(jí)應(yīng)用開(kāi)發(fā)的復(fù)雜性。Spring使我們能夠使用普通的JavaBean來(lái)取得之前使用EJB達(dá)到的效果。然而,Spring的作用并不限于服務(wù)器端開(kāi)發(fā)。任何應(yīng)用程序都可得益于Spring的簡(jiǎn)單性,易測(cè)試性和松散耦合性。

         簡(jiǎn)單地講,Spring是一個(gè)輕量級(jí)的控制反轉(zhuǎn)和面向方面編程的框架。以下是這個(gè)描述的詳細(xì)解釋?zhuān)?/p>

<!--[if !supportLists]-->n  <!--[endif]-->輕量級(jí)。從兩個(gè)方面:大小和預(yù)處理。Spring發(fā)布版本的就是一個(gè)JAR包,大小就是1M多一點(diǎn)。經(jīng)Spring預(yù)處理的時(shí)間幾乎可以忽略。

<!--[if !supportLists]-->n  <!--[endif]-->控制反轉(zhuǎn)。Spring通過(guò)控制反轉(zhuǎn)來(lái)達(dá)到松散耦合。通過(guò)IOC,對(duì)象被動(dòng)地接受他們的依賴(lài),而不是主動(dòng)地查找和創(chuàng)建他們的依賴(lài)。

<!--[if !supportLists]-->n  <!--[endif]-->面向方面。Spring對(duì)AOP的支持使業(yè)務(wù)邏輯從系統(tǒng)服務(wù)中分離出來(lái)。

<!--[if !supportLists]-->n  <!--[endif]-->容器。從包容和管理應(yīng)用程序?qū)ο笊芷诘囊饬x上來(lái)講,Spring是一個(gè)容器。你可以配置你的每一個(gè)JavaBean如何被創(chuàng)建,以及他們之間的關(guān)聯(lián)。不應(yīng)當(dāng)把Spring同重量級(jí)的EJB容器混淆,因?yàn)镋JB容器是重量級(jí)的而且工作起來(lái)很笨重。

<!--[if !supportLists]-->n  <!--[endif]-->框架。Spring組合簡(jiǎn)單的JavaBean成復(fù)雜的的應(yīng)用程序。在Spring中應(yīng)用程序?qū)ο笸ǔMㄟ^(guò)XML文件聲明式地組合。Spring提供很多基礎(chǔ)的功能,如事務(wù)管理,持久化框架集成等,你要做的就是開(kāi)發(fā)業(yè)務(wù)邏輯。

1.2.1 Spring模塊
         Spring框架由七個(gè)良好定義的模塊組成。這些模塊為你提供了企業(yè)級(jí)應(yīng)用程序開(kāi)發(fā)的所有東西,但是你也不必全盤(pán)接受,你可以只選擇適合你的應(yīng)用程序的模塊而忽略其他模塊。所有的Spring模塊都建立在核心容器之上,容器定義JavaBeans如何被創(chuàng)建,配置和管理。核心容器的類(lèi)會(huì)被隱式地調(diào)用來(lái)配置你的應(yīng)用程序。

核心容器

         Spring的核心容器提供Spring框架的基礎(chǔ)功能。在這個(gè)容器中有一個(gè)BeanFactory,它是任何基于Spring的應(yīng)用程序的核心。BeanFactory是工廠(chǎng)模式的一個(gè)實(shí)現(xiàn),他應(yīng)用IOC將應(yīng)用程序配置和依賴(lài)說(shuō)明從實(shí)際的應(yīng)用程序代碼中分離出來(lái)。

應(yīng)用程序上下文模塊

         核心模塊的BeanFactory使Spring成為一個(gè)容器,而應(yīng)用程序上下文模塊使Spring成為一個(gè)框架。這個(gè)模塊擴(kuò)展了BeanFactory,增加了對(duì)I18N消息,應(yīng)用程序生命周期事件和驗(yàn)證的支持。

         此外,這個(gè)模塊提供了一些企業(yè)級(jí)服務(wù),如email,JNDI訪(fǎng)問(wèn),EJB集成,遠(yuǎn)程訪(fǎng)問(wèn)和計(jì)劃任務(wù)。還包括對(duì)模板框架如Velocity和FreeMaker集成的支持。

Spring的AOP模塊

         Spring在AOP模塊對(duì)AOP提供了豐富的支持。這個(gè)模塊提供了開(kāi)發(fā)自己的基于Spring的應(yīng)用程序的方面的基礎(chǔ)。

         為確保Spring和其他AOP框架之間的互操作性,Spring的很多對(duì)AOP的支持都是基于AOP Alliance定義的API。AOP Alliance是一個(gè)開(kāi)源的項(xiàng)目,它的目標(biāo)是通過(guò)定義組通用的接口和組件,提高對(duì)AOP的采用率和不同AOP實(shí)現(xiàn)之間的互操作性。

Spring模塊還為Spring引入了元數(shù)據(jù)編程。通過(guò)Spring的元數(shù)據(jù)支持,你可以向源代碼中加入標(biāo)注(Annotation)來(lái)指示在何處通過(guò)何種方式來(lái)應(yīng)用方面(Aspects)。

JDBC抽象和DAO模塊

         Spring的JDBC和DAO模塊使數(shù)據(jù)庫(kù)代碼更加整潔和簡(jiǎn)單,并且防止一些由于關(guān)閉數(shù)據(jù)庫(kù)資源引起失敗帶來(lái)的問(wèn)題。這個(gè)模塊還在由多個(gè)數(shù)據(jù)庫(kù)服務(wù)器給出的錯(cuò)誤消息之上建立了一層有意義的異常。

         另外,這個(gè)模塊使用了Spring的AOP模塊為Spring應(yīng)用程序中的對(duì)象提供了事務(wù)管理服務(wù)。

ORM集成模塊

         Spring為那些喜歡使用ORM功能的人提供了ORM模塊。Spring沒(méi)有試圖實(shí)現(xiàn)自己的ORM解決方案,只是為幾個(gè)流行的ORM框架,如Hibernate,JDO和iBATIS SQL Maps提供了外掛功能。Spring的事務(wù)管理支持這些ORM框架和JDBC。

Spring的Web模塊

         Web上下文模塊建立于應(yīng)用程序上下文模塊,提供了一個(gè)適合于基于Web的應(yīng)用的上下文。此外,這個(gè)模塊提供了對(duì)一些基于Web的任務(wù)的支持,如對(duì)于文件上傳的multipart請(qǐng)求的透明處理和將請(qǐng)求參數(shù)程序化綁定到業(yè)務(wù)對(duì)象。還包含對(duì)Struts的集成的支持。

SpringMVC框架

         Spring為構(gòu)建Web應(yīng)用程序帶來(lái)了一個(gè)完整特性的MVC框架。盡管Spring可以容易地與其他MVC框架如Struts集成,但是Spring的MVC框架使用IOC提供控制邏輯與業(yè)務(wù)邏輯的清晰分離。它允許你聲明式地將請(qǐng)求參數(shù)綁定到業(yè)務(wù)邏輯。還有就是Spring的MVC框架可以利用Spring的其他服務(wù),如I18N消息機(jī)制和驗(yàn)證機(jī)制。

1.3 快速上手
         讓基于Spring的應(yīng)用程序不同的是類(lèi)之間是如何配置和相互引入的。典型地,Spring應(yīng)用程序使用一個(gè)XML文件來(lái)描述如何配置類(lèi),這個(gè)XML文件叫做Spring配置文件。

         GreetingServiceImpl類(lèi)的greeting字段有兩個(gè)方法可以設(shè)置,通過(guò)構(gòu)造函數(shù)或?qū)傩缘膕etter方法。不明顯的是誰(shuí)來(lái)調(diào)用構(gòu)造函數(shù)或setter方法,答案是通過(guò)Spring配置文件讓Spring容器來(lái)調(diào)用。

         Spring配置文件的根元素是<beans>元素。<bean>元素告訴Spring容易一個(gè)類(lèi)的信息和如何配置這個(gè)類(lèi)。<bean>元素的子元素<property>元素用來(lái)設(shè)定類(lèi)的屬性。當(dāng)然也可以通過(guò)構(gòu)造函數(shù)的入?yún)?lái)設(shè)定屬性。

1.4 理解控制反轉(zhuǎn)IOC
         IoC處于Spring框架的核心。IoC聽(tīng)上去可能比較復(fù)雜,實(shí)際上,通過(guò)在項(xiàng)目中使用IoC,你會(huì)發(fā)現(xiàn)你的代碼變得更簡(jiǎn)單,更易于理解和易于測(cè)試。

1.4.1 注入依賴(lài)
         Martin Fowler為IoC造了一個(gè)更好的名字:依賴(lài)注入。

         任何稍大一點(diǎn)的應(yīng)用程序都由多于兩個(gè)類(lèi)組成,他們相互協(xié)作完成一些業(yè)務(wù)邏輯。通常每一個(gè)對(duì)象都負(fù)責(zé)取得與它協(xié)作的那些對(duì)象的引用。你會(huì)發(fā)現(xiàn),這會(huì)導(dǎo)致高耦合,難測(cè)試的代碼。

         通過(guò)應(yīng)用IoC,對(duì)象在創(chuàng)建的時(shí)候,通過(guò)調(diào)度系統(tǒng)中每一個(gè)對(duì)象的外部實(shí)體給予他們的依賴(lài)。即依賴(lài)被注入對(duì)象。即IoC的意思是關(guān)于對(duì)象如何取得他們協(xié)作的對(duì)象的依賴(lài)責(zé)任的反轉(zhuǎn)。

1.4.2 IoC實(shí)踐
         對(duì)于自己取得自己引用的對(duì)象的對(duì)象,無(wú)法單獨(dú)進(jìn)行測(cè)試。被引用的對(duì)象被間接測(cè)試,而且被引用的對(duì)象如果沒(méi)有通過(guò)測(cè)試,可能會(huì)影響調(diào)用對(duì)象的測(cè)試結(jié)果。

         問(wèn)題的所在是由于耦合。降低耦合的一個(gè)通用技術(shù)是將實(shí)現(xiàn)細(xì)節(jié)隱藏在接口后面,這樣可以交換具體實(shí)現(xiàn)而不影響客戶(hù)類(lèi)。但重要的是對(duì)象如何取得它引用的對(duì)象,如果是給予的方式,那么在測(cè)試用例中只要給予Mock對(duì)象,而在產(chǎn)品系統(tǒng)中給予真實(shí)的對(duì)象,就解決了不能單獨(dú)測(cè)試的問(wèn)題。

         這就是IoC要做的事情。

         創(chuàng)建應(yīng)用程序組件之間關(guān)聯(lián)的動(dòng)作被成為wiring,通常的方法是通過(guò)XML文件。

1.4.3 企業(yè)級(jí)應(yīng)用程序中的IoC
         在EJB程序中,需要自己從JNDI上下文環(huán)境中查找EJB Home,然后創(chuàng)建Service,使用Spring后,由Spring容器負(fù)責(zé)設(shè)置Service。

1.5 應(yīng)用面向方面編程
         面向方面編程使你能夠在應(yīng)用程序中以可復(fù)用組件的形式捕獲功能。

1.5.1 AOP簡(jiǎn)介
         AOP通常定義為一種編程技術(shù),它將軟件系統(tǒng)的不同關(guān)注方面分離。系統(tǒng)由多個(gè)組件組成,每一個(gè)組件負(fù)責(zé)一個(gè)特定的功能。像寫(xiě)log,事務(wù)管理和安全之類(lèi)的系統(tǒng)服務(wù)通常存在于一些核心職責(zé)是別的東西的組件。這些系統(tǒng)服務(wù)通常被稱(chēng)作cross-cutting關(guān)注方面,因?yàn)樗鼈冓呄蛴诖嬖谙到y(tǒng)的各個(gè)地方。

         將這些關(guān)注方面分散于系統(tǒng)的多個(gè)組件,會(huì)對(duì)代碼引入兩個(gè)級(jí)別的復(fù)雜性:

<!--[if !supportLists]-->n  <!--[endif]-->實(shí)現(xiàn)系統(tǒng)范圍關(guān)注方面的代碼,在多個(gè)組件間存在重復(fù)。這意味著如果你需要修改這些關(guān)注方面的話(huà),你需要修改多個(gè)組件。即使你將這些關(guān)注方面抽象到單獨(dú)的模塊,對(duì)組件的影響只是一個(gè)方法調(diào)用,這個(gè)方法調(diào)用也會(huì)在多個(gè)組件中重復(fù)。

<!--[if !supportLists]-->n  <!--[endif]-->組件中會(huì)散布一些與核心功能無(wú)關(guān)的代碼。

AOP能夠讓這些服務(wù)模塊化,然后以聲明的形式應(yīng)用于它們應(yīng)該影響的組件。這樣能夠使組件能夠更具有聚合性,專(zhuān)注于他們的關(guān)注方面,可以完全忽略需要涉及的系統(tǒng)服務(wù)。

通過(guò)AOP,可以用多層功能覆蓋核心的應(yīng)用。這些層可以靈活的聲明式地應(yīng)用于應(yīng)用程序,核心應(yīng)用程序甚至不知道他們的存在。

1.5.2 AOP實(shí)踐
         實(shí)現(xiàn)MethodBeforeService接口,可以在一個(gè)方法調(diào)用時(shí)做一些事情,而被調(diào)用的類(lèi)和方法甚至不知道被做的這些事情。

織補(bǔ)Aspect

         在Spring中,Aspect通過(guò)Spring的XML文件織入對(duì)象,同Bean被繞在一起的方式差不多。

1.5.3 企業(yè)級(jí)的AOP
         EJB可以通過(guò)部署描述子來(lái)聲明事務(wù)和安全策略。但是EJB比較復(fù)雜。

         盡管Spring的AOP可以用來(lái)分布于應(yīng)用程序核心邏輯各處的關(guān)注方面,但它的主要工作是作為Spring對(duì)聲明式事務(wù)支持的基礎(chǔ)。Spring具有幾個(gè)方面,使對(duì)JavaBeans聲明事務(wù)策略成為可能。而Acegi安全系統(tǒng)提供對(duì)JavaBeans的聲明式安全。同所有Spring配置一樣,事務(wù)和安全策略都在Spring配置文件中描述。

         使用Spring的TransactionProxyFactoryBean,使我們能夠?qū)却娴腸lass監(jiān)聽(tīng)函數(shù)調(diào)用和應(yīng)用事務(wù)上下文。

1.6 Spring的代替品
1.6.1 比較Spring與EJB
         選擇Spring與EJB并不是可以直接用誰(shuí)取代誰(shuí)。而且,你也不必要么選擇Spring,要么選擇EJB,Spring可以用來(lái)支持既有的EJBs。

EJB是一個(gè)標(biāo)準(zhǔn)

         EJB是有JCP定義的規(guī)范,作為標(biāo)準(zhǔn)具有一些重要的啟示:

<!--[if !supportLists]-->n  <!--[endif]-->廣泛的業(yè)界支持—有很多重要的廠(chǎng)商都支持這項(xiàng)技術(shù),如Sun,IBM,Oracle和BEA。這意味著很多年之內(nèi)EJB都會(huì)被支持和積極開(kāi)發(fā),這使很多公司感覺(jué)到選擇EJB作為J2EE框架比較安全。

<!--[if !supportLists]-->n  <!--[endif]-->廣泛的采用—EJB技術(shù)已經(jīng)被成千上萬(wàn)的公司部署。因此EJB是大部分J2EE開(kāi)發(fā)者的工具,擁有EJB技術(shù)更容易找到工作,公司使用EJB技術(shù)也更容易招聘到技術(shù)人員。

<!--[if !supportLists]-->n  <!--[endif]-->有工具支持—EJB規(guī)范是一個(gè)固定的東西,這更容易地使得廠(chǎng)商更快地生產(chǎn)工具來(lái)幫助開(kāi)發(fā)者創(chuàng)建EJB應(yīng)用程序。EJB工具具有更廣泛的選擇。

Spring和EJB的共同點(diǎn)

         作為J2EE容器,EJB和Spring都為開(kāi)發(fā)者開(kāi)發(fā)應(yīng)用程序提供了強(qiáng)大的特性。下表列出了兩個(gè)框架的共同特性和它們的實(shí)現(xiàn)比較。

特性
 EJB
 Spring
 
事務(wù)管理
 <!--[if !supportLists]-->n  <!--[endif]-->必需使用JTA事務(wù)管理。

<!--[if !supportLists]-->n  <!--[endif]-->支持跨越遠(yuǎn)程方法調(diào)用的事務(wù)。
 <!--[if !supportLists]-->n  <!--[endif]-->通過(guò)PlatformTransactionManager接口支持多事務(wù)環(huán)境,包括JTA,Hibernate,JDO和JDBC。

<!--[if !supportLists]-->n  <!--[endif]-->自身不支持分布式事務(wù)—必需與JTA事務(wù)管理器一起使用。
 
聲明式事務(wù)支持
 <!--[if !supportLists]-->n  <!--[endif]-->可以通過(guò)部署描述子聲明式地定義事務(wù)。

<!--[if !supportLists]-->n  <!--[endif]-->可以使用通配符*來(lái)針對(duì)每一個(gè)方法或每一個(gè)類(lèi)定義事務(wù)行為。

<!--[if !supportLists]-->n  <!--[endif]-->不能聲明式地定義回滾行為,必需程序?qū)崿F(xiàn)。
 <!--[if !supportLists]-->n  <!--[endif]-->可以通過(guò)Spring配置文件或類(lèi)的元數(shù)據(jù)聲明式地定義事務(wù)。

<!--[if !supportLists]-->n  <!--[endif]-->可以顯示地或通過(guò)正則表達(dá)式定義對(duì)哪些方法應(yīng)用事務(wù)行為。

<!--[if !supportLists]-->n  <!--[endif]-->可以針對(duì)每一個(gè)方法或每一個(gè)異常類(lèi)型聲明式地定義回滾行為。
 
持久化
 <!--[if !supportLists]-->n  <!--[endif]-->支持程序化的Bean管理方式的持久化和聲明式的容器管理持久化。
 <!--[if !supportLists]-->n  <!--[endif]-->提供集成多個(gè)持久化技術(shù)的框架,包括JDBC,Hibernate,JDO和iBATIS。
 
聲明式安全
 <!--[if !supportLists]-->n  <!--[endif]-->通過(guò)用戶(hù)和角色支持聲明式安全。用戶(hù)和角色的管理和實(shí)現(xiàn)是容器專(zhuān)有的。
 <!--[if !supportLists]-->n  <!--[endif]-->自身不具有安全的實(shí)現(xiàn)。

<!--[if !supportLists]-->n  <!--[endif]-->一個(gè)建立于Spring之上的開(kāi)源的安全框架Acegi,通過(guò)Spring配置文件或類(lèi)元數(shù)據(jù)提供聲明式安全。
 
分布計(jì)算
 <!--[if !supportLists]-->n  <!--[endif]-->提供容器管理的遠(yuǎn)程方法調(diào)用。
 <!--[if !supportLists]-->n  <!--[endif]-->通過(guò)RMI,JAX-RPC和Web服務(wù)提供遠(yuǎn)程調(diào)用的代理。
 

         對(duì)于大部分的J2EE項(xiàng)目,EJB和Spring都能滿(mǎn)足其技術(shù)需求。但是也有例外—你的應(yīng)用程序也許需要遠(yuǎn)程事務(wù)調(diào)用,如果是這樣的話(huà),選擇EJB更合適。但Spring提供了對(duì)JTA事務(wù)的集成,也能滿(mǎn)足。但如果你要找的是提供聲明式事務(wù)管理和靈活的持久化引擎的J2EE框架,Spring是最好的選擇。

EJB的復(fù)雜性

         EJB以下的復(fù)雜性使大家傾向于選擇輕量級(jí)的容器:

<!--[if !supportLists]-->n  <!--[endif]-->編寫(xiě)一個(gè)EJB太過(guò)于復(fù)雜—寫(xiě)一個(gè)EJB必需接觸至少四個(gè)文件:業(yè)務(wù)接口,home接口,bean實(shí)現(xiàn)和部署描述子。可能還牽扯到其他類(lèi),如utility類(lèi)和Value Object。相反Spring使你通過(guò)POJO定義你的實(shí)現(xiàn),和通過(guò)注入或AOP來(lái)纏繞任何額外的服務(wù)。

<!--[if !supportLists]-->n  <!--[endif]-->EJB具有侵入性—為使用EJB容器提供的服務(wù),則必需使用javax.ejb接口。這使組件代碼與EJB技術(shù)綁定,使組件很難用于EJB容器之外。Spring通常不要求組件實(shí)現(xiàn)、擴(kuò)展或使用任何特定于Spring的類(lèi)或接口,是組件更具有可復(fù)用性,即使沒(méi)有Spring的存在也能使用。

<!--[if !supportLists]-->n  <!--[endif]-->實(shí)體EJBs功能較弱—實(shí)體EJBs不如其他ORM工具特性多,也不夠靈活。Spring可以集成很多其他ORM框架。Value Object會(huì)導(dǎo)致重復(fù)的代碼,使用Spring和其他ORM工具,實(shí)體對(duì)象不與持久化機(jī)制耦合,可以傳遞于應(yīng)用程序的不同層。

1.6.2 考慮其他輕量級(jí)容器
         下表列出了IoC的類(lèi)型。

類(lèi)型
 名稱(chēng)
 描述
 
類(lèi)型1
 接口依賴(lài)
 為使容器管理依賴(lài),Beans必需實(shí)現(xiàn)特定的接口。
 
類(lèi)型2
 Setter注入
 依賴(lài)和屬性通過(guò)Beans的setter方法配置。
 
類(lèi)型3
 構(gòu)造函數(shù)注入
 依賴(lài)和屬性通過(guò)Beans的構(gòu)造函數(shù)配置。
 

下面看一下其他輕量級(jí)容器。

PicoContainer

         PicoContainer是一個(gè)小型的輕量級(jí)的容器,通過(guò)構(gòu)造函數(shù)和setter函數(shù)的形式提供IoC。有一個(gè)子項(xiàng)目NanoContainer通過(guò)XML和各種腳本語(yǔ)言提供對(duì)配置PicoContainer的支持。

         PicoContainer的一個(gè)局限是,對(duì)于任何一個(gè)類(lèi)型,在注冊(cè)表中只允許存在一個(gè)實(shí)例。

         PicoContainer只是一個(gè)容器,不提供其他Spring提供的強(qiáng)大功能,如AOP和第三方框架集成。

HiveMind

         HiveMind是一個(gè)相對(duì)較新的IoC容器。它也是通過(guò)構(gòu)造函數(shù)和setter函數(shù)來(lái)纏繞和配置服務(wù)。HiveMind允許用XML文件或它的簡(jiǎn)單的數(shù)據(jù)語(yǔ)言定義你的配置。

         HiveMind還通過(guò)Interceptors提供類(lèi)似AOP的特性。但是沒(méi)有Spring的AOP框架強(qiáng)大。

         它提供管理組件的框架,但不提供對(duì)其他技術(shù)的集成。

Avalon

         Avalon是第一批開(kāi)發(fā)出來(lái)的IoC容器之一。但設(shè)計(jì)中存在很多錯(cuò)誤。Avalon主要提供接口依賴(lài)的IoC。這使Avalon成為侵入式的框架。

1.6.3 Web框架
         Spring有自己的Web框架。先比較一下其他的Web框架。

Struts

         Struts可以看作是Web MVC框架的事實(shí)上的標(biāo)準(zhǔn)。

         最常使用的Struts類(lèi)是Action類(lèi)。Action是一個(gè)抽象類(lèi),因此你的所有處理輸入的類(lèi)都必需繼承這個(gè)類(lèi),相比之下Spring提供了Controller接口。

         另一個(gè)重要的區(qū)別是二者處理表單輸入的方式。通常情況下,當(dāng)用戶(hù)提交一個(gè)Web表單時(shí),進(jìn)來(lái)的數(shù)據(jù)對(duì)應(yīng)于你的應(yīng)用程序中的一個(gè)對(duì)象。為處理表單提交,Struts要求使用ActionForm類(lèi)處理傳入的參數(shù)。這意味這你必需創(chuàng)建一個(gè)類(lèi)來(lái)將表單提交映射到你的領(lǐng)域?qū)ο?。Spring允許你直接將表單提交映射到領(lǐng)域?qū)ο蟆?/p>

         Struts提供內(nèi)置的聲明式表單驗(yàn)證支持。這意味著你可以通過(guò)XML文件來(lái)定義驗(yàn)證傳入的表單數(shù)據(jù)的規(guī)則。這使驗(yàn)證與業(yè)務(wù)邏輯分離,也會(huì)導(dǎo)致一些笨拙和混亂。Spring不提供聲明式驗(yàn)證,不過(guò)你可以自己集成一個(gè)驗(yàn)證框架,如Jakarta Commons Validator。

         Spring可以集成Struts。

WebWork

         WebWork提供多視圖技術(shù)。WebWork與其他框架的最大區(qū)別是它為處理Web提交增加了一個(gè)抽象層。

         WebWork提供了一個(gè)Spring沒(méi)有提供的功能:Action鏈。它允許你將一個(gè)邏輯請(qǐng)求映射到一系列的Actions。

Tapestry

         Tapestry與之前提到的Web框架有很大區(qū)別。Taperstry不提供基于請(qǐng)求-響應(yīng)的Servlet機(jī)制的框架。它是一個(gè)從可復(fù)用組件創(chuàng)建Web應(yīng)用的框架。

         Tapestry背后的思想是,減輕開(kāi)發(fā)者思考Sessions屬性和URLs,而是以組件和方法的形式考慮Web應(yīng)用。

         Tapestry不是一個(gè)使用JSPs的框架,而是代替JSPs。

1.6.4 持久化框架
         Spring沒(méi)有內(nèi)置的持久化框架,Spring提供對(duì)Hibernate,JDO,OJB和iBATIS的集成點(diǎn)。Spring的JDBC和ORM框架工作于Spring的事務(wù)管理框架。

第二章 纏繞Beans
         任何稍具規(guī)模的應(yīng)用程序都是由多個(gè)組件組成,它們一起工作來(lái)完成一個(gè)業(yè)務(wù)目標(biāo)。這些組件必需意識(shí)到其他組件的存在,相互會(huì)話(huà)來(lái)完成工作。

         創(chuàng)建對(duì)象關(guān)聯(lián)的通常辦法會(huì)導(dǎo)致難以復(fù)用和進(jìn)行單元測(cè)試的代碼。

         在Spring中,組件本身不負(fù)責(zé)管理與其他組件的關(guān)聯(lián)。相反,對(duì)寫(xiě)作組件的引用是由容器給他們的。創(chuàng)建應(yīng)用程序組件之間關(guān)聯(lián)的動(dòng)作成為纏繞(Wiring)。

2.1 包含Beans
         你在為Spring框架配置beans的時(shí)候,你實(shí)際上是在向Spring容器發(fā)出指令。容器處于Spring框架的核心。Spring容器使用IoC來(lái)管理組成應(yīng)用程序的各個(gè)組件。Spring具有兩個(gè)不同類(lèi)型的容器。Bean工廠(chǎng)是最簡(jiǎn)單的容器(由org.springframework.beans.factory.BeanFactory接口定義),它為依賴(lài)注入提供簡(jiǎn)單的支持。應(yīng)用程序上下文建立于bean工廠(chǎng)的概念之上,它提供諸如從屬性文件解析文本消息之類(lèi)的應(yīng)用程序框架服務(wù),以及向感興趣的應(yīng)用程序事件監(jiān)聽(tīng)者發(fā)布應(yīng)用程序事件的能力。

2.1.1 介紹BeanFactory
       不同于其他工廠(chǎng)模式的實(shí)現(xiàn)只分派一種類(lèi)型的對(duì)象,這里的BeanFactory是一個(gè)通用的工廠(chǎng),可以分派很多類(lèi)型的Beans。

         Bean工廠(chǎng)知道應(yīng)用程序中的很多對(duì)象,在實(shí)例化對(duì)象時(shí),它能夠創(chuàng)建協(xié)作對(duì)象之間的關(guān)聯(lián)關(guān)系。這移除了Bean本身和它的客戶(hù)端的配置負(fù)擔(dān)。因此,當(dāng)Bean工廠(chǎng)分發(fā)對(duì)象時(shí),這些對(duì)象是已經(jīng)完全配置好的,已經(jīng)知道了他們協(xié)作的對(duì)象,已經(jīng)可以供使用。

         Spring中有很多BeanFactory的實(shí)現(xiàn),但最有用的是org.springframework.beans.factory.xmlBeanFactory,它基于包含在XML文件中的定義來(lái)加載Beans。

         為創(chuàng)建XmlBeanFactory,向構(gòu)造函數(shù)傳遞一個(gè)java.io.InputStream。這個(gè)InputStream向工廠(chǎng)提供XML文件。Beans以懶加載的方式加載到Beans工廠(chǎng)中,Bean工廠(chǎng)會(huì)立即加載Bean的定義,但只要當(dāng)這些Beans被使用的時(shí)候才會(huì)被實(shí)例化。

         為從BeanFactory獲取Bean,只要調(diào)用getBean()方法,向它傳遞你要獲取的bean的名稱(chēng)。當(dāng)調(diào)用getBean()時(shí),工廠(chǎng)會(huì)實(shí)例化Bean并開(kāi)始使用依賴(lài)注入來(lái)設(shè)置Bean的屬性。

2.1.2 使用應(yīng)用程序上下文
         ApplicationContext提供更多的東西:

<!--[if !supportLists]-->n  <!--[endif]-->提供解析文本消息的方法,包括對(duì)這些消息提供I18N支持。

<!--[if !supportLists]-->n  <!--[endif]-->提供加載文件資源,如圖片,的通用方法。

<!--[if !supportLists]-->n  <!--[endif]-->可以向注冊(cè)為監(jiān)聽(tīng)者的Beans發(fā)布事件。

因?yàn)锳pplicationContext的額外功能,幾乎在所有的應(yīng)用程序中都優(yōu)先使用ApplicationContext,而非BeanFactory。

ApplicationContext的實(shí)現(xiàn)有很多,而常用的有三個(gè):

<!--[if !supportLists]-->n  <!--[endif]-->ClassPathXmlApplicationContext—從在classpath中定位的XML文件加載一個(gè)上下文定義,將上下文定義文件作為classpath資源處理。

<!--[if !supportLists]-->n  <!--[endif]-->FileSystemXmlApplicationContext—從文件系統(tǒng)中的XML文件加載上下文定義。

<!--[if !supportLists]-->n  <!--[endif]-->XmlWebApplicationContext—從包含于Web應(yīng)用中的XML文件加載上下文定義。

獲取Bean的方式同BeanFactory,可以使用getBean()方法,因?yàn)锳pplicationContext繼承了BeanFactory接口。

應(yīng)用程序上下文與Bean工廠(chǎng)的另一個(gè)比較大的區(qū)別是他們加載單例Beans的方式。BeanFactory懶加載所有Beans,而ApplicationContext預(yù)先加載所有的單例Beans。

2.1.3 Bean的生命周期
         Bean可以使用之前BeanFactory執(zhí)行幾步設(shè)置操作:

<!--[if !supportLists]-->1.       <!--[endif]-->容器找到Bean的定義并實(shí)例化Bean。

<!--[if !supportLists]-->2.       <!--[endif]-->Spring使用依賴(lài)注入產(chǎn)生Bean定義中指定的所有的屬性。

<!--[if !supportLists]-->3.       <!--[endif]-->如果Bean實(shí)現(xiàn)了BeanNameAware接口,工廠(chǎng)調(diào)用setBeanName()方法,傳遞Bean的ID。

<!--[if !supportLists]-->4.       <!--[endif]-->如果Bean實(shí)現(xiàn)了BeanFactoryAware接口,工廠(chǎng)調(diào)用setBeanFactory方法,并傳遞工廠(chǎng)本身的實(shí)例。

<!--[if !supportLists]-->5.       <!--[endif]-->如果存在與Bean關(guān)聯(lián)的BeanPostProcessors,則調(diào)用他們的postProcessBeforeInitialization方法。

<!--[if !supportLists]-->6.       <!--[endif]-->如果指定了Bean的init方法,則調(diào)用這個(gè)方法。

<!--[if !supportLists]-->7.       <!--[endif]-->最后,如果存在與Bean關(guān)聯(lián)的BeanPostProcessors,則調(diào)用他們的postProcessAfterInitialization方法。

從這時(shí)候起,Bean可以被應(yīng)用程序使用,并一直存在與Bean工廠(chǎng)中,直到不需要它。有兩種方式將Bean從Bean工廠(chǎng)中移除。

<!--[if !supportLists]-->1.       <!--[endif]-->如果Bean實(shí)現(xiàn)了DisposableBean接口,調(diào)用destroy()方法。

<!--[if !supportLists]-->2.       <!--[endif]-->如果指定了自定義的destroy方法,則調(diào)用這個(gè)方法。

處在Spring應(yīng)用程序上下文中的Bean與BeanFactory中的唯一區(qū)別是,如果Bean實(shí)現(xiàn)了ApplicationContextAware接口,則調(diào)用它的setApplicationContext方法。

2.2基本的纏繞(Basic Wiring)
只需要一點(diǎn)XML基礎(chǔ)。

2.2.1使用XML纏繞
理論上Bean纏繞可以由任何配置源驅(qū)動(dòng),包括屬性文件,關(guān)系數(shù)據(jù)庫(kù),甚至是LDAP目錄。XML文件是最常用的方式。有幾個(gè)支持通過(guò)XML纏繞Beans的Spring容器:XmlBeanFactory,ClasspathXmlApplicationContext,F(xiàn)ileSystemXmlApplicationContext,XmlWebApplicationContext。

上下文定義文件的根節(jié)點(diǎn)是<beans>元素,這個(gè)元素具有一個(gè)或多個(gè)<bean>元素作為子元素。每一個(gè)<bean>元素都定義一個(gè)配置到Spring容器中的JavaBean。

2.2.2 增加一個(gè)Bean
         Spring中Bean的最簡(jiǎn)單的配置包括Bean的ID和Bean的完整類(lèi)名。

原型vs單例

         缺省情況下,所有Spring中的Beans都是單例的。如果每一次都要請(qǐng)求一個(gè)特定的實(shí)例的話(huà),可以定義一個(gè)原型Bean。將<bean>元素的singleton屬性設(shè)置為false就可以將Bean定義為原型Bean。

初始化與銷(xiāo)毀

         使用init-method屬性指定的方法在Bean實(shí)例化之后立即執(zhí)行。destroy-method屬性指定的方法在Bean從容器移除之前調(diào)用。Spring提供了兩個(gè)接口,可以執(zhí)行相同的功能:InitializingBean和DisposableBean。前者提供了一個(gè)方法afterPropertiesSet(),在Bean的所有屬性設(shè)置后執(zhí)行,后者提供的方法destroy()在Bean從容器中移除時(shí)調(diào)用。使用接口的好處是不需要配置,但是也會(huì)將Bean與Spring的API綁定起來(lái)。

2.2.3 通過(guò)setter方法注入依賴(lài)
         使用<bean>元素的<property>子元素來(lái)以setter方法進(jìn)行注入依賴(lài)。

簡(jiǎn)單的Bean的配置

         使用<property>元素的子元素<value>元素可以來(lái)給Bean設(shè)置原始類(lèi)型的屬性。原始類(lèi)型的屬性的設(shè)定無(wú)須指定類(lèi)型,Spring會(huì)自動(dòng)進(jìn)行類(lèi)型轉(zhuǎn)換。

引用其他Beans

         使用<property>元素的子元素<ref>元素來(lái)引用其他Beans。

內(nèi)部的Beans

         可以將<bean>元素直接嵌入到<property>元素。這種纏繞方式的缺點(diǎn)是被嵌入的元素不能在別處使用。而且影響XML文件的可讀性。

纏繞集合

         Spring支持很多種集合類(lèi)型作為Bean屬性,如下表所示。

XML
 類(lèi)型
 
<list>
 java.util.List, arrays
 
<set>
 java.util.Set
 
<map>
 java.util.Map
 
<props>
 java.util.Properties
 

         纏繞集合是使用上表中提到的XML元素,而不是使用<value>或<ref>。

纏繞lists和arrays

         List的元素可以是<value>,<ref>,<bean>或其他<list>。

纏繞集合

         集合可以保證元素的唯一性。

纏繞maps

         Map中的每一個(gè)記錄都用一個(gè)<entry>元素定義。<entry>的key屬性限定為只能是String類(lèi)型。

纏繞Properties

         使用<props>元素纏繞,每一個(gè)<props>元素的子元素都是用一個(gè)<prop>元素定義。

設(shè)定null值

         使用<null/>元素。

Setter注入的一個(gè)代替方式

         通過(guò)構(gòu)造函數(shù)注入。

2.2.4 通過(guò)構(gòu)造函數(shù)注入依賴(lài)
         使用<constructor-arg>元素來(lái)設(shè)定構(gòu)造函數(shù)的參數(shù)。<constructor-arg>的子元素可以使用<property>元素可以使用的任何子元素。

處理模糊的構(gòu)造函數(shù)參數(shù)

         如果出現(xiàn)模糊的情況,Spring會(huì)拋出org.springframework.beans.factory.UnsatisfiedDependencyException。有兩種方法來(lái)解決這個(gè)問(wèn)題,使用下標(biāo)或類(lèi)型。

         使用<constructor-arg>的index屬性指定參數(shù)的下標(biāo)。還可以使用type屬性指定參數(shù)的類(lèi)型。

如何選擇:使用構(gòu)造函數(shù)還是使用setter方法?

         這是一個(gè)有爭(zhēng)議的問(wèn)題。

         支持使用構(gòu)造函數(shù)注入的理由:

<!--[if !supportLists]-->n  <!--[endif]-->強(qiáng)制執(zhí)行一個(gè)強(qiáng)依賴(lài)協(xié)議。簡(jiǎn)單的說(shuō),Bean如果沒(méi)得到所有的依賴(lài)就不會(huì)被實(shí)例化。

<!--[if !supportLists]-->n  <!--[endif]-->既然所有的依賴(lài)都可以通過(guò)構(gòu)造函數(shù)設(shè)定,使用setter方法比較多余。

<!--[if !supportLists]-->n  <!--[endif]-->只允許通過(guò)構(gòu)造函數(shù)設(shè)定函數(shù),可以讓這些屬性不可更改(immutable)。

支持setter方法注入依賴(lài)的理由:

<!--[if !supportLists]-->n  <!--[endif]-->如果依賴(lài)比較多,構(gòu)造函數(shù)的參數(shù)會(huì)比較長(zhǎng)。

<!--[if !supportLists]-->n  <!--[endif]-->如果構(gòu)造可用的對(duì)象的方法有多個(gè)的話(huà),就會(huì)有多個(gè)構(gòu)造函數(shù)。

<!--[if !supportLists]-->n  <!--[endif]-->如果構(gòu)造函數(shù)的多個(gè)參數(shù)的類(lèi)型一樣的話(huà),很難確定參數(shù)的目的是什么。

<!--[if !supportLists]-->n  <!--[endif]-->構(gòu)造函數(shù)注入使自身不具有好的集成性。

2.3 自動(dòng)纏繞(autowiring)
         可以通過(guò)設(shè)定<bean>元素的autowire屬性來(lái)自動(dòng)纏繞。

         有四種類(lèi)型的自動(dòng)纏繞:

<!--[if !supportLists]-->n  <!--[endif]-->byName—在容器中查找與指定的名稱(chēng)相同的Bean。

<!--[if !supportLists]-->n  <!--[endif]-->byType—在容器中查找一個(gè)唯一的與指定類(lèi)型相同的Bean。如果沒(méi)有找到則不纏繞,如果找到多個(gè),拋出org.springframework.beans.factory.UnsatisfiedDependencyException。

<!--[if !supportLists]-->n  <!--[endif]-->constructor—在容器中查找與被纏繞的Bean的構(gòu)造函數(shù)匹配的Bean,使用指定的構(gòu)造函數(shù)的參數(shù)。

<!--[if !supportLists]-->n  <!--[endif]-->autodetect—試圖先使用constructor,然后使用byType方法。

2.3.1 處理自動(dòng)纏繞的模糊性
         如果使用byType或constructor自動(dòng)纏繞,可能會(huì)找到多個(gè)Bean。Spring在這種情況下會(huì)拋出異常。

2.3.2 混合使用自動(dòng)纏繞和顯式纏繞
         對(duì)一個(gè)Bean的多個(gè)屬性,可以設(shè)定自動(dòng)纏繞屬性,再顯示纏繞需要覆蓋的屬性。

2.3.3 缺省自動(dòng)纏繞
         設(shè)定<beans>的default-autowire=”byName”,會(huì)對(duì)所有的Beans缺省byName自動(dòng)纏繞。

2.3.4 自動(dòng)纏繞還是不自動(dòng)纏繞
         自動(dòng)纏繞可能會(huì)引起一些問(wèn)題。例如,屬性改名會(huì)導(dǎo)致纏繞不成功。

2.4 使用Spring的特殊Beans
         通過(guò)實(shí)現(xiàn)特定的接口,可以使你的Beans變得特殊,成為Spring框架的一部分,你可以配置Beans:

<!--[if !supportLists]-->n  <!--[endif]-->可以通過(guò)后置處理配置文件來(lái)參與Bean和Bean的工廠(chǎng)的生命周期。

<!--[if !supportLists]-->n  <!--[endif]-->從外部屬性文件加載配置信息。

<!--[if !supportLists]-->n  <!--[endif]-->改變Spring的依賴(lài)注入,在配置Bean屬性的時(shí)候,自動(dòng)將String類(lèi)型轉(zhuǎn)換成其他類(lèi)型。

<!--[if !supportLists]-->n  <!--[endif]-->加載文本消息,包括國(guó)際化的消息。

<!--[if !supportLists]-->n  <!--[endif]-->監(jiān)聽(tīng)或響應(yīng)由其他Beans或容器發(fā)布的應(yīng)用程序事件。

<!--[if !supportLists]-->n  <!--[endif]-->使其意識(shí)到自己在Spring容器中的身份。

2.4.1 后置處理Beans
         BeanPostProcessor給你兩個(gè)機(jī)會(huì)來(lái)在它被創(chuàng)建和纏繞之后來(lái)更改它。postProcessBeforeInitialization方法和postProcessAfterInitialization。

書(shū)寫(xiě)B(tài)ean后置處理器

         定義一個(gè)類(lèi)實(shí)現(xiàn)BeanPostProcessor接口。

注冊(cè)Bean后置處理器

         如果應(yīng)用程序運(yùn)行于Bean工廠(chǎng),則需要使用程序來(lái)進(jìn)行注冊(cè),調(diào)用工廠(chǎng)的addBeanPostProcessor方法,將上面定義的類(lèi)作為參數(shù)傳入。如果應(yīng)用程序運(yùn)行于應(yīng)用程序上下文,只需要將定義的類(lèi)注冊(cè)為一個(gè)Bean。容器會(huì)將其識(shí)別為后置處理器并應(yīng)用于所有的Beans。

Spring自帶的Bean后置處理器

         例如,ApplicationContextAwareProcessor對(duì)所有實(shí)現(xiàn)ApplicationContextAware接口的Beans設(shè)置應(yīng)用程序上下文。

         DefaultAdvisorAutoProxyCreator基于容器中所有的候選advisors創(chuàng)建AOP代理。

2.4.2 后置處理Bean工廠(chǎng)
         BeanFactoryPostProcessor后置處理Bean工廠(chǎng),在Bean工廠(chǎng)加載Bean定義之后,所有Beans被實(shí)例化之前。postProcessBeanFactory方法。如在應(yīng)用程序上下文中使用,則將其注冊(cè)為正常的Bean就行了,容器自動(dòng)將其注冊(cè)為BeanFactoryPostProcessor。不能在Bean工廠(chǎng)容器中使用。

         有兩個(gè)比較有用的實(shí)現(xiàn)。PropertyPlaceHolderConfigurer從一個(gè)或多個(gè)外部屬性文件中加載屬性,并用這些屬性填充Bean纏繞XML文件中的占位符變量。CustomEditorConfigurer讓你注冊(cè)java.beans.PropertyEditor來(lái)將屬性纏繞值翻譯成其他屬性值。

2.4.3 使配置外部化
         可以將整個(gè)應(yīng)用程序都配置在一個(gè)Bean纏繞文件中,但有時(shí)將配置提取成多個(gè)文件更有益。

         如果使用ApplicationContext作為Spring容器的話(huà),外部化屬性是很容易的??梢允褂肞ropertyPlaceHolderConfigurer來(lái)告訴Spring從外部屬性文件來(lái)加載特定的配置。如果你的配置分解為多個(gè)外部屬性文件的話(huà),使用PropertyPlaceHolderConfigurer的locations屬性來(lái)設(shè)置<list>元素。

2.4.4 定制屬性編輯器
         將String值纏繞到其他類(lèi)型的屬性。

         Spring自帶了幾個(gè)自定義編輯器:

<!--[if !supportLists]-->n  <!--[endif]-->URLEditor—將String對(duì)象纏繞到URL對(duì)象。

<!--[if !supportLists]-->n  <!--[endif]-->ClassEditor

<!--[if !supportLists]-->n  <!--[endif]-->CustomDateEditor

<!--[if !supportLists]-->n  <!--[endif]-->FileEditor

<!--[if !supportLists]-->n  <!--[endif]-->LocaleEditor

<!--[if !supportLists]-->n  <!--[endif]-->StringArrayPropertyEditor

<!--[if !supportLists]-->n  <!--[endif]-->StringTrimmerEditor

2.4.5 解析文本消息
         Spring的ApplicationContext支持參數(shù)化的消息,它通過(guò)MessageSource接口使參數(shù)化消息對(duì)容器可用。Spring自帶了一個(gè)MessageSource的實(shí)現(xiàn)ResourceBundleMessageSource,它簡(jiǎn)單地使用Java的java.util.ResourceBundle來(lái)解析消息。配置文件中的bean必須命名為messageSource,否則不能被ApplicationContext識(shí)別。

         一般情況下,通過(guò)ApplicationContext的getMessage方法來(lái)訪(fǎng)問(wèn)消息,也可以在JSP中使用<spring:message>標(biāo)簽來(lái)訪(fǎng)問(wèn)。

2.4.6 監(jiān)聽(tīng)事件
         在應(yīng)用程序的生命周期中,ApplciationContext會(huì)發(fā)布很多事件,來(lái)告訴監(jiān)聽(tīng)者應(yīng)用程序正在進(jìn)行什么工作。這些事件都是抽象類(lèi)org.springframework.context.ApplicationEvent的子類(lèi),其中的三個(gè)事件為:

<!--[if !supportLists]-->n  <!--[endif]-->ContextClosedEvent—當(dāng)應(yīng)用程序上下文關(guān)閉的時(shí)候發(fā)布。

<!--[if !supportLists]-->n  <!--[endif]-->ContextRefreshedEvent—當(dāng)應(yīng)用程序上下文初始化或刷新的時(shí)候發(fā)布。

<!--[if !supportLists]-->n  <!--[endif]-->RequestHandledEvent—當(dāng)請(qǐng)求已經(jīng)被處理的時(shí)候,在Web應(yīng)用上下文環(huán)境內(nèi)發(fā)布。

如果你想要Bean對(duì)應(yīng)用程序事件作出響應(yīng),只需實(shí)現(xiàn)org.springframework.context.ApplicationListener接口。

2.4.7 發(fā)布事件
         可以讓?xiě)?yīng)用程序發(fā)布自己的事件。實(shí)現(xiàn)ApplicationEvent接口來(lái)定義事件,使用ApplicationContext的publishEvent方法來(lái)發(fā)布事件。

2.4.8 讓Beans有意識(shí)
         通過(guò)實(shí)現(xiàn)BeanNameAware接口,BeanFactoryAware接口和ApplicationContextAware接口,Beans可以意識(shí)到他們的名字,他們的BeanFactory以及他們的ApplicationContext的存在。需要警告你的是,實(shí)現(xiàn)了這些接口之后Beans就會(huì)與Spring耦合。

知道你是誰(shuí)

         BeanNameAware的setBeanName()方法。

知道你在哪生存

         Spring的BeanFactoryAware和ApplicationContextAware接口讓Beans意識(shí)到容器的存在。

第三章 創(chuàng)建方面(Aspects)
3.1 介紹AOP
3.1.1 定義AOP術(shù)語(yǔ)
方面(Aspect)

         一個(gè)方面(Aspect)是你實(shí)現(xiàn)的一個(gè)橫切的功能。它是你模塊化的應(yīng)用程序的一個(gè)方面或領(lǐng)域。最常見(jiàn)的例子是寫(xiě)日志(logging)。

連接點(diǎn)(joinpoint)

         一個(gè)連接點(diǎn)是應(yīng)用程序執(zhí)行過(guò)程中的一個(gè)點(diǎn),在這個(gè)點(diǎn)可以插入一個(gè)方面(Aspect)。這個(gè)點(diǎn)可以是調(diào)用方法,可以是拋出異常,也可以是修改一個(gè)字段。在這些點(diǎn),方面的代碼可以插入到應(yīng)用程序的正常流程來(lái)增加新的行為。

修訂(Advice)

         Advice是方面的實(shí)際實(shí)現(xiàn)。以logging為例,logging advice包含包含實(shí)際實(shí)現(xiàn)loggin的代碼。Advice在jointpoints插入到應(yīng)用程序中。

切入點(diǎn)(Pointcut)

         切入點(diǎn)定義應(yīng)該應(yīng)用何種連接點(diǎn)Advice。Advice可以應(yīng)用于AOP框架支持的任何連接點(diǎn)。

引入(Introduction)

         Introduction允許你向既存的類(lèi)增加方法或?qū)傩浴?/p>

目標(biāo)(Target)

         Target是被修訂的類(lèi)??梢允悄銓?xiě)的類(lèi),也可以是第三方的類(lèi)。

代理(Proxy)

         代理是向目標(biāo)對(duì)象應(yīng)用修訂后創(chuàng)建的對(duì)象。就客戶(hù)端對(duì)象而言,目標(biāo)對(duì)象和代理對(duì)象是相同的。

編織(Weaving)

         編織是向目標(biāo)對(duì)象應(yīng)用方面來(lái)創(chuàng)建一個(gè)新的代理的對(duì)象的過(guò)程。方面在指定的連接點(diǎn)被織入目標(biāo)對(duì)象。編織可以發(fā)生在目標(biāo)類(lèi)的生命周期的多個(gè)點(diǎn):

<!--[if !supportLists]-->n  <!--[endif]-->編譯期—方面在目標(biāo)類(lèi)被編譯時(shí)織入。這需要一個(gè)特殊的編譯器。

<!--[if !supportLists]-->n  <!--[endif]-->類(lèi)加載期—方面在目標(biāo)類(lèi)被加載進(jìn)JVM時(shí)織入。這需要一個(gè)特殊的ClassLoader來(lái)增強(qiáng)目標(biāo)類(lèi)的字節(jié)碼。

<!--[if !supportLists]-->n  <!--[endif]-->運(yùn)行時(shí)—方面在應(yīng)用程序執(zhí)行的某個(gè)時(shí)刻被織入。

Advice包含需要應(yīng)用的橫切行為。

3.1.2 Spring的AOP實(shí)現(xiàn)
Spring的修訂是使用Java編寫(xiě)

         切入點(diǎn)通常以XML形式寫(xiě)在Spring配置文件中。其他框架,如AspectJ,要求必需使用一種特殊的語(yǔ)法來(lái)定義方面和切入點(diǎn)。

Spring在運(yùn)行時(shí)修訂對(duì)象

         Spring只有在代理Bean在應(yīng)用程序中被需要時(shí)才創(chuàng)建代理對(duì)象。Spring有兩種方式可以產(chǎn)生代理類(lèi)。如果你的目標(biāo)對(duì)象實(shí)現(xiàn)了暴露了必要方法的接口,則Spring使用JDK的java.lang.reflect.Proxy接口。這個(gè)類(lèi)允許Spring動(dòng)態(tài)地創(chuàng)建新類(lèi)來(lái)實(shí)現(xiàn)必要的接口,織入任何修訂,和代理任何從這些接口的對(duì)目標(biāo)類(lèi)的方法調(diào)用。如果你的目標(biāo)類(lèi)不實(shí)現(xiàn)接口,Spring使用CGLIB來(lái)產(chǎn)生目標(biāo)類(lèi)的一個(gè)子類(lèi)。創(chuàng)建子類(lèi)時(shí),Spring織入修訂,并使用子類(lèi)代理所有到目標(biāo)類(lèi)的調(diào)用。

Spring實(shí)現(xiàn)AOP Alliance接口

         AOP Alliance是一個(gè)都對(duì)用Java實(shí)現(xiàn)AOP感興趣的多方的聯(lián)合項(xiàng)目。他們的目標(biāo)是標(biāo)準(zhǔn)化Java AOP接口來(lái)提供不同Java的AOP實(shí)現(xiàn)的互操作性。這意味著實(shí)現(xiàn)他們接口的AOP修訂會(huì)在任何與AOP Alliance兼容的框架中復(fù)用。

Spring只提供方法連接點(diǎn)

         這會(huì)有礙于創(chuàng)建細(xì)粒度的修訂,如監(jiān)聽(tīng)對(duì)對(duì)象字段的更新。

3.2 創(chuàng)建修訂(Advice)
         因?yàn)樵诜椒ǖ膱?zhí)行過(guò)程中有多個(gè)點(diǎn)Spring可以織入修訂,所以有多種類(lèi)型的修訂。下表列出了Spring提供的修訂類(lèi)型。

修訂類(lèi)型
 接口
 描述
 
Around
 org.aopalliance.intercept.MethodInterceptor
 攔截對(duì)目標(biāo)函數(shù)的調(diào)用
 
Before
 org.springframework.aop.BeforeAdvice
 在目標(biāo)方法被調(diào)用之前調(diào)用
 
After
 Org.springframework.aop.AfterReturningAdvice
 在目標(biāo)方法返回之后調(diào)用
 
Throws
 Org.springframework.aop.ThrowsAdvice
 當(dāng)目標(biāo)方法拋出異常時(shí)調(diào)用
 

3.2.1 Before修訂
         MethodBeforeAdvice提供目標(biāo)方法,傳遞給目標(biāo)方法的參數(shù),以及方法調(diào)用的目標(biāo)對(duì)象的訪(fǎng)問(wèn)。將修訂應(yīng)用于目標(biāo)對(duì)象是通過(guò)Spring的配置文件。

3.2.2 After修訂
         AfterReturningAdvice還可以訪(fǎng)問(wèn)被修訂方法的返回值。

3.2.3 Around修訂
         MethodInterceptor與前兩者有兩個(gè)不同的地方。首先,MethodInterceptor控制目標(biāo)方法是否被實(shí)際調(diào)用。另一方面,它控制返回什么對(duì)象。

3.2.4 Throws修訂
         ThrowsAdvice的afterThrowing方法具有兩個(gè)簽名。Throws不能捕獲和處理異常,只能拋出其他異常。

3.2.5 引入(Introduction)修訂
         引入修訂為目標(biāo)對(duì)象引入新方法或?qū)傩浴?/p>

3.3 定義切入點(diǎn)
         Spring的切入點(diǎn)允許我們定義我們的修訂在何處以一種 靈活的方式織入我們的類(lèi)。

3.3.1 在Spring中定義切入點(diǎn)
         Spring根據(jù)被修訂的類(lèi)和方法來(lái)定義切入點(diǎn)。修訂(Advice)被織入目標(biāo)類(lèi)和它的方法,基于它們的特征,如類(lèi)名和方法簽名。Spring的切入點(diǎn)框架的核心接口是Pointcut接口。ClassFilter接口確定滿(mǎn)足修訂條件的類(lèi)。實(shí)現(xiàn)此接口的類(lèi)確定作為參數(shù)傳入的類(lèi)是否應(yīng)該被修訂。

         ClassFilter是根據(jù)類(lèi)來(lái)過(guò)濾方面,而MathodFilter是根據(jù)方法來(lái)過(guò)濾。

3.3.2 理解修訂(advisors)
         大部分的方面是由定義方面的行為的修訂(Advice)和定義方面應(yīng)該在何處執(zhí)行的切入點(diǎn)的組合。Spring認(rèn)識(shí)到這一點(diǎn)并且提供Advisor來(lái)組合二者,確切的說(shuō),使用PointcutAdvisor來(lái)做到這一點(diǎn)。大部分的Spring的內(nèi)置切入點(diǎn)都有一個(gè)對(duì)應(yīng)的PointcutAdvisor。

3.3.3 使用Spring的靜態(tài)切入點(diǎn)
         我們更傾向于使用靜態(tài)切入點(diǎn)因?yàn)樗鼈儓?zhí)行效率更高,因?yàn)樗麄冎挥?jì)算一次(創(chuàng)建代理的時(shí)候),而不是每次運(yùn)行時(shí)調(diào)用都計(jì)算。Spring提供了一個(gè)方便的超類(lèi)來(lái)創(chuàng)建靜態(tài)切入點(diǎn):StaticMethodMatcherPointcut,如果你想創(chuàng)建自定義的切入點(diǎn),只要實(shí)現(xiàn)這個(gè)接口的isMatch方法。但大部分時(shí)候,使用Spring自帶的切入點(diǎn)就夠用了。

NameMatchMethodPointcut

         根據(jù)提供的名稱(chēng)來(lái)匹配方法名,提供的名稱(chēng)可以帶通配符。

正則表達(dá)式切入點(diǎn)

         RegexpMethodPointcut使你可以使用正則表達(dá)式來(lái)定義切入點(diǎn)。

3.3.4 使用動(dòng)態(tài)切入點(diǎn)
         也存在切入點(diǎn)需要計(jì)算運(yùn)行時(shí)屬性的情況。Spring提供一個(gè)內(nèi)置的動(dòng)態(tài)的切入點(diǎn):ControlFlowPointcut。這個(gè)切入點(diǎn)根據(jù)當(dāng)前線(xiàn)程的調(diào)用堆棧的信息來(lái)進(jìn)行匹配。使用這個(gè)切入點(diǎn)會(huì)影響性能。

3.3.5 切入點(diǎn)操作
         Spring提供對(duì)切入點(diǎn)的操作如合集和交集操作來(lái)創(chuàng)建新的切入點(diǎn)。Spring提供兩個(gè)類(lèi)來(lái)創(chuàng)建這些類(lèi)型的切入點(diǎn)。第一個(gè)類(lèi)是ComposablePointcut??梢詫?duì)既存的ComposablePointcut,對(duì)象和Pointcut,MethodMatcher和ClassFilter對(duì)象創(chuàng)建合集或交集來(lái)組裝ComposablePointcut??梢酝ㄟ^(guò)調(diào)用一個(gè)ComposablePointcut的一個(gè)實(shí)例的intersection或union方法來(lái)組裝。必需使用Pointcuts來(lái)操作兩個(gè)Pointcut對(duì)象,如使用Pointcuts的union方法來(lái)組合兩個(gè)Pointcut。

3.4 創(chuàng)建介紹(Introductions)
         Introductions影響整個(gè)類(lèi)。Introductions允許動(dòng)態(tài)地構(gòu)造組合對(duì)象,實(shí)現(xiàn)與多繼承相同的效果。

3.4.1 實(shí)現(xiàn)IntroductionInterceptor
         Spring通過(guò)MethodInterceptor的一個(gè)特殊的子接口IntroductionMethodInterceptor來(lái)實(shí)現(xiàn)Introductions。這個(gè)接口增加一個(gè)額外的方法implementsInterface。Spring提供了一個(gè)方便的類(lèi)來(lái)實(shí)現(xiàn)這一切,DelegatingIntroductionMethodInterceptor,它繼承自IntroductionMethodInterceptor。

3.4.2 創(chuàng)建IntroductionAdvisor
         Spring提供了一個(gè)缺省的實(shí)現(xiàn):DefaultIntroductionAdvisor。

3.4.3 謹(jǐn)慎使用Introduction Advice
         你不能為自己的代碼創(chuàng)建的對(duì)象使用Introductions。

3.5 使用ProxyFactoryBean
         BeanFactory對(duì)象是負(fù)責(zé)創(chuàng)建其他JavaBeans的JavaBeans對(duì)象,于是ProxyFactoryBean創(chuàng)建代理對(duì)象。同其他JavaBeans一樣,它具有控制自己行為的屬性,下表是它的每一個(gè)屬性的解釋。

屬性
 使用
 
target
 代理的target bean。
 
proxyInterfaces
 proxy應(yīng)該實(shí)現(xiàn)的接口列表。
 
interceptorNames
 應(yīng)用于target的advice的bean名稱(chēng)。這些可以是Interceptors,advisors或其他任何類(lèi)型的修訂的名稱(chēng)??梢栽O(shè)定此屬性來(lái)在一個(gè)BeanFactory中使用這個(gè)bean。
 
singleton
 對(duì)每一個(gè)getBean調(diào)用,工廠(chǎng)是否應(yīng)該返回相同實(shí)例的代理。如果你要使用帶狀態(tài)的修訂,應(yīng)該將這個(gè)屬性設(shè)為false。
 
aopProxyFactory
 使用的ProxyFactoryBean的實(shí)現(xiàn)。Spring自帶了兩個(gè)實(shí)現(xiàn),你極有可能不需要此屬性。
 
exposeProxy
 目標(biāo)類(lèi)是否應(yīng)該訪(fǎng)問(wèn)當(dāng)前代理??梢酝ㄟ^(guò)AppContext.getCurrentProxy。應(yīng)該盡量避免,因?yàn)檫@引入了Spring特定的AOP代碼。
 
frozen
 工廠(chǎng)創(chuàng)建之后是否可以對(duì)代理的修訂進(jìn)行更改。
 
optimize
 是否主動(dòng)優(yōu)化已經(jīng)產(chǎn)生的代理。
 
proxyTargetClass
 是否代理目標(biāo)類(lèi),而不是實(shí)現(xiàn)接口。
 

         最經(jīng)常使用的屬性是target,proxyInterfaces和interceptorNames。

3.6 自動(dòng)代理
         如果我們有很多類(lèi)的話(huà),顯示地代理每一個(gè)類(lèi)會(huì)顯得很笨拙。幸運(yùn)的是,Spring的自動(dòng)代理設(shè)施使容器可以為我們創(chuàng)建代理。Spring提供了兩個(gè)類(lèi)來(lái)做這項(xiàng)工作:BeanNameAutoProxyCreator和DefaultAdvisorAutoProxyCreator。

3.6.1 BeanNameAutoProxyCreator
         為所有匹配一組名稱(chēng)的Beans產(chǎn)生代理。

3.6.2 DefaultAdvisorAutoProxyCreator
         為任何Advisors提供上下文環(huán)境。

3.6.3 元數(shù)據(jù)自動(dòng)代理
         代理配置由代碼級(jí)別的屬性決定。常見(jiàn)應(yīng)用是聲明式事務(wù)。

第二部分 Spring的業(yè)務(wù)層
         介紹如何使用IoC和AOP為應(yīng)用程序?qū)崿F(xiàn)業(yè)務(wù)層功能。

第四章 數(shù)據(jù)庫(kù)相關(guān)問(wèn)題
4.1 學(xué)習(xí)Spring的DAO理念
         Service對(duì)象通過(guò)接口來(lái)訪(fǎng)問(wèn)DAOs。這樣有兩個(gè)好處:第一,因?yàn)閟ervice不與具體的DAO實(shí)現(xiàn)耦合,使service對(duì)象更容易測(cè)試。第二,數(shù)據(jù)訪(fǎng)問(wèn)層使用具體的持久化技術(shù),使用接口不會(huì)暴露你使用的技術(shù)。

Spring提供在它的所有DAO框架中一直的異常層次結(jié)構(gòu)。

4.1.1 理解Spring的DataAccessException
         Spring的DAO不會(huì)拋出特定技術(shù)的異常,如SQLException或者HibernateException。所有拋出的異常都是與特定技術(shù)無(wú)關(guān)的類(lèi)org.springframework.dao.DataAccessException的子類(lèi)。這使你的數(shù)據(jù)訪(fǎng)問(wèn)接口可以?huà)伋鐾ㄓ玫腄ataAccessException,而不必與具體的持久化實(shí)現(xiàn)耦合。

你不必一定要處理DataAccessException

         DataAccessException是一個(gè)RuntimeException,因此它是unchecked的異常。這意味著,當(dāng)持久化層拋出這些異常時(shí),你的代碼不一定要處理。這遵循了Spring的通用理念:checked異常會(huì)導(dǎo)致很多不必要的catch或throws語(yǔ)句,使代碼比較凌亂。

         DataAccessException是從Spring的NestedRuntimeException繼承,根Exception可以從NestedRuntimeException的getClause方法獲得。

Spring對(duì)異常進(jìn)行分類(lèi)

         如下表所示,Spring有豐富的異常層次:

異常
 拋出時(shí)機(jī)
 
CleanupFailureDataAccessException
 操作執(zhí)行成功,但是清理數(shù)據(jù)庫(kù)資源時(shí)發(fā)生異常(如,關(guān)閉數(shù)據(jù)庫(kù)連接)。
 
DataAccessResourceFailureException
 數(shù)據(jù)訪(fǎng)問(wèn)資源完全失敗,例如不能連接到數(shù)據(jù)庫(kù)。
 
DataIntegrityViolationException
 插入或更新違反完整性約束,例如,違反一致性約束。
 
DataRetrievalFailureException
 某些數(shù)據(jù)獲取不到,例如,不能根據(jù)主鍵找到一行。
 
DeadLockLoserDataAccessException
 當(dāng)前線(xiàn)程是死鎖的失敗者。
 
IncorrectUpdateSemanticDataAccessException
 更新時(shí)發(fā)生非計(jì)劃的事情,如更新了比期望要多的行。當(dāng)拋出此異常時(shí),操作的事務(wù)沒(méi)有回滾。
 
InvalidDataAccessApiUsageException
 不正確的使用了Java數(shù)據(jù)訪(fǎng)問(wèn)API,例如執(zhí)行前編譯查詢(xún)語(yǔ)句時(shí),編譯失敗。
 
InvalidDataAccessResourceUsageException
 不正確地使用了數(shù)據(jù)訪(fǎng)問(wèn)資源,例如,使用壞的SQL語(yǔ)法訪(fǎng)問(wèn)關(guān)系數(shù)據(jù)庫(kù)。
 
OptimisticLockingFailureException
 樂(lè)觀(guān)加鎖失敗,這由ORM工具或自定義的DAO實(shí)現(xiàn)拋出。
 
TypeMismatchDataAccessException
 Java類(lèi)型和數(shù)據(jù)庫(kù)類(lèi)型存在失配,例如試圖將一個(gè)String插入到numeric類(lèi)型的數(shù)據(jù)庫(kù)列。
 
UncategorizedDataAccessException
 發(fā)生錯(cuò)誤,但是不能確定具體的錯(cuò)誤。
 

4.1.2 使用DataSources
         在Spring框架中,通過(guò)DataSource獲得Connection對(duì)象。

從JNDI取得DataSource

         Spring應(yīng)用程序通常運(yùn)行于J2EE應(yīng)用服務(wù)器或Tomcat之類(lèi)的Web服務(wù)器。這些服務(wù)器可以通過(guò)JNDI提供DataSource。在Spring中我們處理這件事情也是通過(guò)Spring bean。在這種情況下,我們使用JndiObjectFactoryBean。

創(chuàng)建DataSource連接池

         如果我們的Spring容器運(yùn)行的環(huán)境不存在DataSource,而我們又希望使用連接池的好處,我們只需實(shí)現(xiàn)一個(gè)連接池bean,實(shí)現(xiàn)DataSource接口即可。一個(gè)很好的例子是Jakarta Commons DBCP項(xiàng)目的BasicDataSource類(lèi)。只需在Spring配置文件中配置一個(gè)bean即可。

測(cè)試時(shí)使用DataSource

         Spring自帶了一個(gè)輕量級(jí)的DataSource實(shí)現(xiàn),用于單元測(cè)試或單元測(cè)試套件。

4.1.3 一致性DAO Support
         一個(gè)模板方法定義了一個(gè)過(guò)程的骨架。Spring將數(shù)據(jù)訪(fǎng)問(wèn)過(guò)程的固定和可變的部分分離成兩個(gè)不同的類(lèi):模板與回調(diào)。模板處理數(shù)據(jù)訪(fǎng)問(wèn)的不變部分—控制事務(wù),管理資源和處理異常。回調(diào)接口的實(shí)現(xiàn)定義你的應(yīng)用程序特定的東西—?jiǎng)?chuàng)建語(yǔ)句,綁定參數(shù)和處理結(jié)果集。

         模板回調(diào)設(shè)計(jì)的頂部有一個(gè)DAO Support類(lèi),你的DAO類(lèi)可以從這些類(lèi)繼承。

4.2 在Spring中使用JDBC
4.2.1 JDBC代碼的問(wèn)題
         JDBC在為你提供與數(shù)據(jù)庫(kù)緊密結(jié)合的API的同時(shí),也使你必需負(fù)責(zé)處理與訪(fǎng)問(wèn)數(shù)據(jù)庫(kù)所有的相關(guān)工作。包括管理數(shù)據(jù)庫(kù)資源和處理異常。

         代碼比較冗贅。

4.2.2 使用JdbcTemplate
         Spring的JDBC框架通過(guò)擔(dān)當(dāng)資源管理和錯(cuò)誤處理的負(fù)擔(dān)來(lái)清理你的JDBC代碼。Spring的數(shù)據(jù)訪(fǎng)問(wèn)框架都處理一個(gè)模板類(lèi)。JdbcTemplate的工作所需要的只是一個(gè)DataSource。因?yàn)镾pring的所有DAO模板類(lèi)都是線(xiàn)程安全的,在我們的應(yīng)用程序中對(duì)每一個(gè)DataSource只需要一個(gè)JdbcTemplate實(shí)例。

寫(xiě)數(shù)據(jù)

         PreparedStatementCreator接口的實(shí)現(xiàn)負(fù)責(zé)創(chuàng)建一個(gè)PreparedStatement,這個(gè)接口只提供了一個(gè)方法createPreparedStatement。

         這個(gè)接口的實(shí)現(xiàn)通常也實(shí)現(xiàn)另一個(gè)接口:SqlProvider。通過(guò)實(shí)現(xiàn)這個(gè)類(lèi)的方法—getSql()—使你的類(lèi)向JdbcTemplate類(lèi)提供SQL字符串。

         對(duì)PreparedStatementCreator的補(bǔ)充是PreparedStatementSetter。實(shí)現(xiàn)這個(gè)接口的類(lèi)接受一個(gè)PreparedStatement參數(shù),負(fù)責(zé)設(shè)定所有的參數(shù)。

         如果要更新多行,可以使用BatchPrepareStatementSetter。如果你的JDBC驅(qū)動(dòng)支持批量操作,更新會(huì)批量進(jìn)行,創(chuàng)建高效的數(shù)據(jù)庫(kù)訪(fǎng)問(wèn),否則Spring會(huì)模擬批處理,但是語(yǔ)句會(huì)一個(gè)一個(gè)執(zhí)行。

讀數(shù)據(jù)

         我們要告訴Spring如何處理ResultSet中的每一行,通過(guò)實(shí)現(xiàn)RowCallbackHandler的方法processRow來(lái)處理。

         如果通過(guò)查詢(xún)獲取多個(gè)對(duì)象,可以實(shí)現(xiàn)一個(gè)子接口。例如,如果要獲取某個(gè)類(lèi)的所有對(duì)象,可以實(shí)現(xiàn)ResultReader接口。Spring提供了這個(gè)接口的一個(gè)實(shí)現(xiàn),RowMapperResultReader。RowMapper接口負(fù)責(zé)將ResultSet的一行映射到一個(gè)對(duì)象。

         JdbcTemplate也提供了很多返回結(jié)果為int或String等簡(jiǎn)單類(lèi)型的方法。

調(diào)用存儲(chǔ)過(guò)程

         Spring提供對(duì)存儲(chǔ)過(guò)程的支持是通過(guò)實(shí)現(xiàn)接口CallableStatementCallBack。

4.2.3 將操作創(chuàng)建為對(duì)象
         通過(guò)增加一層與直接JDBC操作的隔離。

創(chuàng)建SqlUpdate對(duì)象

         為創(chuàng)建執(zhí)行插入或更新的可復(fù)用對(duì)象,繼承SqlUpdate類(lèi)。

使用MappingSqlQuery查詢(xún)數(shù)據(jù)庫(kù)

         通過(guò)繼承MappingSqlQuery來(lái)將一個(gè)查詢(xún)建模為對(duì)象。

4.2.4 自動(dòng)遞增鍵值
         通過(guò)DataFieldMaxValueIncrementer接口實(shí)現(xiàn)。

4.3 Spring的ORM框架支持簡(jiǎn)介
         Spring支持Hibernate等很多開(kāi)源ORM工具。

4.4 在Spring中集成Hibernate
4.4.1 Hibernate概覽
         Hibernate通過(guò)XML配置文件將對(duì)象映射到關(guān)系數(shù)據(jù)庫(kù)。通常來(lái)說(shuō),每一個(gè)持久類(lèi)都有一個(gè)對(duì)應(yīng)的XML配置文件,擴(kuò)展名為.hbm.xml。根據(jù)規(guī)范,文件名與類(lèi)名相同。

4.4.2 管理Hibernate資源
         在應(yīng)用程序生命周期中只需要一個(gè)SessionFactory實(shí)例,因此可以在Spring配置文件中配置此對(duì)象??梢允褂肧pring的LocalSessionFactoryBean。使用Spring,可以不再使用hibernate.properties,可以纏繞到LocalSessionFactoryBean的hibernateProperties屬性。使用Spring可以配置mappingDirectoryLocations屬性可以指定一個(gè)作為應(yīng)用程序classpath的一個(gè)子集的路徑,在這個(gè)路徑下的所有*.hbm.xml文件都會(huì)被配置。如同Spring的其他DAO框架一樣,有一個(gè)HibernateTemplate這樣一個(gè)模板類(lèi)。

4.4.3 通過(guò)HibernateTemplate訪(fǎng)問(wèn)Hibernate
         HibernateTemplate與一個(gè)回調(diào)接口:HibernateCallBack。HibernateCallBack只要一個(gè)方法doInHibernate。

4.4.4 繼承HibernateDaoSupport
4.5 Spring與JDO
         JDO是Sun的標(biāo)準(zhǔn)持久化規(guī)范。

4.5.1 配置JDO
         與Hibernate的SessionFactory類(lèi)似,JDO具有一個(gè)生命周期很長(zhǎng)的對(duì)象來(lái)持有持久化配置,PersistenceManagerFactory。如果不使用Spring我們通過(guò)JDOHelper得到這樣一個(gè)對(duì)象。

         在Spring中,使用LocalePersistenceManagerFactoryBean配置PersistenceManagerFactoryBean。有了JDO PersistenceManagerFactory,下一步就是將此bean纏繞到JdoTemplate。

4.5.2 使用JdoTemplate訪(fǎng)問(wèn)數(shù)據(jù)
         JdoTemplate類(lèi)只要一個(gè)方法execute(JdoCallBack)。JdoCallBack也只有一個(gè)方法doInJdo。

4.6 Spring與iBATIS
4.6.1 設(shè)定SQL Maps
         也是使用XML配置文件配置SQL Maps。

4.6.2 使用SqlMapClientTemplate
         同其他ORM框架一樣,只需實(shí)現(xiàn)SqlMapClientTemplate的方法doInSqlMapClient。及SqlMapClientCallBack。

4.7 Spring與OJB
         Spring通過(guò)PersistenceBroker與OJB集成。

4.7.1 設(shè)置OJB的PersistenceBroker
         還是XML文件。

第五章 管理事務(wù)
5.1 理解事務(wù)
5.1.1 用四個(gè)詞解釋事務(wù)
         ACID用來(lái)描述事務(wù):

<!--[if !supportLists]-->n  <!--[endif]-->Atomic—事務(wù)由一個(gè)或多個(gè)活動(dòng)綁定起來(lái)作為一個(gè)工作單元。

<!--[if !supportLists]-->n  <!--[endif]-->Consistent—一旦事務(wù)結(jié)束,系統(tǒng)的狀態(tài)與業(yè)務(wù)狀態(tài)一致。

<!--[if !supportLists]-->n  <!--[endif]-->Isolated—事務(wù)應(yīng)當(dāng)允許多個(gè)用戶(hù)同是工作而不會(huì)相互添亂。

<!--[if !supportLists]-->n  <!--[endif]-->Durable—事務(wù)結(jié)束后,事務(wù)的結(jié)果應(yīng)當(dāng)持久化。

5.1.2 理解Spring的事務(wù)管理支持
         Spring既支持程序式事務(wù),也支持聲明式事務(wù)。程序式事務(wù)可以靈活控制事務(wù)的邊界,而聲明式事務(wù)可以使操作從業(yè)務(wù)規(guī)則中解耦。EJB也支持聲明式事務(wù),但Spring的聲明式事務(wù)提供更多的屬性,如隔離級(jí)別和超時(shí)。

5.1.3 Spring事務(wù)管理器簡(jiǎn)介
         Spring不直接管理事務(wù)。相反,它帶有一些事務(wù)管理器,這些管理器將事務(wù)管理的職責(zé)代理給由JTA或其他持久化機(jī)制提供的平臺(tái)相關(guān)的事務(wù)實(shí)現(xiàn)。Spring的事務(wù)管理器如下表所示。

事務(wù)管理器實(shí)現(xiàn)
 目的
 
org.springframework.jdbc.datasource.DataSourceTransactionManager
 在單個(gè)JDBC DataSource上管理事務(wù)
 
org.springframework.orm.hibernate.HibernateTransactionManager
 當(dāng)Hibernate是持久化機(jī)制時(shí)用來(lái)管理事務(wù)。
 
org.springframework.orm.jdo.JdoTransactionManager
 當(dāng)使用JDO作為持久化時(shí)使用的事務(wù)管理器。
 
org.springframework.transaction.jta.JtaTransactionManager
 使用JTA實(shí)現(xiàn)管理事務(wù),當(dāng)一個(gè)事務(wù)跨越多個(gè)源時(shí),必需使用這個(gè)事務(wù)管理器。
 
org.springframework.orm.ojb.PersistenceBrokerTransactionManager
 使用OJB時(shí)使用的事務(wù)管理器。
 

         為使用事務(wù)管理器,必需在你的應(yīng)用程序上下文中聲明這個(gè)事務(wù)管理器。

JDBC事務(wù)

         如果使用JDBC做應(yīng)用程序的持久化,DataSourceTransactionManager可以為你處理事務(wù)邊界。使用XML纏繞到應(yīng)用程序上下文中。

Hibernate事務(wù)

         如果你的應(yīng)用程序使用Hibernate進(jìn)行持久化,可以使用HibernateTransactionManager可以為你處理事務(wù)。HibernateTransactionManager將事務(wù)管理的職責(zé)代理到net.sf.hibernate.Transaction對(duì)象。

JDO(Java Data Object)事務(wù)

         JdoTransactionManager。

OJB事務(wù)

         PersistenceBrokerTransactionManager。

JTA事務(wù)

         JtaTransactionManager。如果使用多個(gè)數(shù)據(jù)源,需要使用JtaTransactionManager。

5.2 Spring中的程序式事務(wù)
         向你的代碼中添加事務(wù)的一種方法是使用Spring的TransactionTemplate類(lèi)程序式地添加事務(wù)邊界。TransactionTemplate利用了回調(diào)機(jī)制。以實(shí)現(xiàn)TransactionCallBack接口開(kāi)始。

如果你想完全控制事務(wù)的邊界,使用程序式事務(wù)比較好。通常你不需要如此精確的邊界,這就是為什么通常在應(yīng)用程序代碼之外聲明事務(wù)。

5.3 聲明事務(wù)
         Spring對(duì)聲明式事務(wù)的支持是通過(guò)AOP框架實(shí)現(xiàn)的。為在Spring應(yīng)用程序中利用聲明式事務(wù),使用TransactionProxyFactoryBean。

5.3.1 理解事務(wù)屬性
         在Spring中,一個(gè)事務(wù)屬性是值對(duì)事務(wù)策略如何應(yīng)用于一個(gè)方法的一個(gè)描述。這個(gè)描述可以包括以下參數(shù)中的一個(gè)或多個(gè):

<!--[if !supportLists]-->n  <!--[endif]-->傳播行為

<!--[if !supportLists]-->n  <!--[endif]-->隔離級(jí)別

<!--[if !supportLists]-->n  <!--[endif]-->只讀提示

<!--[if !supportLists]-->n  <!--[endif]-->事務(wù)有效時(shí)間

傳播行為

         傳播行為定義了相對(duì)于客戶(hù)端和被調(diào)用方法的事務(wù)的邊界。Spring定義了七種傳播行為,如下表所示:

傳播行為
 含義
 
PROPAGATION_MANDATORY
 表示一個(gè)方法必需運(yùn)行于一個(gè)事務(wù),如果沒(méi)有進(jìn)行中的事務(wù),則拋出一個(gè)異常。
 
PROPAGATION_NESTED
 表示如果存在進(jìn)行中的事務(wù),則方法應(yīng)該運(yùn)行于一個(gè)嵌套的事務(wù)。嵌套的事務(wù)可以從閉包事務(wù)中分別提交或回滾。如果不存在嵌套事務(wù),則與PROPAGATION_REQUIRED行為相同。廠(chǎng)商一般對(duì)這種行為支持不是很好,查詢(xún)資源管理器的文檔來(lái)確定是否支持嵌套事務(wù)。
 
PROPAGATION_NEVER
 表示當(dāng)前方法不應(yīng)當(dāng)運(yùn)行于事務(wù)上下文。如果存在進(jìn)行中的事務(wù),則拋出一個(gè)異常。
 
PROPAGATION_NOT_SUPPORTED
 表示當(dāng)前方法不應(yīng)當(dāng)運(yùn)行于事務(wù),如果存在進(jìn)行中事務(wù),在方法執(zhí)行期間事務(wù)暫停。如果使用JTATransactionManager,則對(duì)TransactionManager的訪(fǎng)問(wèn)是必需的。
 
PROPAGATION_REQUIRED
 表示當(dāng)前方法必需運(yùn)行于事務(wù)內(nèi),如果存在一個(gè)進(jìn)行中的事務(wù),則使用這個(gè)事務(wù),否則開(kāi)啟新的事務(wù)。
 
PROPAGATION_REQUIRED_NEW
 表示當(dāng)前方法必需運(yùn)行于自己的事務(wù)內(nèi)。開(kāi)啟一個(gè)新事務(wù),如果存在進(jìn)行中事務(wù),則在方法執(zhí)行期間暫停。如果使用JTATransactionManager,則對(duì)TransactionManager的訪(fǎng)問(wèn)是必需的。
 
PROPAGATION_SUPPORTS
 表示當(dāng)前方法不必需一個(gè)事務(wù)上下文,但是如果有進(jìn)行中的事務(wù),這個(gè)方法也可以運(yùn)行于這個(gè)事務(wù)。
 

隔離級(jí)別

         事務(wù)的隔離級(jí)別如下表所示:

隔離級(jí)別
 含義
 
ISOLATION_DEFAULT
 使用底層數(shù)據(jù)存儲(chǔ)的缺省隔離級(jí)別。
 
ISOLATION_READ_UNCOMMITED
 允許讀取沒(méi)有提交的更改。可能會(huì)導(dǎo)致臟讀,幻影讀和不可重復(fù)讀。
 
ISOLATION_READ_COMMITED
 允許從沒(méi)有提交的并發(fā)事務(wù)中讀取。阻止臟讀,但仍有可能發(fā)生幻影讀和不可重復(fù)讀。
 
ISOLATION_REPEATABLE_READ
 對(duì)相同字段的多次讀取產(chǎn)生相同的結(jié)果,除非由自身的事務(wù)更改。阻止臟讀和不可重復(fù)讀,但仍有可能發(fā)生幻影讀。
 
ISOLATION_SERIALIZABLE
 完全符合ACID的隔離級(jí)別,阻止臟讀,幻影讀和不可重復(fù)讀。這是速度最慢的隔離級(jí)別,因?yàn)橥ǔMㄟ^(guò)在事務(wù)中完全的表鎖定來(lái)實(shí)現(xiàn)。
 

并不是所有的資源管理器都支持所有的這些隔離級(jí)別。

只讀

         聲明事務(wù)是只讀的,給予底層數(shù)據(jù)存儲(chǔ)機(jī)會(huì)對(duì)事務(wù)進(jìn)行優(yōu)化。

事務(wù)超時(shí)

         設(shè)定事務(wù)過(guò)一定時(shí)間后自動(dòng)回滾。

5.3.2 聲明簡(jiǎn)單的事務(wù)策略
         TransactionProxyFactoryBean查詢(xún)一個(gè)方法的事務(wù)屬性,從而確定如何管理該方法的事務(wù)策略。TransactionProxyFactoryBean具有一個(gè)transactionAttributeSource屬性,該屬性纏繞到TransactionAttributeSource接口的一個(gè)實(shí)例。TransactionAttributeSource接口用作查找一個(gè)方法的事務(wù)屬性的引用。通過(guò)調(diào)用該接口的getTransactionAttribute()方法來(lái)一個(gè)特定方法的事務(wù)屬性。

         MatchAlwaysTransactionAttributeSource是最簡(jiǎn)單的TransactionAttributeSource的實(shí)現(xiàn),當(dāng)調(diào)用其getTransactionAttribute()方法時(shí),不管事務(wù)中纏繞的是哪一個(gè)方法,每次都返回相同的TransactionAttribute(PROPAGATION_REQUIRED和ISOLATION_DEFAULT)。

改變?nèi)笔〉腡ransactionAttribute

         缺省策略是PROPAGATION_REQUIRED和ISOLATION_DEFAULT,如果要改變的話(huà),只需對(duì)transactionAttribute纏繞另一個(gè)TransactionAttribute。

5.4 根據(jù)方法名聲明事務(wù)
         EJB規(guī)范的一個(gè)主要特性是容器管理的事務(wù)(CMT)。使用CMT,可以在EJB的部署描述子中聲明事務(wù)策略。Spring采用了EJB的聲明式事務(wù)模型,提供多個(gè)事務(wù)屬性源,使你可以對(duì)POJOs聲明事務(wù)策略。

5.4.1 使用NameMatchTransactionAttributeSource
         NameMatchTransactionAttributeSource的properties屬性將方法名映射到事務(wù)屬性描述子。

         事務(wù)屬性描述子的格式:

         PROPAGATION,ISOLATION,readOnly,-Exceptions,+Exceptions

5.4.2 簡(jiǎn)化名稱(chēng)匹配的事務(wù)
         TransactionProxyFactoryBean具有一個(gè)transactionAttributes屬性,可以直接將事務(wù)屬性纏繞到此屬性,而不必纏繞NameMatchTransactionAttributeSource。

5.5 使用元數(shù)據(jù)聲明事務(wù)
5.5.1 從元數(shù)據(jù)獲取事務(wù)屬性
         需要將transactionAttributeSource設(shè)置為AttributesTransactionAttributeSource。如下所示:

<bean id="transactionAttributeSource"

class="org.springframework.transaction.interceptor.

AttributesTransactionAttributeSource">

<constructor-arg>

<ref bean="attributesImpl"/>

</constructor-arg>

</bean>

         注意這里的構(gòu)造函數(shù)參數(shù)attributesImpl,事務(wù)屬性源使用它與底層的元數(shù)據(jù)實(shí)現(xiàn)進(jìn)行交互。

5.5.2 使用Commons Attribute聲明事務(wù)
聲明一個(gè)屬性實(shí)現(xiàn)

         <bean id="attributesImpl" class="org.springframework.

metadata.commons.CommonsAttributes">

...

</bean>

標(biāo)記事務(wù)性方法

         Jakarta Commons Attributes通過(guò)替換方法/類(lèi)前面的注釋中doclet標(biāo)簽來(lái)實(shí)現(xiàn)。

設(shè)置Commons Attributes的構(gòu)建

         Jakarta Commons Attributes的機(jī)制是預(yù)編譯你的代碼中的doclet標(biāo)簽,重寫(xiě)你的類(lèi),在代碼中嵌入元數(shù)據(jù)。需要在構(gòu)建文件中加入與編譯器。

5.6 裁剪事務(wù)聲明
5.6.1 從父TransactionProxyFactoryBean繼承
         不必每次都聲明TransactionProxyFactoryBean。

5.6.2 自動(dòng)代理事務(wù)
         使用AOP特性。

第六章 遠(yuǎn)程訪(fǎng)問(wèn)
6.1 Spring遠(yuǎn)程訪(fǎng)問(wèn)概覽
         遠(yuǎn)程訪(fǎng)問(wèn)是客戶(hù)端應(yīng)用程序和服務(wù)之間的對(duì)話(huà)。客戶(hù)端應(yīng)用程序向其他系統(tǒng)請(qǐng)求功能,這些功能稱(chēng)作遠(yuǎn)程服務(wù)。Spring的遠(yuǎn)程訪(fǎng)問(wèn)支持六種RPC模型:RMI,Caucho’s Hessian and Burlap,Spring的HTTP Invoker,EJB和基于JAX-RPC的Web服務(wù)。

         客戶(hù)端調(diào)用代理,好像是代理在提供服務(wù)功能,代理代表客戶(hù)端同遠(yuǎn)程服務(wù)通信。在服務(wù)器端,可以使用以上六種模型將任何Spring管理的Bean的功能暴露為遠(yuǎn)程服務(wù)。

6.2 使用RMI
6.2.1 纏繞RMI服務(wù)
         Spring的RmiProxyFactoryBean可以創(chuàng)建RMI服務(wù)的代理。

6.2.2 導(dǎo)出RMI服務(wù)
在Spring中配置RMI服務(wù)。

         首先書(shū)寫(xiě)服務(wù)接口,無(wú)須從java.rmi.Remote繼承。然后定義這個(gè)接口的實(shí)現(xiàn)類(lèi)。下面就可以將這個(gè)實(shí)現(xiàn)類(lèi)配置成Spring配置文件中的<bean>。然后使用RmiServiceExporter將此實(shí)現(xiàn)導(dǎo)出成服務(wù)。

         RMI有很多局限性。首先,跨防火墻工作比較困難。因?yàn)镽MI的通信使用任意端口,典型的防火墻都不允許這樣做。另外,RMI是基于Java的。這意味著服務(wù)器端和客戶(hù)端都必須使用Java編寫(xiě)。

6.3 使用Hessian和Burlap進(jìn)行遠(yuǎn)程訪(fǎng)問(wèn)
         Hessian和Burlap是由Caucho Technology提供的兩個(gè)解決方案,它允許通過(guò)HTTP提供輕量級(jí)的遠(yuǎn)程服務(wù)。Hessian同RMI一樣使用二進(jìn)制消息在客戶(hù)端和服務(wù)端之間進(jìn)行通信,但同其他二進(jìn)制遠(yuǎn)程訪(fǎng)問(wèn)機(jī)制不同,Hessian的二進(jìn)制消息是獨(dú)立于編程語(yǔ)言的。Burlap是一種基于XML的遠(yuǎn)程訪(fǎng)問(wèn)技術(shù)。

         Hessian和Burlap的唯一區(qū)別就是基于二進(jìn)制消息還是基于XML。

6.3.1 訪(fǎng)問(wèn)Hessian和Burlap服務(wù)
         在Spring中使用Hessian服務(wù)與使用基于RMI服務(wù)的唯一區(qū)別是使用HessianProxyFactoryBean。對(duì)于Burlap服務(wù),是使用BurlapProxyFactoryBean。

6.3.2 使用Hessian/Burlap暴露Bean功能
導(dǎo)出Hessian服務(wù)

         使用HessianServiceExporter。

配置Hessian控制器

         RmiServiceExporter與HessianServiceExporter之間的另一個(gè)主要區(qū)別是,因?yàn)镠essian是基于HTTP的,HessianServiceExporter實(shí)現(xiàn)為一個(gè)Spring MVC控制器。這意味著如果要導(dǎo)出Hessian服務(wù),需要執(zhí)行兩個(gè)額外的配置步驟:

<!--[if !supportLists]-->1.       <!--[endif]-->在Spring配置文件中配置一個(gè)URL處理器,來(lái)對(duì)合適的Hessian服務(wù)bean分派Hessian服務(wù)URLs。

<!--[if !supportLists]-->2.       <!--[endif]-->在web.xml中配置一個(gè)DispatcherServlet,將應(yīng)用程序部署為一個(gè)Web應(yīng)用程序。

將URL模式映射到Hessian服務(wù)。

導(dǎo)出Burlap服務(wù)

         使用BurlapServiceExporter。

6.4 使用HTTP Invoker
         HTTP Invoker是一種新的遠(yuǎn)程訪(fǎng)問(wèn)模型,它是Spring框架的一部分。它跨越HTTP執(zhí)行遠(yuǎn)程訪(fǎng)問(wèn),而且使用Java的序列化機(jī)制。

6.4.1 通過(guò)HTTP訪(fǎng)問(wèn)服務(wù)
         使用HttpInvokerProxyFactoryBean。

6.4.2 將beans暴露為HTTP服務(wù)
         HttpInvokerServiceExporter。

         HTTP Invoker的局限性在于,它的服務(wù)端和客戶(hù)端都必須是基于Spring的。

6.5 使用EJB
6.5.1 訪(fǎng)問(wèn)EJBs
代理EJBs

         Spring提供了兩個(gè)代理工廠(chǎng)beans,來(lái)對(duì)EJBs進(jìn)行代理:LocalStatelessSessionProxyFactoryBean和SimpleRemoteStatelessSessionProxyFactoryBean。

<bean id="paymentService" class="org.springframework.ejb.

access.LocalStatelessSessionProxyFactoryBean"

lazy-init="true">

<property name="jndiName">

<value>payService</value>

</property>

<property name="businessInterface">

<value>com.springinaction.payment.PaymentService</value>

</property>

</bean>

纏繞EJBs

         與纏繞其他Spring beans相同。

工作機(jī)制

         首先,在啟動(dòng)的時(shí)候,LocalStatelessSessionProxyFactoryBean使用由jndiName屬性指定的JNDI名稱(chēng)來(lái)通過(guò)JNDI查找EJB的本地home接口。然后,每次調(diào)用接口方法的時(shí)候,代理都調(diào)用本地home接口的create()方法獲取EJB的引用。最后,代理調(diào)用EJB相應(yīng)的方法。

訪(fǎng)問(wèn)遠(yuǎn)程EJB

         使用SimpleRemoteStatelessSessionProxyFactoryBean。

6.5.2 開(kāi)發(fā)支持Spring的EJBs
         Spring提供了四個(gè)抽象的支持類(lèi)來(lái)更容易地開(kāi)發(fā)EJBs:

<!--[if !supportLists]-->n  <!--[endif]-->AbstractMessageDrivenBean—對(duì)于開(kāi)發(fā)消息驅(qū)動(dòng)的beans比較有用。不接受來(lái)自JMS的消息。

<!--[if !supportLists]-->n  <!--[endif]-->AbstractJmsMessageDrivenBean—接受來(lái)自JMS的消息。

<!--[if !supportLists]-->n  <!--[endif]-->AbstractStatefulSessionBean

<!--[if !supportLists]-->n  <!--[endif]-->AbstractStatelessSessionBean

6.6 使用基于JAX-RPC的Web services
         Java APIs for XML-based remote procedure call。

6.6.1 引用JAX Web service
         實(shí)現(xiàn)簡(jiǎn)單的功能都需要很多代碼。

6.6.2 在Spring中纏繞web服務(wù)
         使用JaxRpcPortProxyFactoryBean。

第七章 訪(fǎng)問(wèn)企業(yè)服務(wù)
7.1 從JNDI獲取對(duì)象
         JNDI為Java應(yīng)用程序提供了一個(gè)存放應(yīng)用程序?qū)ο蟮闹行膸?kù)。例如,典型的J2EE應(yīng)用程序使用JNDI來(lái)存取諸如JDBC數(shù)據(jù)源和JTA事務(wù)管理器之類(lèi)的東西。

         Spring的JNDI抽象,使你能夠在你的應(yīng)用程序配置文件中聲明JNDI查詢(xún),從而可以將這些對(duì)象纏繞到其他beans的屬性中,好像JNDI對(duì)象也是POJO。

7.1.1 使用通常的JNDI
         通常的JNDI使用方法違反依賴(lài)注入的原則。

7.1.2 代理JNDI對(duì)象
         JndiObjectFactoryBean可以纏繞到一個(gè)屬性,它實(shí)際上創(chuàng)建JNDI對(duì)象并且纏繞到這個(gè)屬性。

7.2 發(fā)送E-mail
         為發(fā)送e-mail需要一個(gè)郵件發(fā)送器,由Spring的MailSender接口定義,Spring自帶了兩個(gè)實(shí)現(xiàn):

<!--[if !supportLists]-->n  <!--[endif]-->CosMailSenderImpl—SMTP郵件發(fā)送器的簡(jiǎn)單實(shí)現(xiàn),基于Jason Hunter的COS實(shí)現(xiàn)。

<!--[if !supportLists]-->n  <!--[endif]-->JavaMailSenderImpl—基于JavaMail API的實(shí)現(xiàn)。允許發(fā)送MIME消息。

<bean id="mailSender"

class="org.springframework.mail.javamail.JavaMailSenderImpl">

<property name="host">

<value>mail.springtraining.com</value>

</property>

</bean>

還可以定義郵件模板。

7.3 定時(shí)任務(wù)
         兩個(gè)流行的定時(shí)API是Java的Timer類(lèi)和OpenSymphony的Quartz Scheduler。Spring為二者都提供了抽象。

7.3.1 使用Java的Timer進(jìn)行定時(shí)
創(chuàng)建定時(shí)任務(wù)

         通過(guò)繼承java.util.TimerTask類(lèi)。

對(duì)定時(shí)任務(wù)作出計(jì)劃

         ScheduledTimerTask定義定時(shí)任務(wù)執(zhí)行的頻率。

啟動(dòng)計(jì)時(shí)器

         TimerFactoryBean。

         缺陷是,只能設(shè)定運(yùn)行的頻率,而不能精確設(shè)定每天何時(shí)運(yùn)行定時(shí)任務(wù)。

7.3.2 使用quartz定時(shí)器
         可以設(shè)定每天什么時(shí)候啟動(dòng)任務(wù)。

創(chuàng)建一個(gè)Job

         繼承Spring的QuartzJobBean。

計(jì)劃job

         Quartz的Trigger類(lèi)用來(lái)決定何時(shí)以何種方式運(yùn)行Quartz Job。Spring自帶了兩個(gè)觸發(fā)器:SimpleTriggerBean和CronTriggerBean。

啟動(dòng)job

         SchedulerFactoryBean。

7.3.3 按計(jì)劃調(diào)用方法
         可以定時(shí)執(zhí)行某個(gè)方法,而無(wú)需分別編寫(xiě)Task或JobBean。Spring提供了MethodInvokingTimerTaskFactoryBean和MethodInvokingJobDetailFactoryBean。

7.4 使用JMS發(fā)送消息
         Java Messaging Service是用作異步處理的Java API。JMS支持兩種消息機(jī)制:點(diǎn)對(duì)點(diǎn)和發(fā)布訂閱。

7.4.1 使用JMS模板發(fā)送消息
         Spring利用回調(diào)機(jī)制來(lái)與JMS消息協(xié)調(diào)。

使用模板

         使用JmsTemplate來(lái)發(fā)送消息。

纏繞模板

使用JMS1.0.2

         區(qū)別是JmsTemplate102需要知道是在使用點(diǎn)對(duì)點(diǎn)還是發(fā)布訂閱模式。缺省情況下,JmsTemplate102假定你使用點(diǎn)對(duì)點(diǎn)模式。

處理JMS異常

         不必處理JMSException。

7.4.2 處理消息
         JmsTemplate也可以用來(lái)接收消息。

7.4.3 轉(zhuǎn)化消息
         MessageConverter接口定義了將JMS消息轉(zhuǎn)化為對(duì)象的通用機(jī)制。

第三部分 Spring的web層
第八章 構(gòu)建Web層
8.1 開(kāi)始使用Spring MVC
8.1.1 request的生命周期
         從Spring接收到一個(gè)請(qǐng)求,到返回一個(gè)響應(yīng)到客戶(hù)端,牽扯到了Spring MVC框架的諸多方面。

         過(guò)程開(kāi)始于客戶(hù)端發(fā)送一個(gè)請(qǐng)求,接受這個(gè)請(qǐng)求的第一個(gè)組件是Spring的DispatcherServlet。同大部分基于Java的MVC框架一樣,Spring MVC使用一個(gè)唯一的前端控制器來(lái)處理request。在Spring MVC中DispatcherServlet就是前端控制器。負(fù)責(zé)處理請(qǐng)求的Spring MVC組件是controller,為了知道哪一個(gè)controller應(yīng)該處理某個(gè)請(qǐng)求,DispatcherServlet查詢(xún)一個(gè)或多個(gè)HandlerMapping。HandlerMapping的工作就是將URL模式映射到Controller對(duì)象。一旦DispatcherServlet得到Controller對(duì)象,它將request分派給這個(gè)控制器來(lái)執(zhí)行業(yè)務(wù)邏輯。執(zhí)行完業(yè)務(wù)邏輯后,Controller返回一個(gè)ModelAndView對(duì)象給DispatcherServlet。ModelAndView對(duì)象可以包含一個(gè)View對(duì)象或者View對(duì)象的名稱(chēng)。如果包含的是View對(duì)象的名稱(chēng),DispatcherServlet通過(guò)ViewResolver來(lái)查找對(duì)應(yīng)的View對(duì)象。最后,DispatcherServlet將request分派給View對(duì)象,View對(duì)象負(fù)責(zé)產(chǎn)生一個(gè)響應(yīng)到客戶(hù)端。

8.1.2 配置DispatcherServlet
         DispatcherServlet同任何servlet一樣,必需在web應(yīng)用的web.xml文件中配置。Servlet名稱(chēng)很重要,因?yàn)镈ispatcherServlet根據(jù)基于這個(gè)名稱(chēng)的xml文件加載應(yīng)用程序上下文。

分解應(yīng)用程序上下文

         可以將各層的xml文件分開(kāi)。便于維護(hù)。為保證所有這些配置文件都會(huì)被加載,必需在web.xml文件中配置上下文加載器。有兩個(gè)可供選擇的加載器:ContextLoaderListener和ContextLoaderServlet。

8.1.3 Spring MVC核心
構(gòu)造控制器

         實(shí)現(xiàn)Controller接口,handleRequest方法。

配置Controller Bean

         DispatcherServlet使用的缺省處理器映射是BeanNameUrlHandlerMapping。

聲明view resolver

         View resolver的工作是將返回的ModelAndView中的view名稱(chēng)映射為view對(duì)象。對(duì)于產(chǎn)生JSP的views,最簡(jiǎn)單的莫過(guò)于使用InternalResourceViewResolver。

<bean id="viewResolver" class="org.springframework.web.

servlet.view.InternalResourceViewResolver">

<property name="prefix">

<value>/WEB-INF/jsp/</value>

</property>

<property name="suffix">

<value>.jsp</value>

</property>

</bean>

將view名稱(chēng)映射為/WEB-INF/jsp/[view名稱(chēng)].jsp

創(chuàng)建JSP

所有的組件放在一起工作

8.2 將requests映射到controllers
         Handler Mappings將特定的Controller映射到一個(gè)URL模式。Spring MVC的所有handler mappings都實(shí)現(xiàn)org.springframework.web.servlet.HandlerMapping接口。Spring提供了三個(gè)有用的HandlerMapping的實(shí)現(xiàn):

<!--[if !supportLists]-->n  <!--[endif]-->BeanNameUrlHandlerMapping—基于控制器的名稱(chēng)將控制器映射到URLs。

<!--[if !supportLists]-->n  <!--[endif]-->SimpleUrlHandlerMapping—使用在上下文配置文件中定義的屬性集合將控制器映射到URLs。

<!--[if !supportLists]-->n  <!--[endif]-->CommonsPathMapHandlerMapping—使用controller代碼中的元數(shù)據(jù)將控制器映射到URLs。

8.2.1 將URLs映射到bean名稱(chēng)
         為設(shè)置bean名稱(chēng)映射,首先在上下文配置文件中聲明BeanNameUrlHandlerMapping bean。然后將Controller的名稱(chēng)的值設(shè)置為URL模式。

8.2.2 使用SimpleUrlHandlerMapping
         可以直接將URL模式映射到controllers而無(wú)須為controllers命名。

8.2.3 使用元數(shù)據(jù)映射controllers
         在配置文件中使用CommonsPathMapHandlerMapping聲明<bean>元素,然后在Controllers代碼中標(biāo)記PathMap屬性。最后,還需要設(shè)置Commons Attribute的編譯器。

8.2.4 使用多個(gè)HandlerMapping
         每一個(gè)HandlerMapping類(lèi)都實(shí)現(xiàn)了Ordered接口,這意味著可以聲明多個(gè)HandlerMapping,并設(shè)置order屬性來(lái)指定其優(yōu)先級(jí)。

8.3 使用Controllers處理請(qǐng)求
         Spring MVC的controller類(lèi)的可選擇的集合如下表所示:

類(lèi)型
 類(lèi)
 用于何時(shí)
 
Simple
 Controller(接口)

AbstractController
 當(dāng)你的Controller很簡(jiǎn)單時(shí)使用,比起基本的servlet,無(wú)須更多的功能。
 
Throwaway
 ThrowawayController
 需要一個(gè)簡(jiǎn)單的方式來(lái)將請(qǐng)求處理為命令。
 
Multi-Action
 MultiActionController
 應(yīng)用程序有執(zhí)行相似或相關(guān)邏輯的多個(gè)動(dòng)作。
 
Command
 BaseCommandController

AbstractCommandController
 控制器從請(qǐng)求接受一個(gè)或多個(gè)參數(shù),并將他們綁定到一個(gè)對(duì)象。還可以執(zhí)行參數(shù)驗(yàn)證。
 
Form
 AbstractFormController

SimpleFormController
 需要向用戶(hù)顯示一個(gè)記錄表單,并且處理輸入表單的數(shù)據(jù)。
 
Wizard
 AbstractWizardFormController
 需要使用戶(hù)經(jīng)過(guò)一個(gè)復(fù)雜的多頁(yè)的表單,并最終作為一個(gè)表單來(lái)處理。
 

8.3.1 編寫(xiě)簡(jiǎn)單的控制器
         將你的Controller實(shí)現(xiàn)為AbstractController的子類(lèi)。HandleRequestInternal方法是主要執(zhí)行的方法。

ModelAndView簡(jiǎn)介

         ModelAndView對(duì)象完全封裝view和model數(shù)據(jù)。

纏繞Controller

8.3.2 處理命令
         擴(kuò)展AbstractCommandController。這個(gè)控制器自動(dòng)地將參數(shù)綁定到命令對(duì)象,而且提供掛鉤,使你能夠插入驗(yàn)證器來(lái)確保參數(shù)是可用的。

         Handler方法還接受一個(gè)Command參數(shù)。

8.3.3 處理form提交
         Form控制器接受GET請(qǐng)求時(shí)顯示表單,接受POST請(qǐng)求時(shí)處理表單,控制器知道何時(shí)重新顯示表單,因此用戶(hù)可以糾正錯(cuò)誤重新提交。

驗(yàn)證輸入

         Org.springframework.validation.Validator接口為Spring MVC提供驗(yàn)證。

8.3.4 使用wizard處理復(fù)雜表單
構(gòu)建簡(jiǎn)單的wizard controller

         擴(kuò)展AbstractWizardFormController類(lèi)。

頁(yè)面跳轉(zhuǎn)

         getTargetPage()

結(jié)束wizard

         使用特殊的請(qǐng)求參數(shù)“_finish”。

取消wizard

         增加取消按鈕,使用“_cancel”參數(shù)。

每次驗(yàn)證一個(gè)表單

         控制器的validatePage()方法,可以針對(duì)每個(gè)頁(yè)面進(jìn)行驗(yàn)證。

8.3.5 使用一個(gè)控制器處理多個(gè)動(dòng)作
         MultiActionController。

         根據(jù)URL模式確定方法名。

解決方法名稱(chēng)

         MultiActionController基于方法名稱(chēng)resolver來(lái)解決方法名稱(chēng)。缺省的方法名稱(chēng)解決器是InternalPathMethodNameResolver。Spring提供了另外兩種:ParameterMethodNameResolver和PropertiesMethodNameResolver。

8.3.6 使用Throwaway控制器
         Throwaway控制器充當(dāng)自己的Command對(duì)象。

8.4 解決views
         在Spring MVC中,一個(gè)view是一個(gè)為用戶(hù)產(chǎn)生結(jié)果的bean。View resolver是任何實(shí)現(xiàn)ViewResolver接口的bean。

         Spring提供了ViewResolver的四個(gè)實(shí)現(xiàn):

<!--[if !supportLists]-->n  <!--[endif]-->InternalResourceViewResolver—將邏輯view名稱(chēng)解決為View對(duì)象,這些對(duì)象是使用模板文件資源生成。(如JSPs和Velocity模板)

<!--[if !supportLists]-->n  <!--[endif]-->BeanNameViewResolver—將邏輯view名稱(chēng)解決為DispatcherServlet的應(yīng)用程序上下文中的View Beans。

<!--[if !supportLists]-->n  <!--[endif]-->ResourceBundlerViewResolver—將邏輯View名稱(chēng)解決為包含在ResourceBundle中的View對(duì)象。

<!--[if !supportLists]-->n  <!--[endif]-->XmlViewResolver—從XML文件解決View beans,這個(gè)XML文件是與DispatcherServlet的應(yīng)用程序上下文分開(kāi)的。

8.4.1 使用模板views
         InternalResourceViewResolver。如果使用jstl,可以將viewClass屬性設(shè)置成JstlView。

8.4.2 解決view beans
         BeanNameViewResolver。在上下文配置文件中聲明為<bean>。View對(duì)象也要在上下文配置文件中聲明為<bean>。

在分離的XML文件中聲明view beans。

         XmlFileViewResolver。

從Resouce bundle解決view

         ResourceBundleViewResolver。

8.4.3 選擇View Resolver
         推薦使用InternalResourceViewResolver。如果你的視圖將通過(guò)自定義的View生成,則建議使用BeanNameViewResolver。

使用多個(gè)View Resolver

         可以使用多個(gè),使用order屬性設(shè)定優(yōu)先級(jí)。

8.5 使用Spring的綁定標(biāo)簽
         Spring標(biāo)簽庫(kù)。

8.6 處理異常
         使用SimpleMappingExceptionResolver來(lái)處理異常。在<bean>定義中聲明要處理的異常。

第九章 View層的其他選擇
9.1 使用Velocity模板
9.1.1 聲明Velocity view
         Velocity不是基于標(biāo)簽,而是基于自己的語(yǔ)言VTL。

9.1.2 配置Velocity引擎
         在Spring配置文件中聲明VelocityConfigurer bean。通過(guò)resourceLoaderPath告訴Velocity到何處找到模板。

9.1.3 解決Velocity view
         在上下文配置文件中聲明VelocityViewResolver。

9.1.4 格式化日期和數(shù)字
         通過(guò)指定VelocityViewResolver的dateToolAttribute和numberToolAttribute屬性。

9.1.5 暴露request和session屬性
         使用VelocityViewResolver的exposeRequestAttributes和exposeSessionAttributes屬性。

9.1.6 在Velocity中綁定表單字段
         Spring自帶了幾個(gè)Velocity宏。

9.2 使用FreeMaker
         FreeMaker比Velocity復(fù)雜一些,但是功能也更強(qiáng)大一些。具有內(nèi)置的日期數(shù)字格式化,移除空格等功能。

9.2.1 構(gòu)造FreeMaker view
         具有內(nèi)置的格式化支持。

9.2.2 配置FreeMaker引擎
         聲明FreeMakerConfigurer。

9.2.3 解決FreeMaker view
         FreeMakerViewResolver。

9.2.4 在FreeMaker中綁定表單字段
         使用相應(yīng)的FreeMaker宏。

9.3 使用Tiles設(shè)計(jì)頁(yè)面布局
9.3.1 Tiles view
         寫(xiě)好模板后,下一步是創(chuàng)建Tiles定義文件。

配置Tiles

         TilesConfigurer。

解決Tiles view

         使用InternalResourceViewResolver即可,只要將viewClass設(shè)定為T(mén)ilesView。它將邏輯view名稱(chēng)解決為T(mén)iles定義。

9.3.2 Tiles控制器
         Tiles的每一個(gè)組件都可以有自己?jiǎn)为?dú)的控制器。只要在definition文件中設(shè)定每一個(gè)definition的controllerClass。

9.4 產(chǎn)生非HTML輸出
9.4.1 產(chǎn)生Excel表格
         Spring提供了AbstractExcelView,只要繼承此類(lèi),實(shí)現(xiàn)它的buildExcelDocument()方法即可。它是基于POI的。

9.4.2 產(chǎn)生PDF文檔
         AbstractPdfView。基于iText。

9.4.3 產(chǎn)生其他非HTML文件
         只要實(shí)現(xiàn)View接口的render方法。

第十章 使用其他Web框架
10.1 使用Struts
10.1.1 注冊(cè)Spring插件
         為使Struts可以訪(fǎng)問(wèn)Spring管理的beans,必需注冊(cè)struts插件,使其知道Spring上下文環(huán)境的存在。在struts-config.xml中注冊(cè)ContextLoaderPlugin。

10.1.2 實(shí)現(xiàn)可訪(fǎng)問(wèn)Spring的Struts Action
         使Action繼承ActionSupport類(lèi)。

10.1.3 代理Actions
         DelegatingActionProxy。

將Actions纏繞為Spring beans

         將Actions注冊(cè)為Spring beans。讓代理來(lái)找Actions。

使用request代理

         DelegatingRequestProcessor。

10.2 使用Tapestry
         略。

10.3 與JSF集成
         略。

10.4 與WebWork集成
         略。

第十一章 Spring應(yīng)用程序的安全性
         本章介紹使用Spring AOP和servlet過(guò)濾器來(lái)保證應(yīng)用程序安全性。

11.1 Acegi安全系統(tǒng)簡(jiǎn)介
         Acegi安全框架為基于Spring的應(yīng)用程序提供聲明式安全。它提供一組beans集合,可以配置于Spring上下文環(huán)境,充分利用Spring對(duì)AOP和依賴(lài)注入的支持。

         Acegi使用監(jiān)聽(tīng)request的servlet過(guò)濾器來(lái)執(zhí)行認(rèn)證和實(shí)施安全性。

         Acegi可以在較低級(jí)別上通過(guò)保證方法調(diào)用的安全實(shí)施安全性。通過(guò)使用Spring AOP,Acegi代理對(duì)象,應(yīng)用方面來(lái)保證用戶(hù)具有適當(dāng)?shù)氖跈?quán)來(lái)調(diào)用安全方法。

11.1.1 安全監(jiān)聽(tīng)器
         安全監(jiān)聽(tīng)器好比是門(mén)栓。

11.1.2 認(rèn)證管理器
         安全監(jiān)聽(tīng)器的第一個(gè)“鎖眼”是認(rèn)證管理器。認(rèn)證管理器負(fù)責(zé)決定你是誰(shuí)。通常通過(guò)用戶(hù)名和密碼來(lái)認(rèn)證。

11.1.3 訪(fǎng)問(wèn)決策管理器
         一旦Acegi決定你是誰(shuí),它就必需決定你是否被授權(quán)訪(fǎng)問(wèn)安全資源。訪(fǎng)問(wèn)決策管理器是Acegi的第二個(gè)鎖眼。

11.1.4 運(yùn)行管理器
         運(yùn)行管理器可以用來(lái)替換你的認(rèn)證,使你可以訪(fǎng)問(wèn)更深層的安全資源。

11.2 管理認(rèn)證
         在Acegi中,認(rèn)證管理器的工作是確定用戶(hù)的身份。認(rèn)證管理器通過(guò)net.sf.acegisecurity.AuthenticationManager接口定義:

public interface AuthenticationManager {

public Authentication authenticate(Authentication authentication)

throws AuthenticationException;

}

         Authenticate方法接受一個(gè)Authentication對(duì)象,并試圖認(rèn)證用戶(hù)。如果認(rèn)證成功,則返回一個(gè)完整的Authentication對(duì)象,包含用戶(hù)的授權(quán)信息。如果認(rèn)證失敗,則拋出AuthenticationException異常。Acegi提供了AuthenticationManager的一個(gè)實(shí)現(xiàn):ProviderManager。

11.2.1 配置ProviderManager
         ProviderManager將認(rèn)證的職責(zé)代理到一個(gè)或多個(gè)認(rèn)證提供者。ProviderManager的思想是使你能夠?qū)Χ鄠€(gè)身份管理資源認(rèn)證用戶(hù)。ProviderManager自身并不進(jìn)行認(rèn)證,而是遍歷所有的認(rèn)證提供者,直到其中一個(gè)成功認(rèn)證用戶(hù),或者遍歷完所有的認(rèn)證提供者。可以在Spring配置文件中以如下形式配置ProviderManager:

<bean id="authenticationManager"

class="net.sf.acegisecurity.providers.ProviderManager">

<property name="providers">

<list>

<ref bean="daoAuthenticationProvider"/>

<ref bean="passwordDaoProvider"/>

</list>

</property>

</bean>

         通過(guò)providers屬性給予ProviderManager一組認(rèn)證提供者。認(rèn)證提供者由net.sf.acegisecurity.provider.AuthenticationProvider接口定義。Spring提供了這個(gè)接口的多個(gè)實(shí)現(xiàn),如下表所示:

認(rèn)證提供者
 目的
 
net.sf.acegisecurity.adapters.

AuthByAdapterProvider
 使用容器適配器認(rèn)證。
 
net.sf.acegisecurity.providers.cas.

CasAuthenticationProvider
 使用CAS(Yale Central Authentication Service)認(rèn)證。
 
net.sf.acegisecurity.providers.dao.

DaoAuthenticationProvider
 從數(shù)據(jù)庫(kù)獲取用戶(hù)信息,包括用戶(hù)名和密碼。
 
net.sf.acegisecurity.providers.jaas.

JaasAuthenticationProvider
 從JAAS登錄配置獲取用戶(hù)信息。
 
net.sf.acegisecurity.providers.dao.

PasswordDaoAuthenticationProvider
 從數(shù)據(jù)庫(kù)獲取用戶(hù)信息,但是讓底層的數(shù)據(jù)存儲(chǔ)執(zhí)行實(shí)際的認(rèn)證。
 
net.sf.acegisecurity.providers.rcp.

RemoteAuthenticationProvider
 使用遠(yuǎn)程服務(wù)認(rèn)證。
 
net.sf.acegisecurity.runas.

RunAsImplAuthenticationProvider
 認(rèn)證其身份由運(yùn)行管理器支配的用戶(hù)。
 
net.sf.acegisecurity.providers.

TestingAuthenticationProvider
 單元測(cè)試。自動(dòng)認(rèn)為T(mén)estingAuthenticationProvider是可用的,不應(yīng)在產(chǎn)品中使用。
 

11.2.2 使用數(shù)據(jù)庫(kù)認(rèn)證
         DaoAuthenticationProvider使用它的DAO獲取用戶(hù)名和密碼,并使用獲取的用戶(hù)名和密碼認(rèn)證用戶(hù)。PasswordAuthentcationProvider將認(rèn)證的責(zé)任推給了DAO。

聲明DAO認(rèn)證提供者

<bean id="authenticationProvider" class="net.sf.acegisecurity.

providers.dao.DaoAuthenticationProvider">

<property name="authenticationDao">

<ref bean="authenticationDao"/>

</property>

</bean>

         authenticationDao屬性標(biāo)識(shí)用來(lái)從數(shù)據(jù)庫(kù)獲取用戶(hù)信息的bean。這個(gè)屬性應(yīng)當(dāng)是net.sf.acegisecurity.providers.dao.AuthenticationDao。Acegi提供了AuthenticationDao的兩個(gè)實(shí)現(xiàn):InMemoryDaoImpl和JdbcDaoImpl。

使用內(nèi)存Dao

<bean id="authenticationDao" class="net.sf.acegisecurity.

providers.dao.memory.InMemoryDaoImpl">

<property name="userMap">

<value>

palmerd=4moreyears,ROLE_PRESIDENT

bauerj=ineedsleep,ROLE_FIELD_OPS,ROLE_DIRECTOR

myersn=traitor,disabled,ROLE_FIELD_OPS

</value>

</property>

</bean>

         局限性在于必需編輯Spring配置文件并重新部署應(yīng)用來(lái)管理安全。

聲明JDBC DAO

<bean id="authenticationDao"

class="net.sf.acegisecurity.providers.dao.jdbc.JdbcDaoImpl">

<property name="dataSource">

<ref bean="dataSource"/>

</property>

</bean>

         JdbcDaoImpl假定你在數(shù)據(jù)庫(kù)中使用特定的表來(lái)存儲(chǔ)用戶(hù)信息,特別地,使用“Users”表和“Authorities”表。但可以使用usersByUserNameQuery來(lái)設(shè)定如何獲取用戶(hù)信息。JdbcDaoImpl還提供了usersByUserNameMapping屬性來(lái)引用MappingSqlQuery接口。這個(gè)接口將ResultSet轉(zhuǎn)換成UserDetails對(duì)象。

         同樣可以通過(guò)設(shè)定authoritiesByUserNameQuery和authoritiesByUserNameMapping屬性來(lái)獲取授權(quán)信息。

使用加密密碼

         Acegi提供了三個(gè)密碼解碼器:

<!--[if !supportLists]-->n  <!--[endif]-->PlainTextPasswordEncoder(缺?。?-不執(zhí)行任何解碼。

<!--[if !supportLists]-->n  <!--[endif]-->Md5PasswordEncoder—執(zhí)行MD5解碼。

<!--[if !supportLists]-->n  <!--[endif]-->ShaPasswordEncoder—執(zhí)行安全哈希算法解碼。

可以通過(guò)更改DaoAuthenticationProvider的passwordEncoder屬性來(lái)設(shè)定解碼器。還可以對(duì)解碼器使用salt source,即密鑰(encrytion key)。Acegi提供了兩個(gè)salt sources:ReflectionSaltSource和SystemWideSaltSource。

緩存用戶(hù)信息

         DaoAuthenticationProvider通過(guò)UserCache接口的實(shí)現(xiàn)來(lái)支持用戶(hù)信息的緩存。

public interface UserCache {

public UserDetails getUserFromCache(String username);

public void putUserInCache(UserDetails user);

public void removeUserFromCache(String username);

}

         Acegi提供了兩種實(shí)現(xiàn):NullUserCache和EhCacheBasedUserCache。前者不執(zhí)行任何緩存。后者基于開(kāi)源的ehcache項(xiàng)目。

<bean id="userCache" class="net.sf.acegisecurity.

providers.dao.cache.EhCacheBasedUserCache">

<property name="minutesToIdle">15</property>

</bean>

minutesToIdle設(shè)定在沒(méi)有讀取緩存的情況下,用戶(hù)信息應(yīng)當(dāng)駐留多長(zhǎng)時(shí)間。

11.2.3 使用LDAP中心庫(kù)進(jìn)行認(rèn)證
         PasswordDaoAuthenticationProvider將實(shí)際的認(rèn)證代理給它的Dao。

<bean id="authenticationProvider" class="net.sf.acegisecurity.

providers.dao.PasswordDaoAuthenticationProvider">

<property name="passwordAuthenticationDao">

<ref bean="passwordAuthenticationDao"/>

</property>

</bean>

這個(gè)Dao屬性應(yīng)當(dāng)實(shí)現(xiàn)PasswordAuthenticationDao接口。Acegi不提供這個(gè)接口的實(shí)現(xiàn),但是在它的sandbox中有LdapPasswordAuthenticationDao實(shí)現(xiàn)。

11.2.4 使用Acegi和Yale CAS支持單點(diǎn)登錄
         當(dāng)在Acegi中使用CAS時(shí),Acegi代表應(yīng)用程序驗(yàn)證CAS ticket。它通過(guò)使用CasAuthenticationProvider來(lái)完成。

11.3 控制訪(fǎng)問(wèn)
         如同認(rèn)證管理器負(fù)責(zé)建立用戶(hù)的身份一樣,訪(fǎng)問(wèn)決策管理器負(fù)責(zé)決定用戶(hù)是否有適當(dāng)?shù)臋?quán)限來(lái)訪(fǎng)問(wèn)安全資源。訪(fǎng)問(wèn)決策管理器由net.sf.acegisecurity.AccessDecisionManager接口定義。

public interface AccessDecisionManager {

public void decide(Authentication authentication, Object object,

ConfigAttributeDefinition config)

throws AccessDeniedException;

public boolean supports(ConfigAttribute attribute);

public boolean supports(Class clazz);

}

         Supports方法考量資源的類(lèi)型和配置屬性(安全資源的訪(fǎng)問(wèn)必需條件),據(jù)此決定訪(fǎng)問(wèn)決策管理器是否可以對(duì)資源做出訪(fǎng)問(wèn)決策。Decide方法是作出最終決策的地方,如果它正常返回不拋出異常的話(huà),則授權(quán)對(duì)安全資源的訪(fǎng)問(wèn)。否則,拒絕訪(fǎng)問(wèn)。

11.3.1 投票訪(fǎng)問(wèn)決策
         Acegi提供了AcessDecisionManager的三個(gè)實(shí)現(xiàn):

<!--[if !supportLists]-->n  <!--[endif]-->net.sf.acegisecurity.vote.AffirmativeBased

<!--[if !supportLists]-->n  <!--[endif]-->net.sf.acegisecurity.vote.ConsensusBased

<!--[if !supportLists]-->n  <!--[endif]-->net.sf.acegisecurity.vote.UnanimousBased

這三者的區(qū)別之處在于訪(fǎng)問(wèn)決策管理器如何作出最后的決策,如下表所示:

訪(fǎng)問(wèn)決策管理器
 如何決策
 
AffirmativeBased
 如果至少有一個(gè)投票者投票授權(quán)訪(fǎng)問(wèn),則允許訪(fǎng)問(wèn)。
 
ConsensusBased
 如果大多數(shù)投票者投票授權(quán)訪(fǎng)問(wèn),則允許訪(fǎng)問(wèn)。
 
UnanimousBased
 只有當(dāng)沒(méi)有任何投票者拒絕訪(fǎng)問(wèn)的時(shí)候,才允許訪(fǎng)問(wèn)。
 

例如,以下對(duì)UnanimousBased的配置:

<bean id="accessDecisionManager"

class="net.sf.acegisecurity.vote.UnanimousBased">

<property name="decisionVoters">

<list>

<ref bean="roleVoter"/>

</list>

</property>

</bean>

11.3.2 決定如何投票
         訪(fǎng)問(wèn)決策投票者是任何實(shí)現(xiàn)接口net.sf.acegisecurity.vote.AccessDecisionVoter接口的對(duì)象:

public interface AccessDecisionVoter {

public static final int ACCESS_GRANTED = 1;

public static final int ACCESS_ABSTAIN = 0;

public static final int ACCESS_DENIED = -1;

public boolean supports(ConfigAttribute attribute);

public boolean supports(Class clazz);

public int vote(Authentication authentication, Object object,

ConfigAttributeDefinition config);

}

         Acegi提供了RoleVoter實(shí)現(xiàn)。處理基于角色的安全資源配置。

11.3.3 處理角色禁止
         如果所有的投票都是拒絕訪(fǎng)問(wèn),則訪(fǎng)問(wèn)決策管理器拒絕對(duì)安全資源的訪(fǎng)問(wèn)。但是可以覆蓋這種缺省設(shè)置。

11.4 保護(hù)Web應(yīng)用
         Acegi對(duì)Web安全性的支持主要基于servlet過(guò)濾器。這些過(guò)濾器截獲傳入的請(qǐng)求,應(yīng)用一些安全性處理,然后請(qǐng)求才有應(yīng)用程序處理。Acegi提供了六種過(guò)濾器。如下表所示:

過(guò)濾器
 目的
 
通道處理過(guò)濾器
 確保請(qǐng)求是由安全的通道傳輸(如https)。
 
認(rèn)證處理過(guò)濾器
 接受認(rèn)證請(qǐng)求,將請(qǐng)求傳輸給認(rèn)證管理器來(lái)執(zhí)行認(rèn)證。
 
CAS處理過(guò)濾器
 接受CAS票據(jù)作為一個(gè)Yale CAS已經(jīng)認(rèn)證一個(gè)用戶(hù)的證明。
 
HTTP基本認(rèn)證過(guò)濾器
 處理由HTTP基本認(rèn)證執(zhí)行的認(rèn)證。
 
集成過(guò)濾器
 處理請(qǐng)求間的認(rèn)證存儲(chǔ)(例如,在HTTP Session中的)。
 
安全執(zhí)行過(guò)濾器
 確保用戶(hù)已經(jīng)被認(rèn)證,具有訪(fǎng)問(wèn)受保護(hù)Web資源的屬性授權(quán)必要條件。
 

         當(dāng)請(qǐng)求提交到Acegi保護(hù)的Web應(yīng)用時(shí),請(qǐng)求被傳遞給所有Acegi的過(guò)濾器。

11.4.1 代理Acegi過(guò)濾器
         Acegi提供了FilterToBeanProxy,它將工作代理給Spring應(yīng)用上下文中的bean。代理bean同其他servlet過(guò)濾器一樣,實(shí)現(xiàn)javax.servlet.Filter接口,只是在Spring配置文件中配置。

         在web.xml中只包含對(duì)FilterToBeanProxy的聲明。最后,還要將過(guò)濾器與URL模式關(guān)聯(lián)。

11.4.2 執(zhí)行Web安全性
         安全性執(zhí)行過(guò)濾器處理請(qǐng)求截獲,決定請(qǐng)求是否是安全的,并給認(rèn)證和訪(fǎng)問(wèn)決策管理器一個(gè)機(jī)會(huì)來(lái)驗(yàn)證用戶(hù)的身份和特權(quán)。

使用過(guò)濾器安全截獲器

11.4.3 處理login
11.4.6 使用Acegi標(biāo)簽庫(kù)
         只有一個(gè)標(biāo)簽<authz:authorize>。

11.5 方法調(diào)用安全性
11.5.1 創(chuàng)建安全性方面
        

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶(hù)發(fā)布,不代表本站觀(guān)點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶(hù) 評(píng)論公約

    類(lèi)似文章 更多