序言
軟件開發(fā)者具有高超的手藝必須具備幾個特征。首先,他們必需具有良好的分析思考能力和問題解決能力。開發(fā)者的主要角色是創(chuàng)造軟件來解決業(yè)務(wù)問題。這要求分析客戶需要和提出成功的具有創(chuàng)新性的解決方案。
軟件開發(fā)者還必須具有好奇心。軟件業(yè)中的開發(fā)總是不斷更換目標(biāo),不斷進化。新框架,新技術(shù),新語言和新方法學(xué)總是不斷地出現(xiàn)。每一樣都是新工具,需要我們掌握和加入到我們的工具箱中,使我們能夠更好更快地完成我們的工作。
最后一個特征,也是最受珍愛的特征就是“惰性”。這種惰性刺激開發(fā)者努力工作來尋找需要最少工作量的解決方案。正是有了這種好奇心,惰性和我們擁有的所有的分析能力,四年前我們倆開始行動起來,尋找開發(fā)軟件的新方法。
當(dāng)今時代,Java社區(qū)的開源軟件已經(jīng)達到了一個關(guān)鍵的數(shù)量。無數(shù)的開源框架在Java世界遍地開花。為決定選用一種框架,它必需能極大滿足我們的需要—它必需能滿足我們80%的需求。對于任何不能滿足我們的功能,框架必需容易地擴展來包含這些功能。擴展并不意味著隨便組裝一下,這樣以后會感到惡心,而是意味著以優(yōu)雅的方式來擴展。這樣的要求有點過分,哈?
我們立即采用的這樣的框架之一就是ant。從一開始我們就可以看出,ant是由一個能理解我們構(gòu)建Java應(yīng)用的痛楚的開發(fā)者創(chuàng)建的。從從選用ant的那一刻起,不再有javac,不再有CLASSPATH。所需的一切只是直觀的(即使有時候比較繁瑣)XML配置文件。生活(和構(gòu)建)變得簡單。
隨著我們的進展,我們開始采用越來越多的工具。Eclipse成為我們的IDE選擇。Log4J成為我們的(也是所有其他人的)缺省的日志工具。Lucene替代了我們的商業(yè)搜索解決方案。所有這些工具都滿足我們的準(zhǔn)則:滿足需求的同時,易用,易理解,易擴展。
但仍然缺少一些東西,這些偉大的工具如ant和Eclipse,被設(shè)計用來輔助開發(fā)軟件,或者滿足某一特定應(yīng)用需要,如使用Lucene進行搜索和使用log4J來進行記錄日志。他們都沒有解決企業(yè)應(yīng)用的核心:持久化,事務(wù)和與其他企業(yè)資源集成。
直到去年我們發(fā)現(xiàn)了令人驚奇的兩大企業(yè)武器Spring和Hibernate。這兩個框架滿足了幾乎所有的中間層及數(shù)據(jù)層的需求。
我們首先采用了Hibernate。它是最直觀的特性最多的ORM工具。但直到采用了Spring我們才使代碼看上去更加優(yōu)雅。使用Spring的控制反轉(zhuǎn)(Inversion of Control)我們可以拋棄所有的自定義工廠及配置器。事實上這也是我們將Spring集成到我們應(yīng)用中的最初原因。它的wiring使我們的應(yīng)用程序更加合理化,移除了我們自制的解決方案。(每一個開發(fā)者都會編寫自己的框架,但是有時候必需得放棄)。
我們很快發(fā)現(xiàn)了一個額外的好處,Spring提供了對Hibernate的很容易地繼承。這使我們不必自制對Hibernate的繼承類,可以直接使用Spring的支持。這引領(lǐng)我們走進Spring對透明持久化的支持。
如果你稍加注意的話你應(yīng)該發(fā)現(xiàn)了一個模式。越多地使用Spring,你就能發(fā)現(xiàn)越多的Spring的特性。而且我們發(fā)現(xiàn)的每一個特性都讓我們感受到樂趣。它的Web MVC框架在好幾個應(yīng)用中很好地工作。它的AOP支持在好幾個地方,特別是安全方面對我們很有幫助。它的JDBC支持對于小型程序很有幫助。對了,我們還用它來做日程。還有JNDI訪問。還有email集成。談及能不能滿足開發(fā)的需要,Spring可謂是雪中送炭。
我們非常喜歡Spring,并且決定應(yīng)該寫一本關(guān)于它的書。幸運地是,我們之中已經(jīng)有人為manning寫過一本書,知道寫書是怎么回事。很快“應(yīng)該寫這樣一本書的人”成為了我們。我們寫這本書的目的是傳遞Spring的精神。Spring框架是我們工作的樂趣—我們預(yù)測也會成為您的樂趣。最后,我們希望這本書能成為你到達終點的快樂的交通工具。
第一部分 Spring基礎(chǔ)
介紹Spring框架的兩個核心特性:控制反轉(zhuǎn)和面向方面編程。
第一章 快速進入Spring世界
一切都起源于Bean。然而復(fù)雜的應(yīng)用通常需要諸如事務(wù)支持,安全和分布式計算之類的服務(wù),JavaBean規(guī)范并沒有直接支持這些服務(wù)。因此Sun發(fā)布了EJB規(guī)范。盡管有很多基于EJB的成功的應(yīng)用,然而EJB并沒有取得其預(yù)期的目的:簡化企業(yè)應(yīng)用開發(fā)。新技術(shù),如AOP和IOC使JavaBean同樣可以具有EJB的強大功能,這就是Spring出現(xiàn)的背景。
1.1 為什么選擇Spring?
簡而言之,Spring使開發(fā)企業(yè)應(yīng)用更容易。
1.1.1 J2EE開發(fā)者的一天
為開發(fā)一個組件(component)必需編寫幾個類,還要為JavaBean創(chuàng)建部署描述子。為節(jié)省力氣,引入XDoclet。XDoclet是一個代碼生成工具,可以從一個源文件生成所有需要的EJB文件。為驗證代碼邏輯的正確性,需要書寫幾個測試用例,問題是測試代碼必需運行在EJB容器中。為解決這個問題,創(chuàng)建一個Servlet來運行這些測試用例。因為所有的J2EE容器都支持Servlet,所以測試代碼可以與EJB運行于同一個容器。但是如果測試用例沒有通過,必需修改代碼,重新啟動容器,再次測試,重啟容器的時間就被浪費掉了。
1.1.2 Spring的承諾
EJB之所以復(fù)雜是因為它是用來解決復(fù)雜問題的,如分布式對象和遠程事務(wù)。不幸的是,很多企業(yè)級項目并沒有達到這種的復(fù)雜程度,但仍然要承擔(dān)EJB的多個Java文件和部署描述子,以及重量級容器。使用EJB,無論你要解決的問題是否復(fù)雜,你的應(yīng)用程序都很復(fù)雜,然而對于Spring來說,應(yīng)用程序的復(fù)雜程度是與要解決的問題的復(fù)雜程度成正比的。Spring遵循的邏輯是:J2EE應(yīng)該易于使用,為保持這種邏輯,Spring的設(shè)計遵循以下信念:
<!--[if !supportLists]-->n <!--[endif]-->好的設(shè)計比起底層的實現(xiàn)技術(shù)更重要。
<!--[if !supportLists]-->n <!--[endif]-->通過接口松散耦合的JavaBean是很好的模型。
<!--[if !supportLists]-->n <!--[endif]-->代碼應(yīng)該易于測試。
好的設(shè)計比起底層的實現(xiàn)技術(shù)更重要。
不必為了使用EJB技術(shù)而使用它,真正需要它提供的服務(wù)才去使用它。
通過接口松散耦合的JavaBean是很好的模型
使用Spring,JavaBean通過接口依賴于協(xié)作。你所需要做的就是創(chuàng)建彼此通過接口通信的類,其他的都交給Spring來做。
代碼應(yīng)該易于測試
測試不需啟動J2EE容器,因為你測試的是POJO。
1.2 Spring是什么?
Spring是一個開源的框架,由Rod Johnson創(chuàng)建。Spring的創(chuàng)建用來解決企業(yè)級應(yīng)用開發(fā)的復(fù)雜性。Spring使我們能夠使用普通的JavaBean來取得之前使用EJB達到的效果。然而,Spring的作用并不限于服務(wù)器端開發(fā)。任何應(yīng)用程序都可得益于Spring的簡單性,易測試性和松散耦合性。
簡單地講,Spring是一個輕量級的控制反轉(zhuǎn)和面向方面編程的框架。以下是這個描述的詳細解釋:
<!--[if !supportLists]-->n <!--[endif]-->輕量級。從兩個方面:大小和預(yù)處理。Spring發(fā)布版本的就是一個JAR包,大小就是1M多一點。經(jīng)Spring預(yù)處理的時間幾乎可以忽略。
<!--[if !supportLists]-->n <!--[endif]-->控制反轉(zhuǎn)。Spring通過控制反轉(zhuǎn)來達到松散耦合。通過IOC,對象被動地接受他們的依賴,而不是主動地查找和創(chuàng)建他們的依賴。
<!--[if !supportLists]-->n <!--[endif]-->面向方面。Spring對AOP的支持使業(yè)務(wù)邏輯從系統(tǒng)服務(wù)中分離出來。
<!--[if !supportLists]-->n <!--[endif]-->容器。從包容和管理應(yīng)用程序?qū)ο笊芷诘囊饬x上來講,Spring是一個容器。你可以配置你的每一個JavaBean如何被創(chuàng)建,以及他們之間的關(guān)聯(lián)。不應(yīng)當(dāng)把Spring同重量級的EJB容器混淆,因為EJB容器是重量級的而且工作起來很笨重。
<!--[if !supportLists]-->n <!--[endif]-->框架。Spring組合簡單的JavaBean成復(fù)雜的的應(yīng)用程序。在Spring中應(yīng)用程序?qū)ο笸ǔMㄟ^XML文件聲明式地組合。Spring提供很多基礎(chǔ)的功能,如事務(wù)管理,持久化框架集成等,你要做的就是開發(fā)業(yè)務(wù)邏輯。
1.2.1 Spring模塊
Spring框架由七個良好定義的模塊組成。這些模塊為你提供了企業(yè)級應(yīng)用程序開發(fā)的所有東西,但是你也不必全盤接受,你可以只選擇適合你的應(yīng)用程序的模塊而忽略其他模塊。所有的Spring模塊都建立在核心容器之上,容器定義JavaBeans如何被創(chuàng)建,配置和管理。核心容器的類會被隱式地調(diào)用來配置你的應(yīng)用程序。
核心容器
Spring的核心容器提供Spring框架的基礎(chǔ)功能。在這個容器中有一個BeanFactory,它是任何基于Spring的應(yīng)用程序的核心。BeanFactory是工廠模式的一個實現(xiàn),他應(yīng)用IOC將應(yīng)用程序配置和依賴說明從實際的應(yīng)用程序代碼中分離出來。
應(yīng)用程序上下文模塊
核心模塊的BeanFactory使Spring成為一個容器,而應(yīng)用程序上下文模塊使Spring成為一個框架。這個模塊擴展了BeanFactory,增加了對I18N消息,應(yīng)用程序生命周期事件和驗證的支持。
此外,這個模塊提供了一些企業(yè)級服務(wù),如email,JNDI訪問,EJB集成,遠程訪問和計劃任務(wù)。還包括對模板框架如Velocity和FreeMaker集成的支持。
Spring的AOP模塊
Spring在AOP模塊對AOP提供了豐富的支持。這個模塊提供了開發(fā)自己的基于Spring的應(yīng)用程序的方面的基礎(chǔ)。
為確保Spring和其他AOP框架之間的互操作性,Spring的很多對AOP的支持都是基于AOP Alliance定義的API。AOP Alliance是一個開源的項目,它的目標(biāo)是通過定義組通用的接口和組件,提高對AOP的采用率和不同AOP實現(xiàn)之間的互操作性。
Spring模塊還為Spring引入了元數(shù)據(jù)編程。通過Spring的元數(shù)據(jù)支持,你可以向源代碼中加入標(biāo)注(Annotation)來指示在何處通過何種方式來應(yīng)用方面(Aspects)。
JDBC抽象和DAO模塊
Spring的JDBC和DAO模塊使數(shù)據(jù)庫代碼更加整潔和簡單,并且防止一些由于關(guān)閉數(shù)據(jù)庫資源引起失敗帶來的問題。這個模塊還在由多個數(shù)據(jù)庫服務(wù)器給出的錯誤消息之上建立了一層有意義的異常。
另外,這個模塊使用了Spring的AOP模塊為Spring應(yīng)用程序中的對象提供了事務(wù)管理服務(wù)。
ORM集成模塊
Spring為那些喜歡使用ORM功能的人提供了ORM模塊。Spring沒有試圖實現(xiàn)自己的ORM解決方案,只是為幾個流行的ORM框架,如Hibernate,JDO和iBATIS SQL Maps提供了外掛功能。Spring的事務(wù)管理支持這些ORM框架和JDBC。
Spring的Web模塊
Web上下文模塊建立于應(yīng)用程序上下文模塊,提供了一個適合于基于Web的應(yīng)用的上下文。此外,這個模塊提供了對一些基于Web的任務(wù)的支持,如對于文件上傳的multipart請求的透明處理和將請求參數(shù)程序化綁定到業(yè)務(wù)對象。還包含對Struts的集成的支持。
SpringMVC框架
Spring為構(gòu)建Web應(yīng)用程序帶來了一個完整特性的MVC框架。盡管Spring可以容易地與其他MVC框架如Struts集成,但是Spring的MVC框架使用IOC提供控制邏輯與業(yè)務(wù)邏輯的清晰分離。它允許你聲明式地將請求參數(shù)綁定到業(yè)務(wù)邏輯。還有就是Spring的MVC框架可以利用Spring的其他服務(wù),如I18N消息機制和驗證機制。
1.3 快速上手
讓基于Spring的應(yīng)用程序不同的是類之間是如何配置和相互引入的。典型地,Spring應(yīng)用程序使用一個XML文件來描述如何配置類,這個XML文件叫做Spring配置文件。
GreetingServiceImpl類的greeting字段有兩個方法可以設(shè)置,通過構(gòu)造函數(shù)或?qū)傩缘膕etter方法。不明顯的是誰來調(diào)用構(gòu)造函數(shù)或setter方法,答案是通過Spring配置文件讓Spring容器來調(diào)用。
Spring配置文件的根元素是<beans>元素。<bean>元素告訴Spring容易一個類的信息和如何配置這個類。<bean>元素的子元素<property>元素用來設(shè)定類的屬性。當(dāng)然也可以通過構(gòu)造函數(shù)的入?yún)碓O(shè)定屬性。
1.4 理解控制反轉(zhuǎn)IOC
IoC處于Spring框架的核心。IoC聽上去可能比較復(fù)雜,實際上,通過在項目中使用IoC,你會發(fā)現(xiàn)你的代碼變得更簡單,更易于理解和易于測試。
1.4.1 注入依賴
Martin Fowler為IoC造了一個更好的名字:依賴注入。
任何稍大一點的應(yīng)用程序都由多于兩個類組成,他們相互協(xié)作完成一些業(yè)務(wù)邏輯。通常每一個對象都負責(zé)取得與它協(xié)作的那些對象的引用。你會發(fā)現(xiàn),這會導(dǎo)致高耦合,難測試的代碼。
通過應(yīng)用IoC,對象在創(chuàng)建的時候,通過調(diào)度系統(tǒng)中每一個對象的外部實體給予他們的依賴。即依賴被注入對象。即IoC的意思是關(guān)于對象如何取得他們協(xié)作的對象的依賴責(zé)任的反轉(zhuǎn)。
1.4.2 IoC實踐
對于自己取得自己引用的對象的對象,無法單獨進行測試。被引用的對象被間接測試,而且被引用的對象如果沒有通過測試,可能會影響調(diào)用對象的測試結(jié)果。
問題的所在是由于耦合。降低耦合的一個通用技術(shù)是將實現(xiàn)細節(jié)隱藏在接口后面,這樣可以交換具體實現(xiàn)而不影響客戶類。但重要的是對象如何取得它引用的對象,如果是給予的方式,那么在測試用例中只要給予Mock對象,而在產(chǎn)品系統(tǒng)中給予真實的對象,就解決了不能單獨測試的問題。
這就是IoC要做的事情。
創(chuàng)建應(yīng)用程序組件之間關(guān)聯(lián)的動作被成為wiring,通常的方法是通過XML文件。
1.4.3 企業(yè)級應(yīng)用程序中的IoC
在EJB程序中,需要自己從JNDI上下文環(huán)境中查找EJB Home,然后創(chuàng)建Service,使用Spring后,由Spring容器負責(zé)設(shè)置Service。
1.5 應(yīng)用面向方面編程
面向方面編程使你能夠在應(yīng)用程序中以可復(fù)用組件的形式捕獲功能。
1.5.1 AOP簡介
AOP通常定義為一種編程技術(shù),它將軟件系統(tǒng)的不同關(guān)注方面分離。系統(tǒng)由多個組件組成,每一個組件負責(zé)一個特定的功能。像寫log,事務(wù)管理和安全之類的系統(tǒng)服務(wù)通常存在于一些核心職責(zé)是別的東西的組件。這些系統(tǒng)服務(wù)通常被稱作cross-cutting關(guān)注方面,因為它們趨向于存在系統(tǒng)的各個地方。
將這些關(guān)注方面分散于系統(tǒng)的多個組件,會對代碼引入兩個級別的復(fù)雜性:
<!--[if !supportLists]-->n <!--[endif]-->實現(xiàn)系統(tǒng)范圍關(guān)注方面的代碼,在多個組件間存在重復(fù)。這意味著如果你需要修改這些關(guān)注方面的話,你需要修改多個組件。即使你將這些關(guān)注方面抽象到單獨的模塊,對組件的影響只是一個方法調(diào)用,這個方法調(diào)用也會在多個組件中重復(fù)。
<!--[if !supportLists]-->n <!--[endif]-->組件中會散布一些與核心功能無關(guān)的代碼。
AOP能夠讓這些服務(wù)模塊化,然后以聲明的形式應(yīng)用于它們應(yīng)該影響的組件。這樣能夠使組件能夠更具有聚合性,專注于他們的關(guān)注方面,可以完全忽略需要涉及的系統(tǒng)服務(wù)。
通過AOP,可以用多層功能覆蓋核心的應(yīng)用。這些層可以靈活的聲明式地應(yīng)用于應(yīng)用程序,核心應(yīng)用程序甚至不知道他們的存在。
1.5.2 AOP實踐
實現(xiàn)MethodBeforeService接口,可以在一個方法調(diào)用時做一些事情,而被調(diào)用的類和方法甚至不知道被做的這些事情。
織補Aspect
在Spring中,Aspect通過Spring的XML文件織入對象,同Bean被繞在一起的方式差不多。
1.5.3 企業(yè)級的AOP
EJB可以通過部署描述子來聲明事務(wù)和安全策略。但是EJB比較復(fù)雜。
盡管Spring的AOP可以用來分布于應(yīng)用程序核心邏輯各處的關(guān)注方面,但它的主要工作是作為Spring對聲明式事務(wù)支持的基礎(chǔ)。Spring具有幾個方面,使對JavaBeans聲明事務(wù)策略成為可能。而Acegi安全系統(tǒng)提供對JavaBeans的聲明式安全。同所有Spring配置一樣,事務(wù)和安全策略都在Spring配置文件中描述。
使用Spring的TransactionProxyFactoryBean,使我們能夠?qū)却娴腸lass監(jiān)聽函數(shù)調(diào)用和應(yīng)用事務(wù)上下文。
1.6 Spring的代替品
1.6.1 比較Spring與EJB
選擇Spring與EJB并不是可以直接用誰取代誰。而且,你也不必要么選擇Spring,要么選擇EJB,Spring可以用來支持既有的EJBs。
EJB是一個標(biāo)準(zhǔn)
EJB是有JCP定義的規(guī)范,作為標(biāo)準(zhǔn)具有一些重要的啟示:
<!--[if !supportLists]-->n <!--[endif]-->廣泛的業(yè)界支持—有很多重要的廠商都支持這項技術(shù),如Sun,IBM,Oracle和BEA。這意味著很多年之內(nèi)EJB都會被支持和積極開發(fā),這使很多公司感覺到選擇EJB作為J2EE框架比較安全。
<!--[if !supportLists]-->n <!--[endif]-->廣泛的采用—EJB技術(shù)已經(jīng)被成千上萬的公司部署。因此EJB是大部分J2EE開發(fā)者的工具,擁有EJB技術(shù)更容易找到工作,公司使用EJB技術(shù)也更容易招聘到技術(shù)人員。
<!--[if !supportLists]-->n <!--[endif]-->有工具支持—EJB規(guī)范是一個固定的東西,這更容易地使得廠商更快地生產(chǎn)工具來幫助開發(fā)者創(chuàng)建EJB應(yīng)用程序。EJB工具具有更廣泛的選擇。
Spring和EJB的共同點
作為J2EE容器,EJB和Spring都為開發(fā)者開發(fā)應(yīng)用程序提供了強大的特性。下表列出了兩個框架的共同特性和它們的實現(xiàn)比較。
特性
EJB
Spring
事務(wù)管理
<!--[if !supportLists]-->n <!--[endif]-->必需使用JTA事務(wù)管理。
<!--[if !supportLists]-->n <!--[endif]-->支持跨越遠程方法調(diào)用的事務(wù)。
<!--[if !supportLists]-->n <!--[endif]-->通過PlatformTransactionManager接口支持多事務(wù)環(huán)境,包括JTA,Hibernate,JDO和JDBC。
<!--[if !supportLists]-->n <!--[endif]-->自身不支持分布式事務(wù)—必需與JTA事務(wù)管理器一起使用。
聲明式事務(wù)支持
<!--[if !supportLists]-->n <!--[endif]-->可以通過部署描述子聲明式地定義事務(wù)。
<!--[if !supportLists]-->n <!--[endif]-->可以使用通配符*來針對每一個方法或每一個類定義事務(wù)行為。
<!--[if !supportLists]-->n <!--[endif]-->不能聲明式地定義回滾行為,必需程序?qū)崿F(xiàn)。
<!--[if !supportLists]-->n <!--[endif]-->可以通過Spring配置文件或類的元數(shù)據(jù)聲明式地定義事務(wù)。
<!--[if !supportLists]-->n <!--[endif]-->可以顯示地或通過正則表達式定義對哪些方法應(yīng)用事務(wù)行為。
<!--[if !supportLists]-->n <!--[endif]-->可以針對每一個方法或每一個異常類型聲明式地定義回滾行為。
持久化
<!--[if !supportLists]-->n <!--[endif]-->支持程序化的Bean管理方式的持久化和聲明式的容器管理持久化。
<!--[if !supportLists]-->n <!--[endif]-->提供集成多個持久化技術(shù)的框架,包括JDBC,Hibernate,JDO和iBATIS。
聲明式安全
<!--[if !supportLists]-->n <!--[endif]-->通過用戶和角色支持聲明式安全。用戶和角色的管理和實現(xiàn)是容器專有的。
<!--[if !supportLists]-->n <!--[endif]-->自身不具有安全的實現(xiàn)。
<!--[if !supportLists]-->n <!--[endif]-->一個建立于Spring之上的開源的安全框架Acegi,通過Spring配置文件或類元數(shù)據(jù)提供聲明式安全。
分布計算
<!--[if !supportLists]-->n <!--[endif]-->提供容器管理的遠程方法調(diào)用。
<!--[if !supportLists]-->n <!--[endif]-->通過RMI,JAX-RPC和Web服務(wù)提供遠程調(diào)用的代理。
對于大部分的J2EE項目,EJB和Spring都能滿足其技術(shù)需求。但是也有例外—你的應(yīng)用程序也許需要遠程事務(wù)調(diào)用,如果是這樣的話,選擇EJB更合適。但Spring提供了對JTA事務(wù)的集成,也能滿足。但如果你要找的是提供聲明式事務(wù)管理和靈活的持久化引擎的J2EE框架,Spring是最好的選擇。
EJB的復(fù)雜性
EJB以下的復(fù)雜性使大家傾向于選擇輕量級的容器:
<!--[if !supportLists]-->n <!--[endif]-->編寫一個EJB太過于復(fù)雜—寫一個EJB必需接觸至少四個文件:業(yè)務(wù)接口,home接口,bean實現(xiàn)和部署描述子??赡苓€牽扯到其他類,如utility類和Value Object。相反Spring使你通過POJO定義你的實現(xiàn),和通過注入或AOP來纏繞任何額外的服務(wù)。
<!--[if !supportLists]-->n <!--[endif]-->EJB具有侵入性—為使用EJB容器提供的服務(wù),則必需使用javax.ejb接口。這使組件代碼與EJB技術(shù)綁定,使組件很難用于EJB容器之外。Spring通常不要求組件實現(xiàn)、擴展或使用任何特定于Spring的類或接口,是組件更具有可復(fù)用性,即使沒有Spring的存在也能使用。
<!--[if !supportLists]-->n <!--[endif]-->實體EJBs功能較弱—實體EJBs不如其他ORM工具特性多,也不夠靈活。Spring可以集成很多其他ORM框架。Value Object會導(dǎo)致重復(fù)的代碼,使用Spring和其他ORM工具,實體對象不與持久化機制耦合,可以傳遞于應(yīng)用程序的不同層。
1.6.2 考慮其他輕量級容器
下表列出了IoC的類型。
類型
名稱
描述
類型1
接口依賴
為使容器管理依賴,Beans必需實現(xiàn)特定的接口。
類型2
Setter注入
依賴和屬性通過Beans的setter方法配置。
類型3
構(gòu)造函數(shù)注入
依賴和屬性通過Beans的構(gòu)造函數(shù)配置。
下面看一下其他輕量級容器。
PicoContainer
PicoContainer是一個小型的輕量級的容器,通過構(gòu)造函數(shù)和setter函數(shù)的形式提供IoC。有一個子項目NanoContainer通過XML和各種腳本語言提供對配置PicoContainer的支持。
PicoContainer的一個局限是,對于任何一個類型,在注冊表中只允許存在一個實例。
PicoContainer只是一個容器,不提供其他Spring提供的強大功能,如AOP和第三方框架集成。
HiveMind
HiveMind是一個相對較新的IoC容器。它也是通過構(gòu)造函數(shù)和setter函數(shù)來纏繞和配置服務(wù)。HiveMind允許用XML文件或它的簡單的數(shù)據(jù)語言定義你的配置。
HiveMind還通過Interceptors提供類似AOP的特性。但是沒有Spring的AOP框架強大。
它提供管理組件的框架,但不提供對其他技術(shù)的集成。
Avalon
Avalon是第一批開發(fā)出來的IoC容器之一。但設(shè)計中存在很多錯誤。Avalon主要提供接口依賴的IoC。這使Avalon成為侵入式的框架。
1.6.3 Web框架
Spring有自己的Web框架。先比較一下其他的Web框架。
Struts
Struts可以看作是Web MVC框架的事實上的標(biāo)準(zhǔn)。
最常使用的Struts類是Action類。Action是一個抽象類,因此你的所有處理輸入的類都必需繼承這個類,相比之下Spring提供了Controller接口。
另一個重要的區(qū)別是二者處理表單輸入的方式。通常情況下,當(dāng)用戶提交一個Web表單時,進來的數(shù)據(jù)對應(yīng)于你的應(yīng)用程序中的一個對象。為處理表單提交,Struts要求使用ActionForm類處理傳入的參數(shù)。這意味這你必需創(chuàng)建一個類來將表單提交映射到你的領(lǐng)域?qū)ο?。Spring允許你直接將表單提交映射到領(lǐng)域?qū)ο蟆?/p>
Struts提供內(nèi)置的聲明式表單驗證支持。這意味著你可以通過XML文件來定義驗證傳入的表單數(shù)據(jù)的規(guī)則。這使驗證與業(yè)務(wù)邏輯分離,也會導(dǎo)致一些笨拙和混亂。Spring不提供聲明式驗證,不過你可以自己集成一個驗證框架,如Jakarta Commons Validator。
Spring可以集成Struts。
WebWork
WebWork提供多視圖技術(shù)。WebWork與其他框架的最大區(qū)別是它為處理Web提交增加了一個抽象層。
WebWork提供了一個Spring沒有提供的功能:Action鏈。它允許你將一個邏輯請求映射到一系列的Actions。
Tapestry
Tapestry與之前提到的Web框架有很大區(qū)別。Taperstry不提供基于請求-響應(yīng)的Servlet機制的框架。它是一個從可復(fù)用組件創(chuàng)建Web應(yīng)用的框架。
Tapestry背后的思想是,減輕開發(fā)者思考Sessions屬性和URLs,而是以組件和方法的形式考慮Web應(yīng)用。
Tapestry不是一個使用JSPs的框架,而是代替JSPs。
1.6.4 持久化框架
Spring沒有內(nèi)置的持久化框架,Spring提供對Hibernate,JDO,OJB和iBATIS的集成點。Spring的JDBC和ORM框架工作于Spring的事務(wù)管理框架。
第二章 纏繞Beans
任何稍具規(guī)模的應(yīng)用程序都是由多個組件組成,它們一起工作來完成一個業(yè)務(wù)目標(biāo)。這些組件必需意識到其他組件的存在,相互會話來完成工作。
創(chuàng)建對象關(guān)聯(lián)的通常辦法會導(dǎo)致難以復(fù)用和進行單元測試的代碼。
在Spring中,組件本身不負責(zé)管理與其他組件的關(guān)聯(lián)。相反,對寫作組件的引用是由容器給他們的。創(chuàng)建應(yīng)用程序組件之間關(guān)聯(lián)的動作成為纏繞(Wiring)。
2.1 包含Beans
你在為Spring框架配置beans的時候,你實際上是在向Spring容器發(fā)出指令。容器處于Spring框架的核心。Spring容器使用IoC來管理組成應(yīng)用程序的各個組件。Spring具有兩個不同類型的容器。Bean工廠是最簡單的容器(由org.springframework.beans.factory.BeanFactory接口定義),它為依賴注入提供簡單的支持。應(yīng)用程序上下文建立于bean工廠的概念之上,它提供諸如從屬性文件解析文本消息之類的應(yīng)用程序框架服務(wù),以及向感興趣的應(yīng)用程序事件監(jiān)聽者發(fā)布應(yīng)用程序事件的能力。
2.1.1 介紹BeanFactory
不同于其他工廠模式的實現(xiàn)只分派一種類型的對象,這里的BeanFactory是一個通用的工廠,可以分派很多類型的Beans。
Bean工廠知道應(yīng)用程序中的很多對象,在實例化對象時,它能夠創(chuàng)建協(xié)作對象之間的關(guān)聯(lián)關(guān)系。這移除了Bean本身和它的客戶端的配置負擔(dān)。因此,當(dāng)Bean工廠分發(fā)對象時,這些對象是已經(jīng)完全配置好的,已經(jīng)知道了他們協(xié)作的對象,已經(jīng)可以供使用。
Spring中有很多BeanFactory的實現(xiàn),但最有用的是org.springframework.beans.factory.xmlBeanFactory,它基于包含在XML文件中的定義來加載Beans。
為創(chuàng)建XmlBeanFactory,向構(gòu)造函數(shù)傳遞一個java.io.InputStream。這個InputStream向工廠提供XML文件。Beans以懶加載的方式加載到Beans工廠中,Bean工廠會立即加載Bean的定義,但只要當(dāng)這些Beans被使用的時候才會被實例化。
為從BeanFactory獲取Bean,只要調(diào)用getBean()方法,向它傳遞你要獲取的bean的名稱。當(dāng)調(diào)用getBean()時,工廠會實例化Bean并開始使用依賴注入來設(shè)置Bean的屬性。
2.1.2 使用應(yīng)用程序上下文
ApplicationContext提供更多的東西:
<!--[if !supportLists]-->n <!--[endif]-->提供解析文本消息的方法,包括對這些消息提供I18N支持。
<!--[if !supportLists]-->n <!--[endif]-->提供加載文件資源,如圖片,的通用方法。
<!--[if !supportLists]-->n <!--[endif]-->可以向注冊為監(jiān)聽者的Beans發(fā)布事件。
因為ApplicationContext的額外功能,幾乎在所有的應(yīng)用程序中都優(yōu)先使用ApplicationContext,而非BeanFactory。
ApplicationContext的實現(xiàn)有很多,而常用的有三個:
<!--[if !supportLists]-->n <!--[endif]-->ClassPathXmlApplicationContext—從在classpath中定位的XML文件加載一個上下文定義,將上下文定義文件作為classpath資源處理。
<!--[if !supportLists]-->n <!--[endif]-->FileSystemXmlApplicationContext—從文件系統(tǒng)中的XML文件加載上下文定義。
<!--[if !supportLists]-->n <!--[endif]-->XmlWebApplicationContext—從包含于Web應(yīng)用中的XML文件加載上下文定義。
獲取Bean的方式同BeanFactory,可以使用getBean()方法,因為ApplicationContext繼承了BeanFactory接口。
應(yīng)用程序上下文與Bean工廠的另一個比較大的區(qū)別是他們加載單例Beans的方式。BeanFactory懶加載所有Beans,而ApplicationContext預(yù)先加載所有的單例Beans。
2.1.3 Bean的生命周期
Bean可以使用之前BeanFactory執(zhí)行幾步設(shè)置操作:
<!--[if !supportLists]-->1. <!--[endif]-->容器找到Bean的定義并實例化Bean。
<!--[if !supportLists]-->2. <!--[endif]-->Spring使用依賴注入產(chǎn)生Bean定義中指定的所有的屬性。
<!--[if !supportLists]-->3. <!--[endif]-->如果Bean實現(xiàn)了BeanNameAware接口,工廠調(diào)用setBeanName()方法,傳遞Bean的ID。
<!--[if !supportLists]-->4. <!--[endif]-->如果Bean實現(xiàn)了BeanFactoryAware接口,工廠調(diào)用setBeanFactory方法,并傳遞工廠本身的實例。
<!--[if !supportLists]-->5. <!--[endif]-->如果存在與Bean關(guān)聯(lián)的BeanPostProcessors,則調(diào)用他們的postProcessBeforeInitialization方法。
<!--[if !supportLists]-->6. <!--[endif]-->如果指定了Bean的init方法,則調(diào)用這個方法。
<!--[if !supportLists]-->7. <!--[endif]-->最后,如果存在與Bean關(guān)聯(lián)的BeanPostProcessors,則調(diào)用他們的postProcessAfterInitialization方法。
從這時候起,Bean可以被應(yīng)用程序使用,并一直存在與Bean工廠中,直到不需要它。有兩種方式將Bean從Bean工廠中移除。
<!--[if !supportLists]-->1. <!--[endif]-->如果Bean實現(xiàn)了DisposableBean接口,調(diào)用destroy()方法。
<!--[if !supportLists]-->2. <!--[endif]-->如果指定了自定義的destroy方法,則調(diào)用這個方法。
處在Spring應(yīng)用程序上下文中的Bean與BeanFactory中的唯一區(qū)別是,如果Bean實現(xiàn)了ApplicationContextAware接口,則調(diào)用它的setApplicationContext方法。
2.2基本的纏繞(Basic Wiring)
只需要一點XML基礎(chǔ)。
2.2.1使用XML纏繞
理論上Bean纏繞可以由任何配置源驅(qū)動,包括屬性文件,關(guān)系數(shù)據(jù)庫,甚至是LDAP目錄。XML文件是最常用的方式。有幾個支持通過XML纏繞Beans的Spring容器:XmlBeanFactory,ClasspathXmlApplicationContext,F(xiàn)ileSystemXmlApplicationContext,XmlWebApplicationContext。
上下文定義文件的根節(jié)點是<beans>元素,這個元素具有一個或多個<bean>元素作為子元素。每一個<bean>元素都定義一個配置到Spring容器中的JavaBean。
2.2.2 增加一個Bean
Spring中Bean的最簡單的配置包括Bean的ID和Bean的完整類名。
原型vs單例
缺省情況下,所有Spring中的Beans都是單例的。如果每一次都要請求一個特定的實例的話,可以定義一個原型Bean。將<bean>元素的singleton屬性設(shè)置為false就可以將Bean定義為原型Bean。
初始化與銷毀
使用init-method屬性指定的方法在Bean實例化之后立即執(zhí)行。destroy-method屬性指定的方法在Bean從容器移除之前調(diào)用。Spring提供了兩個接口,可以執(zhí)行相同的功能:InitializingBean和DisposableBean。前者提供了一個方法afterPropertiesSet(),在Bean的所有屬性設(shè)置后執(zhí)行,后者提供的方法destroy()在Bean從容器中移除時調(diào)用。使用接口的好處是不需要配置,但是也會將Bean與Spring的API綁定起來。
2.2.3 通過setter方法注入依賴
使用<bean>元素的<property>子元素來以setter方法進行注入依賴。
簡單的Bean的配置
使用<property>元素的子元素<value>元素可以來給Bean設(shè)置原始類型的屬性。原始類型的屬性的設(shè)定無須指定類型,Spring會自動進行類型轉(zhuǎn)換。
引用其他Beans
使用<property>元素的子元素<ref>元素來引用其他Beans。
內(nèi)部的Beans
可以將<bean>元素直接嵌入到<property>元素。這種纏繞方式的缺點是被嵌入的元素不能在別處使用。而且影響XML文件的可讀性。
纏繞集合
Spring支持很多種集合類型作為Bean屬性,如下表所示。
XML
類型
<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中的每一個記錄都用一個<entry>元素定義。<entry>的key屬性限定為只能是String類型。
纏繞Properties
使用<props>元素纏繞,每一個<props>元素的子元素都是用一個<prop>元素定義。
設(shè)定null值
使用<null/>元素。
Setter注入的一個代替方式
通過構(gòu)造函數(shù)注入。
2.2.4 通過構(gòu)造函數(shù)注入依賴
使用<constructor-arg>元素來設(shè)定構(gòu)造函數(shù)的參數(shù)。<constructor-arg>的子元素可以使用<property>元素可以使用的任何子元素。
處理模糊的構(gòu)造函數(shù)參數(shù)
如果出現(xiàn)模糊的情況,Spring會拋出org.springframework.beans.factory.UnsatisfiedDependencyException。有兩種方法來解決這個問題,使用下標(biāo)或類型。
使用<constructor-arg>的index屬性指定參數(shù)的下標(biāo)。還可以使用type屬性指定參數(shù)的類型。
如何選擇:使用構(gòu)造函數(shù)還是使用setter方法?
這是一個有爭議的問題。
支持使用構(gòu)造函數(shù)注入的理由:
<!--[if !supportLists]-->n <!--[endif]-->強制執(zhí)行一個強依賴協(xié)議。簡單的說,Bean如果沒得到所有的依賴就不會被實例化。
<!--[if !supportLists]-->n <!--[endif]-->既然所有的依賴都可以通過構(gòu)造函數(shù)設(shè)定,使用setter方法比較多余。
<!--[if !supportLists]-->n <!--[endif]-->只允許通過構(gòu)造函數(shù)設(shè)定函數(shù),可以讓這些屬性不可更改(immutable)。
支持setter方法注入依賴的理由:
<!--[if !supportLists]-->n <!--[endif]-->如果依賴比較多,構(gòu)造函數(shù)的參數(shù)會比較長。
<!--[if !supportLists]-->n <!--[endif]-->如果構(gòu)造可用的對象的方法有多個的話,就會有多個構(gòu)造函數(shù)。
<!--[if !supportLists]-->n <!--[endif]-->如果構(gòu)造函數(shù)的多個參數(shù)的類型一樣的話,很難確定參數(shù)的目的是什么。
<!--[if !supportLists]-->n <!--[endif]-->構(gòu)造函數(shù)注入使自身不具有好的集成性。
2.3 自動纏繞(autowiring)
可以通過設(shè)定<bean>元素的autowire屬性來自動纏繞。
有四種類型的自動纏繞:
<!--[if !supportLists]-->n <!--[endif]-->byName—在容器中查找與指定的名稱相同的Bean。
<!--[if !supportLists]-->n <!--[endif]-->byType—在容器中查找一個唯一的與指定類型相同的Bean。如果沒有找到則不纏繞,如果找到多個,拋出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 處理自動纏繞的模糊性
如果使用byType或constructor自動纏繞,可能會找到多個Bean。Spring在這種情況下會拋出異常。
2.3.2 混合使用自動纏繞和顯式纏繞
對一個Bean的多個屬性,可以設(shè)定自動纏繞屬性,再顯示纏繞需要覆蓋的屬性。
2.3.3 缺省自動纏繞
設(shè)定<beans>的default-autowire=”byName”,會對所有的Beans缺省byName自動纏繞。
2.3.4 自動纏繞還是不自動纏繞
自動纏繞可能會引起一些問題。例如,屬性改名會導(dǎo)致纏繞不成功。
2.4 使用Spring的特殊Beans
通過實現(xiàn)特定的接口,可以使你的Beans變得特殊,成為Spring框架的一部分,你可以配置Beans:
<!--[if !supportLists]-->n <!--[endif]-->可以通過后置處理配置文件來參與Bean和Bean的工廠的生命周期。
<!--[if !supportLists]-->n <!--[endif]-->從外部屬性文件加載配置信息。
<!--[if !supportLists]-->n <!--[endif]-->改變Spring的依賴注入,在配置Bean屬性的時候,自動將String類型轉(zhuǎn)換成其他類型。
<!--[if !supportLists]-->n <!--[endif]-->加載文本消息,包括國際化的消息。
<!--[if !supportLists]-->n <!--[endif]-->監(jiān)聽或響應(yīng)由其他Beans或容器發(fā)布的應(yīng)用程序事件。
<!--[if !supportLists]-->n <!--[endif]-->使其意識到自己在Spring容器中的身份。
2.4.1 后置處理Beans
BeanPostProcessor給你兩個機會來在它被創(chuàng)建和纏繞之后來更改它。postProcessBeforeInitialization方法和postProcessAfterInitialization。
書寫B(tài)ean后置處理器
定義一個類實現(xiàn)BeanPostProcessor接口。
注冊Bean后置處理器
如果應(yīng)用程序運行于Bean工廠,則需要使用程序來進行注冊,調(diào)用工廠的addBeanPostProcessor方法,將上面定義的類作為參數(shù)傳入。如果應(yīng)用程序運行于應(yīng)用程序上下文,只需要將定義的類注冊為一個Bean。容器會將其識別為后置處理器并應(yīng)用于所有的Beans。
Spring自帶的Bean后置處理器
例如,ApplicationContextAwareProcessor對所有實現(xiàn)ApplicationContextAware接口的Beans設(shè)置應(yīng)用程序上下文。
DefaultAdvisorAutoProxyCreator基于容器中所有的候選advisors創(chuàng)建AOP代理。
2.4.2 后置處理Bean工廠
BeanFactoryPostProcessor后置處理Bean工廠,在Bean工廠加載Bean定義之后,所有Beans被實例化之前。postProcessBeanFactory方法。如在應(yīng)用程序上下文中使用,則將其注冊為正常的Bean就行了,容器自動將其注冊為BeanFactoryPostProcessor。不能在Bean工廠容器中使用。
有兩個比較有用的實現(xiàn)。PropertyPlaceHolderConfigurer從一個或多個外部屬性文件中加載屬性,并用這些屬性填充Bean纏繞XML文件中的占位符變量。CustomEditorConfigurer讓你注冊java.beans.PropertyEditor來將屬性纏繞值翻譯成其他屬性值。
2.4.3 使配置外部化
可以將整個應(yīng)用程序都配置在一個Bean纏繞文件中,但有時將配置提取成多個文件更有益。
如果使用ApplicationContext作為Spring容器的話,外部化屬性是很容易的??梢允褂肞ropertyPlaceHolderConfigurer來告訴Spring從外部屬性文件來加載特定的配置。如果你的配置分解為多個外部屬性文件的話,使用PropertyPlaceHolderConfigurer的locations屬性來設(shè)置<list>元素。
2.4.4 定制屬性編輯器
將String值纏繞到其他類型的屬性。
Spring自帶了幾個自定義編輯器:
<!--[if !supportLists]-->n <!--[endif]-->URLEditor—將String對象纏繞到URL對象。
<!--[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ù)化的消息,它通過MessageSource接口使參數(shù)化消息對容器可用。Spring自帶了一個MessageSource的實現(xiàn)ResourceBundleMessageSource,它簡單地使用Java的java.util.ResourceBundle來解析消息。配置文件中的bean必須命名為messageSource,否則不能被ApplicationContext識別。
一般情況下,通過ApplicationContext的getMessage方法來訪問消息,也可以在JSP中使用<spring:message>標(biāo)簽來訪問。
2.4.6 監(jiān)聽事件
在應(yīng)用程序的生命周期中,ApplciationContext會發(fā)布很多事件,來告訴監(jiān)聽者應(yīng)用程序正在進行什么工作。這些事件都是抽象類org.springframework.context.ApplicationEvent的子類,其中的三個事件為:
<!--[if !supportLists]-->n <!--[endif]-->ContextClosedEvent—當(dāng)應(yīng)用程序上下文關(guān)閉的時候發(fā)布。
<!--[if !supportLists]-->n <!--[endif]-->ContextRefreshedEvent—當(dāng)應(yīng)用程序上下文初始化或刷新的時候發(fā)布。
<!--[if !supportLists]-->n <!--[endif]-->RequestHandledEvent—當(dāng)請求已經(jīng)被處理的時候,在Web應(yīng)用上下文環(huán)境內(nèi)發(fā)布。
如果你想要Bean對應(yīng)用程序事件作出響應(yīng),只需實現(xiàn)org.springframework.context.ApplicationListener接口。
2.4.7 發(fā)布事件
可以讓應(yīng)用程序發(fā)布自己的事件。實現(xiàn)ApplicationEvent接口來定義事件,使用ApplicationContext的publishEvent方法來發(fā)布事件。
2.4.8 讓Beans有意識
通過實現(xiàn)BeanNameAware接口,BeanFactoryAware接口和ApplicationContextAware接口,Beans可以意識到他們的名字,他們的BeanFactory以及他們的ApplicationContext的存在。需要警告你的是,實現(xiàn)了這些接口之后Beans就會與Spring耦合。
知道你是誰
BeanNameAware的setBeanName()方法。
知道你在哪生存
Spring的BeanFactoryAware和ApplicationContextAware接口讓Beans意識到容器的存在。
第三章 創(chuàng)建方面(Aspects)
3.1 介紹AOP
3.1.1 定義AOP術(shù)語
方面(Aspect)
一個方面(Aspect)是你實現(xiàn)的一個橫切的功能。它是你模塊化的應(yīng)用程序的一個方面或領(lǐng)域。最常見的例子是寫日志(logging)。
連接點(joinpoint)
一個連接點是應(yīng)用程序執(zhí)行過程中的一個點,在這個點可以插入一個方面(Aspect)。這個點可以是調(diào)用方法,可以是拋出異常,也可以是修改一個字段。在這些點,方面的代碼可以插入到應(yīng)用程序的正常流程來增加新的行為。
修訂(Advice)
Advice是方面的實際實現(xiàn)。以logging為例,logging advice包含包含實際實現(xiàn)loggin的代碼。Advice在jointpoints插入到應(yīng)用程序中。
切入點(Pointcut)
切入點定義應(yīng)該應(yīng)用何種連接點Advice。Advice可以應(yīng)用于AOP框架支持的任何連接點。
引入(Introduction)
Introduction允許你向既存的類增加方法或?qū)傩浴?/p>
目標(biāo)(Target)
Target是被修訂的類??梢允悄銓懙念?,也可以是第三方的類。
代理(Proxy)
代理是向目標(biāo)對象應(yīng)用修訂后創(chuàng)建的對象。就客戶端對象而言,目標(biāo)對象和代理對象是相同的。
編織(Weaving)
編織是向目標(biāo)對象應(yīng)用方面來創(chuàng)建一個新的代理的對象的過程。方面在指定的連接點被織入目標(biāo)對象。編織可以發(fā)生在目標(biāo)類的生命周期的多個點:
<!--[if !supportLists]-->n <!--[endif]-->編譯期—方面在目標(biāo)類被編譯時織入。這需要一個特殊的編譯器。
<!--[if !supportLists]-->n <!--[endif]-->類加載期—方面在目標(biāo)類被加載進JVM時織入。這需要一個特殊的ClassLoader來增強目標(biāo)類的字節(jié)碼。
<!--[if !supportLists]-->n <!--[endif]-->運行時—方面在應(yīng)用程序執(zhí)行的某個時刻被織入。
Advice包含需要應(yīng)用的橫切行為。
3.1.2 Spring的AOP實現(xiàn)
Spring的修訂是使用Java編寫
切入點通常以XML形式寫在Spring配置文件中。其他框架,如AspectJ,要求必需使用一種特殊的語法來定義方面和切入點。
Spring在運行時修訂對象
Spring只有在代理Bean在應(yīng)用程序中被需要時才創(chuàng)建代理對象。Spring有兩種方式可以產(chǎn)生代理類。如果你的目標(biāo)對象實現(xiàn)了暴露了必要方法的接口,則Spring使用JDK的java.lang.reflect.Proxy接口。這個類允許Spring動態(tài)地創(chuàng)建新類來實現(xiàn)必要的接口,織入任何修訂,和代理任何從這些接口的對目標(biāo)類的方法調(diào)用。如果你的目標(biāo)類不實現(xiàn)接口,Spring使用CGLIB來產(chǎn)生目標(biāo)類的一個子類。創(chuàng)建子類時,Spring織入修訂,并使用子類代理所有到目標(biāo)類的調(diào)用。
Spring實現(xiàn)AOP Alliance接口
AOP Alliance是一個都對用Java實現(xiàn)AOP感興趣的多方的聯(lián)合項目。他們的目標(biāo)是標(biāo)準(zhǔn)化Java AOP接口來提供不同Java的AOP實現(xiàn)的互操作性。這意味著實現(xiàn)他們接口的AOP修訂會在任何與AOP Alliance兼容的框架中復(fù)用。
Spring只提供方法連接點
這會有礙于創(chuàng)建細粒度的修訂,如監(jiān)聽對對象字段的更新。
3.2 創(chuàng)建修訂(Advice)
因為在方法的執(zhí)行過程中有多個點Spring可以織入修訂,所以有多種類型的修訂。下表列出了Spring提供的修訂類型。
修訂類型
接口
描述
Around
org.aopalliance.intercept.MethodInterceptor
攔截對目標(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)方法拋出異常時調(diào)用
3.2.1 Before修訂
MethodBeforeAdvice提供目標(biāo)方法,傳遞給目標(biāo)方法的參數(shù),以及方法調(diào)用的目標(biāo)對象的訪問。將修訂應(yīng)用于目標(biāo)對象是通過Spring的配置文件。
3.2.2 After修訂
AfterReturningAdvice還可以訪問被修訂方法的返回值。
3.2.3 Around修訂
MethodInterceptor與前兩者有兩個不同的地方。首先,MethodInterceptor控制目標(biāo)方法是否被實際調(diào)用。另一方面,它控制返回什么對象。
3.2.4 Throws修訂
ThrowsAdvice的afterThrowing方法具有兩個簽名。Throws不能捕獲和處理異常,只能拋出其他異常。
3.2.5 引入(Introduction)修訂
引入修訂為目標(biāo)對象引入新方法或?qū)傩浴?/p>
3.3 定義切入點
Spring的切入點允許我們定義我們的修訂在何處以一種 靈活的方式織入我們的類。
3.3.1 在Spring中定義切入點
Spring根據(jù)被修訂的類和方法來定義切入點。修訂(Advice)被織入目標(biāo)類和它的方法,基于它們的特征,如類名和方法簽名。Spring的切入點框架的核心接口是Pointcut接口。ClassFilter接口確定滿足修訂條件的類。實現(xiàn)此接口的類確定作為參數(shù)傳入的類是否應(yīng)該被修訂。
ClassFilter是根據(jù)類來過濾方面,而MathodFilter是根據(jù)方法來過濾。
3.3.2 理解修訂(advisors)
大部分的方面是由定義方面的行為的修訂(Advice)和定義方面應(yīng)該在何處執(zhí)行的切入點的組合。Spring認識到這一點并且提供Advisor來組合二者,確切的說,使用PointcutAdvisor來做到這一點。大部分的Spring的內(nèi)置切入點都有一個對應(yīng)的PointcutAdvisor。
3.3.3 使用Spring的靜態(tài)切入點
我們更傾向于使用靜態(tài)切入點因為它們執(zhí)行效率更高,因為他們只計算一次(創(chuàng)建代理的時候),而不是每次運行時調(diào)用都計算。Spring提供了一個方便的超類來創(chuàng)建靜態(tài)切入點:StaticMethodMatcherPointcut,如果你想創(chuàng)建自定義的切入點,只要實現(xiàn)這個接口的isMatch方法。但大部分時候,使用Spring自帶的切入點就夠用了。
NameMatchMethodPointcut
根據(jù)提供的名稱來匹配方法名,提供的名稱可以帶通配符。
正則表達式切入點
RegexpMethodPointcut使你可以使用正則表達式來定義切入點。
3.3.4 使用動態(tài)切入點
也存在切入點需要計算運行時屬性的情況。Spring提供一個內(nèi)置的動態(tài)的切入點:ControlFlowPointcut。這個切入點根據(jù)當(dāng)前線程的調(diào)用堆棧的信息來進行匹配。使用這個切入點會影響性能。
3.3.5 切入點操作
Spring提供對切入點的操作如合集和交集操作來創(chuàng)建新的切入點。Spring提供兩個類來創(chuàng)建這些類型的切入點。第一個類是ComposablePointcut??梢詫却娴腃omposablePointcut,對象和Pointcut,MethodMatcher和ClassFilter對象創(chuàng)建合集或交集來組裝ComposablePointcut??梢酝ㄟ^調(diào)用一個ComposablePointcut的一個實例的intersection或union方法來組裝。必需使用Pointcuts來操作兩個Pointcut對象,如使用Pointcuts的union方法來組合兩個Pointcut。
3.4 創(chuàng)建介紹(Introductions)
Introductions影響整個類。Introductions允許動態(tài)地構(gòu)造組合對象,實現(xiàn)與多繼承相同的效果。
3.4.1 實現(xiàn)IntroductionInterceptor
Spring通過MethodInterceptor的一個特殊的子接口IntroductionMethodInterceptor來實現(xiàn)Introductions。這個接口增加一個額外的方法implementsInterface。Spring提供了一個方便的類來實現(xiàn)這一切,DelegatingIntroductionMethodInterceptor,它繼承自IntroductionMethodInterceptor。
3.4.2 創(chuàng)建IntroductionAdvisor
Spring提供了一個缺省的實現(xiàn):DefaultIntroductionAdvisor。
3.4.3 謹慎使用Introduction Advice
你不能為自己的代碼創(chuàng)建的對象使用Introductions。
3.5 使用ProxyFactoryBean
BeanFactory對象是負責(zé)創(chuàng)建其他JavaBeans的JavaBeans對象,于是ProxyFactoryBean創(chuàng)建代理對象。同其他JavaBeans一樣,它具有控制自己行為的屬性,下表是它的每一個屬性的解釋。
屬性
使用
target
代理的target bean。
proxyInterfaces
proxy應(yīng)該實現(xiàn)的接口列表。
interceptorNames
應(yīng)用于target的advice的bean名稱。這些可以是Interceptors,advisors或其他任何類型的修訂的名稱。可以設(shè)定此屬性來在一個BeanFactory中使用這個bean。
singleton
對每一個getBean調(diào)用,工廠是否應(yīng)該返回相同實例的代理。如果你要使用帶狀態(tài)的修訂,應(yīng)該將這個屬性設(shè)為false。
aopProxyFactory
使用的ProxyFactoryBean的實現(xiàn)。Spring自帶了兩個實現(xiàn),你極有可能不需要此屬性。
exposeProxy
目標(biāo)類是否應(yīng)該訪問當(dāng)前代理??梢酝ㄟ^AppContext.getCurrentProxy。應(yīng)該盡量避免,因為這引入了Spring特定的AOP代碼。
frozen
工廠創(chuàng)建之后是否可以對代理的修訂進行更改。
optimize
是否主動優(yōu)化已經(jīng)產(chǎn)生的代理。
proxyTargetClass
是否代理目標(biāo)類,而不是實現(xiàn)接口。
最經(jīng)常使用的屬性是target,proxyInterfaces和interceptorNames。
3.6 自動代理
如果我們有很多類的話,顯示地代理每一個類會顯得很笨拙。幸運的是,Spring的自動代理設(shè)施使容器可以為我們創(chuàng)建代理。Spring提供了兩個類來做這項工作:BeanNameAutoProxyCreator和DefaultAdvisorAutoProxyCreator。
3.6.1 BeanNameAutoProxyCreator
為所有匹配一組名稱的Beans產(chǎn)生代理。
3.6.2 DefaultAdvisorAutoProxyCreator
為任何Advisors提供上下文環(huán)境。
3.6.3 元數(shù)據(jù)自動代理
代理配置由代碼級別的屬性決定。常見應(yīng)用是聲明式事務(wù)。
第二部分 Spring的業(yè)務(wù)層
介紹如何使用IoC和AOP為應(yīng)用程序?qū)崿F(xiàn)業(yè)務(wù)層功能。
第四章 數(shù)據(jù)庫相關(guān)問題
4.1 學(xué)習(xí)Spring的DAO理念
Service對象通過接口來訪問DAOs。這樣有兩個好處:第一,因為service不與具體的DAO實現(xiàn)耦合,使service對象更容易測試。第二,數(shù)據(jù)訪問層使用具體的持久化技術(shù),使用接口不會暴露你使用的技術(shù)。
Spring提供在它的所有DAO框架中一直的異常層次結(jié)構(gòu)。
4.1.1 理解Spring的DataAccessException
Spring的DAO不會拋出特定技術(shù)的異常,如SQLException或者HibernateException。所有拋出的異常都是與特定技術(shù)無關(guān)的類org.springframework.dao.DataAccessException的子類。這使你的數(shù)據(jù)訪問接口可以拋出通用的DataAccessException,而不必與具體的持久化實現(xiàn)耦合。
你不必一定要處理DataAccessException
DataAccessException是一個RuntimeException,因此它是unchecked的異常。這意味著,當(dāng)持久化層拋出這些異常時,你的代碼不一定要處理。這遵循了Spring的通用理念:checked異常會導(dǎo)致很多不必要的catch或throws語句,使代碼比較凌亂。
DataAccessException是從Spring的NestedRuntimeException繼承,根Exception可以從NestedRuntimeException的getClause方法獲得。
Spring對異常進行分類
如下表所示,Spring有豐富的異常層次:
異常
拋出時機
CleanupFailureDataAccessException
操作執(zhí)行成功,但是清理數(shù)據(jù)庫資源時發(fā)生異常(如,關(guān)閉數(shù)據(jù)庫連接)。
DataAccessResourceFailureException
數(shù)據(jù)訪問資源完全失敗,例如不能連接到數(shù)據(jù)庫。
DataIntegrityViolationException
插入或更新違反完整性約束,例如,違反一致性約束。
DataRetrievalFailureException
某些數(shù)據(jù)獲取不到,例如,不能根據(jù)主鍵找到一行。
DeadLockLoserDataAccessException
當(dāng)前線程是死鎖的失敗者。
IncorrectUpdateSemanticDataAccessException
更新時發(fā)生非計劃的事情,如更新了比期望要多的行。當(dāng)拋出此異常時,操作的事務(wù)沒有回滾。
InvalidDataAccessApiUsageException
不正確的使用了Java數(shù)據(jù)訪問API,例如執(zhí)行前編譯查詢語句時,編譯失敗。
InvalidDataAccessResourceUsageException
不正確地使用了數(shù)據(jù)訪問資源,例如,使用壞的SQL語法訪問關(guān)系數(shù)據(jù)庫。
OptimisticLockingFailureException
樂觀加鎖失敗,這由ORM工具或自定義的DAO實現(xiàn)拋出。
TypeMismatchDataAccessException
Java類型和數(shù)據(jù)庫類型存在失配,例如試圖將一個String插入到numeric類型的數(shù)據(jù)庫列。
UncategorizedDataAccessException
發(fā)生錯誤,但是不能確定具體的錯誤。
4.1.2 使用DataSources
在Spring框架中,通過DataSource獲得Connection對象。
從JNDI取得DataSource
Spring應(yīng)用程序通常運行于J2EE應(yīng)用服務(wù)器或Tomcat之類的Web服務(wù)器。這些服務(wù)器可以通過JNDI提供DataSource。在Spring中我們處理這件事情也是通過Spring bean。在這種情況下,我們使用JndiObjectFactoryBean。
創(chuàng)建DataSource連接池
如果我們的Spring容器運行的環(huán)境不存在DataSource,而我們又希望使用連接池的好處,我們只需實現(xiàn)一個連接池bean,實現(xiàn)DataSource接口即可。一個很好的例子是Jakarta Commons DBCP項目的BasicDataSource類。只需在Spring配置文件中配置一個bean即可。
測試時使用DataSource
Spring自帶了一個輕量級的DataSource實現(xiàn),用于單元測試或單元測試套件。
4.1.3 一致性DAO Support
一個模板方法定義了一個過程的骨架。Spring將數(shù)據(jù)訪問過程的固定和可變的部分分離成兩個不同的類:模板與回調(diào)。模板處理數(shù)據(jù)訪問的不變部分—控制事務(wù),管理資源和處理異常?;卣{(diào)接口的實現(xiàn)定義你的應(yīng)用程序特定的東西—創(chuàng)建語句,綁定參數(shù)和處理結(jié)果集。
模板回調(diào)設(shè)計的頂部有一個DAO Support類,你的DAO類可以從這些類繼承。
4.2 在Spring中使用JDBC
4.2.1 JDBC代碼的問題
JDBC在為你提供與數(shù)據(jù)庫緊密結(jié)合的API的同時,也使你必需負責(zé)處理與訪問數(shù)據(jù)庫所有的相關(guān)工作。包括管理數(shù)據(jù)庫資源和處理異常。
代碼比較冗贅。
4.2.2 使用JdbcTemplate
Spring的JDBC框架通過擔(dān)當(dāng)資源管理和錯誤處理的負擔(dān)來清理你的JDBC代碼。Spring的數(shù)據(jù)訪問框架都處理一個模板類。JdbcTemplate的工作所需要的只是一個DataSource。因為Spring的所有DAO模板類都是線程安全的,在我們的應(yīng)用程序中對每一個DataSource只需要一個JdbcTemplate實例。
寫數(shù)據(jù)
PreparedStatementCreator接口的實現(xiàn)負責(zé)創(chuàng)建一個PreparedStatement,這個接口只提供了一個方法createPreparedStatement。
這個接口的實現(xiàn)通常也實現(xiàn)另一個接口:SqlProvider。通過實現(xiàn)這個類的方法—getSql()—使你的類向JdbcTemplate類提供SQL字符串。
對PreparedStatementCreator的補充是PreparedStatementSetter。實現(xiàn)這個接口的類接受一個PreparedStatement參數(shù),負責(zé)設(shè)定所有的參數(shù)。
如果要更新多行,可以使用BatchPrepareStatementSetter。如果你的JDBC驅(qū)動支持批量操作,更新會批量進行,創(chuàng)建高效的數(shù)據(jù)庫訪問,否則Spring會模擬批處理,但是語句會一個一個執(zhí)行。
讀數(shù)據(jù)
我們要告訴Spring如何處理ResultSet中的每一行,通過實現(xiàn)RowCallbackHandler的方法processRow來處理。
如果通過查詢獲取多個對象,可以實現(xiàn)一個子接口。例如,如果要獲取某個類的所有對象,可以實現(xiàn)ResultReader接口。Spring提供了這個接口的一個實現(xiàn),RowMapperResultReader。RowMapper接口負責(zé)將ResultSet的一行映射到一個對象。
JdbcTemplate也提供了很多返回結(jié)果為int或String等簡單類型的方法。
調(diào)用存儲過程
Spring提供對存儲過程的支持是通過實現(xiàn)接口CallableStatementCallBack。
4.2.3 將操作創(chuàng)建為對象
通過增加一層與直接JDBC操作的隔離。
創(chuàng)建SqlUpdate對象
為創(chuàng)建執(zhí)行插入或更新的可復(fù)用對象,繼承SqlUpdate類。
使用MappingSqlQuery查詢數(shù)據(jù)庫
通過繼承MappingSqlQuery來將一個查詢建模為對象。
4.2.4 自動遞增鍵值
通過DataFieldMaxValueIncrementer接口實現(xiàn)。
4.3 Spring的ORM框架支持簡介
Spring支持Hibernate等很多開源ORM工具。
4.4 在Spring中集成Hibernate
4.4.1 Hibernate概覽
Hibernate通過XML配置文件將對象映射到關(guān)系數(shù)據(jù)庫。通常來說,每一個持久類都有一個對應(yīng)的XML配置文件,擴展名為.hbm.xml。根據(jù)規(guī)范,文件名與類名相同。
4.4.2 管理Hibernate資源
在應(yīng)用程序生命周期中只需要一個SessionFactory實例,因此可以在Spring配置文件中配置此對象??梢允褂肧pring的LocalSessionFactoryBean。使用Spring,可以不再使用hibernate.properties,可以纏繞到LocalSessionFactoryBean的hibernateProperties屬性。使用Spring可以配置mappingDirectoryLocations屬性可以指定一個作為應(yīng)用程序classpath的一個子集的路徑,在這個路徑下的所有*.hbm.xml文件都會被配置。如同Spring的其他DAO框架一樣,有一個HibernateTemplate這樣一個模板類。
4.4.3 通過HibernateTemplate訪問Hibernate
HibernateTemplate與一個回調(diào)接口:HibernateCallBack。HibernateCallBack只要一個方法doInHibernate。
4.4.4 繼承HibernateDaoSupport
4.5 Spring與JDO
JDO是Sun的標(biāo)準(zhǔn)持久化規(guī)范。
4.5.1 配置JDO
與Hibernate的SessionFactory類似,JDO具有一個生命周期很長的對象來持有持久化配置,PersistenceManagerFactory。如果不使用Spring我們通過JDOHelper得到這樣一個對象。
在Spring中,使用LocalePersistenceManagerFactoryBean配置PersistenceManagerFactoryBean。有了JDO PersistenceManagerFactory,下一步就是將此bean纏繞到JdoTemplate。
4.5.2 使用JdoTemplate訪問數(shù)據(jù)
JdoTemplate類只要一個方法execute(JdoCallBack)。JdoCallBack也只有一個方法doInJdo。
4.6 Spring與iBATIS
4.6.1 設(shè)定SQL Maps
也是使用XML配置文件配置SQL Maps。
4.6.2 使用SqlMapClientTemplate
同其他ORM框架一樣,只需實現(xiàn)SqlMapClientTemplate的方法doInSqlMapClient。及SqlMapClientCallBack。
4.7 Spring與OJB
Spring通過PersistenceBroker與OJB集成。
4.7.1 設(shè)置OJB的PersistenceBroker
還是XML文件。
第五章 管理事務(wù)
5.1 理解事務(wù)
5.1.1 用四個詞解釋事務(wù)
ACID用來描述事務(wù):
<!--[if !supportLists]-->n <!--[endif]-->Atomic—事務(wù)由一個或多個活動綁定起來作為一個工作單元。
<!--[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)允許多個用戶同是工作而不會相互添亂。
<!--[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ù)提供更多的屬性,如隔離級別和超時。
5.1.3 Spring事務(wù)管理器簡介
Spring不直接管理事務(wù)。相反,它帶有一些事務(wù)管理器,這些管理器將事務(wù)管理的職責(zé)代理給由JTA或其他持久化機制提供的平臺相關(guān)的事務(wù)實現(xiàn)。Spring的事務(wù)管理器如下表所示。
事務(wù)管理器實現(xiàn)
目的
org.springframework.jdbc.datasource.DataSourceTransactionManager
在單個JDBC DataSource上管理事務(wù)
org.springframework.orm.hibernate.HibernateTransactionManager
當(dāng)Hibernate是持久化機制時用來管理事務(wù)。
org.springframework.orm.jdo.JdoTransactionManager
當(dāng)使用JDO作為持久化時使用的事務(wù)管理器。
org.springframework.transaction.jta.JtaTransactionManager
使用JTA實現(xiàn)管理事務(wù),當(dāng)一個事務(wù)跨越多個源時,必需使用這個事務(wù)管理器。
org.springframework.orm.ojb.PersistenceBrokerTransactionManager
使用OJB時使用的事務(wù)管理器。
為使用事務(wù)管理器,必需在你的應(yīng)用程序上下文中聲明這個事務(wù)管理器。
JDBC事務(wù)
如果使用JDBC做應(yīng)用程序的持久化,DataSourceTransactionManager可以為你處理事務(wù)邊界。使用XML纏繞到應(yīng)用程序上下文中。
Hibernate事務(wù)
如果你的應(yīng)用程序使用Hibernate進行持久化,可以使用HibernateTransactionManager可以為你處理事務(wù)。HibernateTransactionManager將事務(wù)管理的職責(zé)代理到net.sf.hibernate.Transaction對象。
JDO(Java Data Object)事務(wù)
JdoTransactionManager。
OJB事務(wù)
PersistenceBrokerTransactionManager。
JTA事務(wù)
JtaTransactionManager。如果使用多個數(shù)據(jù)源,需要使用JtaTransactionManager。
5.2 Spring中的程序式事務(wù)
向你的代碼中添加事務(wù)的一種方法是使用Spring的TransactionTemplate類程序式地添加事務(wù)邊界。TransactionTemplate利用了回調(diào)機制。以實現(xiàn)TransactionCallBack接口開始。
如果你想完全控制事務(wù)的邊界,使用程序式事務(wù)比較好。通常你不需要如此精確的邊界,這就是為什么通常在應(yīng)用程序代碼之外聲明事務(wù)。
5.3 聲明事務(wù)
Spring對聲明式事務(wù)的支持是通過AOP框架實現(xiàn)的。為在Spring應(yīng)用程序中利用聲明式事務(wù),使用TransactionProxyFactoryBean。
5.3.1 理解事務(wù)屬性
在Spring中,一個事務(wù)屬性是值對事務(wù)策略如何應(yīng)用于一個方法的一個描述。這個描述可以包括以下參數(shù)中的一個或多個:
<!--[if !supportLists]-->n <!--[endif]-->傳播行為
<!--[if !supportLists]-->n <!--[endif]-->隔離級別
<!--[if !supportLists]-->n <!--[endif]-->只讀提示
<!--[if !supportLists]-->n <!--[endif]-->事務(wù)有效時間
傳播行為
傳播行為定義了相對于客戶端和被調(diào)用方法的事務(wù)的邊界。Spring定義了七種傳播行為,如下表所示:
傳播行為
含義
PROPAGATION_MANDATORY
表示一個方法必需運行于一個事務(wù),如果沒有進行中的事務(wù),則拋出一個異常。
PROPAGATION_NESTED
表示如果存在進行中的事務(wù),則方法應(yīng)該運行于一個嵌套的事務(wù)。嵌套的事務(wù)可以從閉包事務(wù)中分別提交或回滾。如果不存在嵌套事務(wù),則與PROPAGATION_REQUIRED行為相同。廠商一般對這種行為支持不是很好,查詢資源管理器的文檔來確定是否支持嵌套事務(wù)。
PROPAGATION_NEVER
表示當(dāng)前方法不應(yīng)當(dāng)運行于事務(wù)上下文。如果存在進行中的事務(wù),則拋出一個異常。
PROPAGATION_NOT_SUPPORTED
表示當(dāng)前方法不應(yīng)當(dāng)運行于事務(wù),如果存在進行中事務(wù),在方法執(zhí)行期間事務(wù)暫停。如果使用JTATransactionManager,則對TransactionManager的訪問是必需的。
PROPAGATION_REQUIRED
表示當(dāng)前方法必需運行于事務(wù)內(nèi),如果存在一個進行中的事務(wù),則使用這個事務(wù),否則開啟新的事務(wù)。
PROPAGATION_REQUIRED_NEW
表示當(dāng)前方法必需運行于自己的事務(wù)內(nèi)。開啟一個新事務(wù),如果存在進行中事務(wù),則在方法執(zhí)行期間暫停。如果使用JTATransactionManager,則對TransactionManager的訪問是必需的。
PROPAGATION_SUPPORTS
表示當(dāng)前方法不必需一個事務(wù)上下文,但是如果有進行中的事務(wù),這個方法也可以運行于這個事務(wù)。
隔離級別
事務(wù)的隔離級別如下表所示:
隔離級別
含義
ISOLATION_DEFAULT
使用底層數(shù)據(jù)存儲的缺省隔離級別。
ISOLATION_READ_UNCOMMITED
允許讀取沒有提交的更改??赡軙?dǎo)致臟讀,幻影讀和不可重復(fù)讀。
ISOLATION_READ_COMMITED
允許從沒有提交的并發(fā)事務(wù)中讀取。阻止臟讀,但仍有可能發(fā)生幻影讀和不可重復(fù)讀。
ISOLATION_REPEATABLE_READ
對相同字段的多次讀取產(chǎn)生相同的結(jié)果,除非由自身的事務(wù)更改。阻止臟讀和不可重復(fù)讀,但仍有可能發(fā)生幻影讀。
ISOLATION_SERIALIZABLE
完全符合ACID的隔離級別,阻止臟讀,幻影讀和不可重復(fù)讀。這是速度最慢的隔離級別,因為通常通過在事務(wù)中完全的表鎖定來實現(xiàn)。
并不是所有的資源管理器都支持所有的這些隔離級別。
只讀
聲明事務(wù)是只讀的,給予底層數(shù)據(jù)存儲機會對事務(wù)進行優(yōu)化。
事務(wù)超時
設(shè)定事務(wù)過一定時間后自動回滾。
5.3.2 聲明簡單的事務(wù)策略
TransactionProxyFactoryBean查詢一個方法的事務(wù)屬性,從而確定如何管理該方法的事務(wù)策略。TransactionProxyFactoryBean具有一個transactionAttributeSource屬性,該屬性纏繞到TransactionAttributeSource接口的一個實例。TransactionAttributeSource接口用作查找一個方法的事務(wù)屬性的引用。通過調(diào)用該接口的getTransactionAttribute()方法來一個特定方法的事務(wù)屬性。
MatchAlwaysTransactionAttributeSource是最簡單的TransactionAttributeSource的實現(xiàn),當(dāng)調(diào)用其getTransactionAttribute()方法時,不管事務(wù)中纏繞的是哪一個方法,每次都返回相同的TransactionAttribute(PROPAGATION_REQUIRED和ISOLATION_DEFAULT)。
改變?nèi)笔〉腡ransactionAttribute
缺省策略是PROPAGATION_REQUIRED和ISOLATION_DEFAULT,如果要改變的話,只需對transactionAttribute纏繞另一個TransactionAttribute。
5.4 根據(jù)方法名聲明事務(wù)
EJB規(guī)范的一個主要特性是容器管理的事務(wù)(CMT)。使用CMT,可以在EJB的部署描述子中聲明事務(wù)策略。Spring采用了EJB的聲明式事務(wù)模型,提供多個事務(wù)屬性源,使你可以對POJOs聲明事務(wù)策略。
5.4.1 使用NameMatchTransactionAttributeSource
NameMatchTransactionAttributeSource的properties屬性將方法名映射到事務(wù)屬性描述子。
事務(wù)屬性描述子的格式:
PROPAGATION,ISOLATION,readOnly,-Exceptions,+Exceptions
5.4.2 簡化名稱匹配的事務(wù)
TransactionProxyFactoryBean具有一個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ù)實現(xiàn)進行交互。
5.5.2 使用Commons Attribute聲明事務(wù)
聲明一個屬性實現(xiàn)
<bean id="attributesImpl" class="org.springframework.
metadata.commons.CommonsAttributes">
...
</bean>
標(biāo)記事務(wù)性方法
Jakarta Commons Attributes通過替換方法/類前面的注釋中doclet標(biāo)簽來實現(xiàn)。
設(shè)置Commons Attributes的構(gòu)建
Jakarta Commons Attributes的機制是預(yù)編譯你的代碼中的doclet標(biāo)簽,重寫你的類,在代碼中嵌入元數(shù)據(jù)。需要在構(gòu)建文件中加入與編譯器。
5.6 裁剪事務(wù)聲明
5.6.1 從父TransactionProxyFactoryBean繼承
不必每次都聲明TransactionProxyFactoryBean。
5.6.2 自動代理事務(wù)
使用AOP特性。
第六章 遠程訪問
6.1 Spring遠程訪問概覽
遠程訪問是客戶端應(yīng)用程序和服務(wù)之間的對話。客戶端應(yīng)用程序向其他系統(tǒng)請求功能,這些功能稱作遠程服務(wù)。Spring的遠程訪問支持六種RPC模型:RMI,Caucho’s Hessian and Burlap,Spring的HTTP Invoker,EJB和基于JAX-RPC的Web服務(wù)。
客戶端調(diào)用代理,好像是代理在提供服務(wù)功能,代理代表客戶端同遠程服務(wù)通信。在服務(wù)器端,可以使用以上六種模型將任何Spring管理的Bean的功能暴露為遠程服務(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ù)。
首先書寫服務(wù)接口,無須從java.rmi.Remote繼承。然后定義這個接口的實現(xiàn)類。下面就可以將這個實現(xiàn)類配置成Spring配置文件中的<bean>。然后使用RmiServiceExporter將此實現(xiàn)導(dǎo)出成服務(wù)。
RMI有很多局限性。首先,跨防火墻工作比較困難。因為RMI的通信使用任意端口,典型的防火墻都不允許這樣做。另外,RMI是基于Java的。這意味著服務(wù)器端和客戶端都必須使用Java編寫。
6.3 使用Hessian和Burlap進行遠程訪問
Hessian和Burlap是由Caucho Technology提供的兩個解決方案,它允許通過HTTP提供輕量級的遠程服務(wù)。Hessian同RMI一樣使用二進制消息在客戶端和服務(wù)端之間進行通信,但同其他二進制遠程訪問機制不同,Hessian的二進制消息是獨立于編程語言的。Burlap是一種基于XML的遠程訪問技術(shù)。
Hessian和Burlap的唯一區(qū)別就是基于二進制消息還是基于XML。
6.3.1 訪問Hessian和Burlap服務(wù)
在Spring中使用Hessian服務(wù)與使用基于RMI服務(wù)的唯一區(qū)別是使用HessianProxyFactoryBean。對于Burlap服務(wù),是使用BurlapProxyFactoryBean。
6.3.2 使用Hessian/Burlap暴露Bean功能
導(dǎo)出Hessian服務(wù)
使用HessianServiceExporter。
配置Hessian控制器
RmiServiceExporter與HessianServiceExporter之間的另一個主要區(qū)別是,因為Hessian是基于HTTP的,HessianServiceExporter實現(xiàn)為一個Spring MVC控制器。這意味著如果要導(dǎo)出Hessian服務(wù),需要執(zhí)行兩個額外的配置步驟:
<!--[if !supportLists]-->1. <!--[endif]-->在Spring配置文件中配置一個URL處理器,來對合適的Hessian服務(wù)bean分派Hessian服務(wù)URLs。
<!--[if !supportLists]-->2. <!--[endif]-->在web.xml中配置一個DispatcherServlet,將應(yīng)用程序部署為一個Web應(yīng)用程序。
將URL模式映射到Hessian服務(wù)。
導(dǎo)出Burlap服務(wù)
使用BurlapServiceExporter。
6.4 使用HTTP Invoker
HTTP Invoker是一種新的遠程訪問模型,它是Spring框架的一部分。它跨越HTTP執(zhí)行遠程訪問,而且使用Java的序列化機制。
6.4.1 通過HTTP訪問服務(wù)
使用HttpInvokerProxyFactoryBean。
6.4.2 將beans暴露為HTTP服務(wù)
HttpInvokerServiceExporter。
HTTP Invoker的局限性在于,它的服務(wù)端和客戶端都必須是基于Spring的。
6.5 使用EJB
6.5.1 訪問EJBs
代理EJBs
Spring提供了兩個代理工廠beans,來對EJBs進行代理: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相同。
工作機制
首先,在啟動的時候,LocalStatelessSessionProxyFactoryBean使用由jndiName屬性指定的JNDI名稱來通過JNDI查找EJB的本地home接口。然后,每次調(diào)用接口方法的時候,代理都調(diào)用本地home接口的create()方法獲取EJB的引用。最后,代理調(diào)用EJB相應(yīng)的方法。
訪問遠程EJB
使用SimpleRemoteStatelessSessionProxyFactoryBean。
6.5.2 開發(fā)支持Spring的EJBs
Spring提供了四個抽象的支持類來更容易地開發(fā)EJBs:
<!--[if !supportLists]-->n <!--[endif]-->AbstractMessageDrivenBean—對于開發(fā)消息驅(qū)動的beans比較有用。不接受來自JMS的消息。
<!--[if !supportLists]-->n <!--[endif]-->AbstractJmsMessageDrivenBean—接受來自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
實現(xiàn)簡單的功能都需要很多代碼。
6.6.2 在Spring中纏繞web服務(wù)
使用JaxRpcPortProxyFactoryBean。
第七章 訪問企業(yè)服務(wù)
7.1 從JNDI獲取對象
JNDI為Java應(yīng)用程序提供了一個存放應(yīng)用程序?qū)ο蟮闹行膸?。例如,典型的J2EE應(yīng)用程序使用JNDI來存取諸如JDBC數(shù)據(jù)源和JTA事務(wù)管理器之類的東西。
Spring的JNDI抽象,使你能夠在你的應(yīng)用程序配置文件中聲明JNDI查詢,從而可以將這些對象纏繞到其他beans的屬性中,好像JNDI對象也是POJO。
7.1.1 使用通常的JNDI
通常的JNDI使用方法違反依賴注入的原則。
7.1.2 代理JNDI對象
JndiObjectFactoryBean可以纏繞到一個屬性,它實際上創(chuàng)建JNDI對象并且纏繞到這個屬性。
7.2 發(fā)送E-mail
為發(fā)送e-mail需要一個郵件發(fā)送器,由Spring的MailSender接口定義,Spring自帶了兩個實現(xiàn):
<!--[if !supportLists]-->n <!--[endif]-->CosMailSenderImpl—SMTP郵件發(fā)送器的簡單實現(xiàn),基于Jason Hunter的COS實現(xiàn)。
<!--[if !supportLists]-->n <!--[endif]-->JavaMailSenderImpl—基于JavaMail API的實現(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 定時任務(wù)
兩個流行的定時API是Java的Timer類和OpenSymphony的Quartz Scheduler。Spring為二者都提供了抽象。
7.3.1 使用Java的Timer進行定時
創(chuàng)建定時任務(wù)
通過繼承java.util.TimerTask類。
對定時任務(wù)作出計劃
ScheduledTimerTask定義定時任務(wù)執(zhí)行的頻率。
啟動計時器
TimerFactoryBean。
缺陷是,只能設(shè)定運行的頻率,而不能精確設(shè)定每天何時運行定時任務(wù)。
7.3.2 使用quartz定時器
可以設(shè)定每天什么時候啟動任務(wù)。
創(chuàng)建一個Job
繼承Spring的QuartzJobBean。
計劃job
Quartz的Trigger類用來決定何時以何種方式運行Quartz Job。Spring自帶了兩個觸發(fā)器:SimpleTriggerBean和CronTriggerBean。
啟動job
SchedulerFactoryBean。
7.3.3 按計劃調(diào)用方法
可以定時執(zhí)行某個方法,而無需分別編寫Task或JobBean。Spring提供了MethodInvokingTimerTaskFactoryBean和MethodInvokingJobDetailFactoryBean。
7.4 使用JMS發(fā)送消息
Java Messaging Service是用作異步處理的Java API。JMS支持兩種消息機制:點對點和發(fā)布訂閱。
7.4.1 使用JMS模板發(fā)送消息
Spring利用回調(diào)機制來與JMS消息協(xié)調(diào)。
使用模板
使用JmsTemplate來發(fā)送消息。
纏繞模板
使用JMS1.0.2
區(qū)別是JmsTemplate102需要知道是在使用點對點還是發(fā)布訂閱模式。缺省情況下,JmsTemplate102假定你使用點對點模式。
處理JMS異常
不必處理JMSException。
7.4.2 處理消息
JmsTemplate也可以用來接收消息。
7.4.3 轉(zhuǎn)化消息
MessageConverter接口定義了將JMS消息轉(zhuǎn)化為對象的通用機制。
第三部分 Spring的web層
第八章 構(gòu)建Web層
8.1 開始使用Spring MVC
8.1.1 request的生命周期
從Spring接收到一個請求,到返回一個響應(yīng)到客戶端,牽扯到了Spring MVC框架的諸多方面。
過程開始于客戶端發(fā)送一個請求,接受這個請求的第一個組件是Spring的DispatcherServlet。同大部分基于Java的MVC框架一樣,Spring MVC使用一個唯一的前端控制器來處理request。在Spring MVC中DispatcherServlet就是前端控制器。負責(zé)處理請求的Spring MVC組件是controller,為了知道哪一個controller應(yīng)該處理某個請求,DispatcherServlet查詢一個或多個HandlerMapping。HandlerMapping的工作就是將URL模式映射到Controller對象。一旦DispatcherServlet得到Controller對象,它將request分派給這個控制器來執(zhí)行業(yè)務(wù)邏輯。執(zhí)行完業(yè)務(wù)邏輯后,Controller返回一個ModelAndView對象給DispatcherServlet。ModelAndView對象可以包含一個View對象或者View對象的名稱。如果包含的是View對象的名稱,DispatcherServlet通過ViewResolver來查找對應(yīng)的View對象。最后,DispatcherServlet將request分派給View對象,View對象負責(zé)產(chǎn)生一個響應(yīng)到客戶端。
8.1.2 配置DispatcherServlet
DispatcherServlet同任何servlet一樣,必需在web應(yīng)用的web.xml文件中配置。Servlet名稱很重要,因為DispatcherServlet根據(jù)基于這個名稱的xml文件加載應(yīng)用程序上下文。
分解應(yīng)用程序上下文
可以將各層的xml文件分開。便于維護。為保證所有這些配置文件都會被加載,必需在web.xml文件中配置上下文加載器。有兩個可供選擇的加載器:ContextLoaderListener和ContextLoaderServlet。
8.1.3 Spring MVC核心
構(gòu)造控制器
實現(xiàn)Controller接口,handleRequest方法。
配置Controller Bean
DispatcherServlet使用的缺省處理器映射是BeanNameUrlHandlerMapping。
聲明view resolver
View resolver的工作是將返回的ModelAndView中的view名稱映射為view對象。對于產(chǎn)生JSP的views,最簡單的莫過于使用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名稱映射為/WEB-INF/jsp/[view名稱].jsp
創(chuàng)建JSP
所有的組件放在一起工作
8.2 將requests映射到controllers
Handler Mappings將特定的Controller映射到一個URL模式。Spring MVC的所有handler mappings都實現(xiàn)org.springframework.web.servlet.HandlerMapping接口。Spring提供了三個有用的HandlerMapping的實現(xiàn):
<!--[if !supportLists]-->n <!--[endif]-->BeanNameUrlHandlerMapping—基于控制器的名稱將控制器映射到URLs。
<!--[if !supportLists]-->n <!--[endif]-->SimpleUrlHandlerMapping—使用在上下文配置文件中定義的屬性集合將控制器映射到URLs。
<!--[if !supportLists]-->n <!--[endif]-->CommonsPathMapHandlerMapping—使用controller代碼中的元數(shù)據(jù)將控制器映射到URLs。
8.2.1 將URLs映射到bean名稱
為設(shè)置bean名稱映射,首先在上下文配置文件中聲明BeanNameUrlHandlerMapping bean。然后將Controller的名稱的值設(shè)置為URL模式。
8.2.2 使用SimpleUrlHandlerMapping
可以直接將URL模式映射到controllers而無須為controllers命名。
8.2.3 使用元數(shù)據(jù)映射controllers
在配置文件中使用CommonsPathMapHandlerMapping聲明<bean>元素,然后在Controllers代碼中標(biāo)記PathMap屬性。最后,還需要設(shè)置Commons Attribute的編譯器。
8.2.4 使用多個HandlerMapping
每一個HandlerMapping類都實現(xiàn)了Ordered接口,這意味著可以聲明多個HandlerMapping,并設(shè)置order屬性來指定其優(yōu)先級。
8.3 使用Controllers處理請求
Spring MVC的controller類的可選擇的集合如下表所示:
類型
類
用于何時
Simple
Controller(接口)
AbstractController
當(dāng)你的Controller很簡單時使用,比起基本的servlet,無須更多的功能。
Throwaway
ThrowawayController
需要一個簡單的方式來將請求處理為命令。
Multi-Action
MultiActionController
應(yīng)用程序有執(zhí)行相似或相關(guān)邏輯的多個動作。
Command
BaseCommandController
AbstractCommandController
控制器從請求接受一個或多個參數(shù),并將他們綁定到一個對象。還可以執(zhí)行參數(shù)驗證。
Form
AbstractFormController
SimpleFormController
需要向用戶顯示一個記錄表單,并且處理輸入表單的數(shù)據(jù)。
Wizard
AbstractWizardFormController
需要使用戶經(jīng)過一個復(fù)雜的多頁的表單,并最終作為一個表單來處理。
8.3.1 編寫簡單的控制器
將你的Controller實現(xiàn)為AbstractController的子類。HandleRequestInternal方法是主要執(zhí)行的方法。
ModelAndView簡介
ModelAndView對象完全封裝view和model數(shù)據(jù)。
纏繞Controller
8.3.2 處理命令
擴展AbstractCommandController。這個控制器自動地將參數(shù)綁定到命令對象,而且提供掛鉤,使你能夠插入驗證器來確保參數(shù)是可用的。
Handler方法還接受一個Command參數(shù)。
8.3.3 處理form提交
Form控制器接受GET請求時顯示表單,接受POST請求時處理表單,控制器知道何時重新顯示表單,因此用戶可以糾正錯誤重新提交。
驗證輸入
Org.springframework.validation.Validator接口為Spring MVC提供驗證。
8.3.4 使用wizard處理復(fù)雜表單
構(gòu)建簡單的wizard controller
擴展AbstractWizardFormController類。
頁面跳轉(zhuǎn)
getTargetPage()
結(jié)束wizard
使用特殊的請求參數(shù)“_finish”。
取消wizard
增加取消按鈕,使用“_cancel”參數(shù)。
每次驗證一個表單
控制器的validatePage()方法,可以針對每個頁面進行驗證。
8.3.5 使用一個控制器處理多個動作
MultiActionController。
根據(jù)URL模式確定方法名。
解決方法名稱
MultiActionController基于方法名稱resolver來解決方法名稱。缺省的方法名稱解決器是InternalPathMethodNameResolver。Spring提供了另外兩種:ParameterMethodNameResolver和PropertiesMethodNameResolver。
8.3.6 使用Throwaway控制器
Throwaway控制器充當(dāng)自己的Command對象。
8.4 解決views
在Spring MVC中,一個view是一個為用戶產(chǎn)生結(jié)果的bean。View resolver是任何實現(xiàn)ViewResolver接口的bean。
Spring提供了ViewResolver的四個實現(xiàn):
<!--[if !supportLists]-->n <!--[endif]-->InternalResourceViewResolver—將邏輯view名稱解決為View對象,這些對象是使用模板文件資源生成。(如JSPs和Velocity模板)
<!--[if !supportLists]-->n <!--[endif]-->BeanNameViewResolver—將邏輯view名稱解決為DispatcherServlet的應(yīng)用程序上下文中的View Beans。
<!--[if !supportLists]-->n <!--[endif]-->ResourceBundlerViewResolver—將邏輯View名稱解決為包含在ResourceBundle中的View對象。
<!--[if !supportLists]-->n <!--[endif]-->XmlViewResolver—從XML文件解決View beans,這個XML文件是與DispatcherServlet的應(yīng)用程序上下文分開的。
8.4.1 使用模板views
InternalResourceViewResolver。如果使用jstl,可以將viewClass屬性設(shè)置成JstlView。
8.4.2 解決view beans
BeanNameViewResolver。在上下文配置文件中聲明為<bean>。View對象也要在上下文配置文件中聲明為<bean>。
在分離的XML文件中聲明view beans。
XmlFileViewResolver。
從Resouce bundle解決view
ResourceBundleViewResolver。
8.4.3 選擇View Resolver
推薦使用InternalResourceViewResolver。如果你的視圖將通過自定義的View生成,則建議使用BeanNameViewResolver。
使用多個View Resolver
可以使用多個,使用order屬性設(shè)定優(yōu)先級。
8.5 使用Spring的綁定標(biāo)簽
Spring標(biāo)簽庫。
8.6 處理異常
使用SimpleMappingExceptionResolver來處理異常。在<bean>定義中聲明要處理的異常。
第九章 View層的其他選擇
9.1 使用Velocity模板
9.1.1 聲明Velocity view
Velocity不是基于標(biāo)簽,而是基于自己的語言VTL。
9.1.2 配置Velocity引擎
在Spring配置文件中聲明VelocityConfigurer bean。通過resourceLoaderPath告訴Velocity到何處找到模板。
9.1.3 解決Velocity view
在上下文配置文件中聲明VelocityViewResolver。
9.1.4 格式化日期和數(shù)字
通過指定VelocityViewResolver的dateToolAttribute和numberToolAttribute屬性。
9.1.5 暴露request和session屬性
使用VelocityViewResolver的exposeRequestAttributes和exposeSessionAttributes屬性。
9.1.6 在Velocity中綁定表單字段
Spring自帶了幾個Velocity宏。
9.2 使用FreeMaker
FreeMaker比Velocity復(fù)雜一些,但是功能也更強大一些。具有內(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è)計頁面布局
9.3.1 Tiles view
寫好模板后,下一步是創(chuàng)建Tiles定義文件。
配置Tiles
TilesConfigurer。
解決Tiles view
使用InternalResourceViewResolver即可,只要將viewClass設(shè)定為TilesView。它將邏輯view名稱解決為Tiles定義。
9.3.2 Tiles控制器
Tiles的每一個組件都可以有自己單獨的控制器。只要在definition文件中設(shè)定每一個definition的controllerClass。
9.4 產(chǎn)生非HTML輸出
9.4.1 產(chǎn)生Excel表格
Spring提供了AbstractExcelView,只要繼承此類,實現(xiàn)它的buildExcelDocument()方法即可。它是基于POI的。
9.4.2 產(chǎn)生PDF文檔
AbstractPdfView。基于iText。
9.4.3 產(chǎn)生其他非HTML文件
只要實現(xiàn)View接口的render方法。
第十章 使用其他Web框架
10.1 使用Struts
10.1.1 注冊Spring插件
為使Struts可以訪問Spring管理的beans,必需注冊struts插件,使其知道Spring上下文環(huán)境的存在。在struts-config.xml中注冊ContextLoaderPlugin。
10.1.2 實現(xiàn)可訪問Spring的Struts Action
使Action繼承ActionSupport類。
10.1.3 代理Actions
DelegatingActionProxy。
將Actions纏繞為Spring beans
將Actions注冊為Spring beans。讓代理來找Actions。
使用request代理
DelegatingRequestProcessor。
10.2 使用Tapestry
略。
10.3 與JSF集成
略。
10.4 與WebWork集成
略。
第十一章 Spring應(yīng)用程序的安全性
本章介紹使用Spring AOP和servlet過濾器來保證應(yīng)用程序安全性。
11.1 Acegi安全系統(tǒng)簡介
Acegi安全框架為基于Spring的應(yīng)用程序提供聲明式安全。它提供一組beans集合,可以配置于Spring上下文環(huán)境,充分利用Spring對AOP和依賴注入的支持。
Acegi使用監(jiān)聽request的servlet過濾器來執(zhí)行認證和實施安全性。
Acegi可以在較低級別上通過保證方法調(diào)用的安全實施安全性。通過使用Spring AOP,Acegi代理對象,應(yīng)用方面來保證用戶具有適當(dāng)?shù)氖跈?quán)來調(diào)用安全方法。
11.1.1 安全監(jiān)聽器
安全監(jiān)聽器好比是門栓。
11.1.2 認證管理器
安全監(jiān)聽器的第一個“鎖眼”是認證管理器。認證管理器負責(zé)決定你是誰。通常通過用戶名和密碼來認證。
11.1.3 訪問決策管理器
一旦Acegi決定你是誰,它就必需決定你是否被授權(quán)訪問安全資源。訪問決策管理器是Acegi的第二個鎖眼。
11.1.4 運行管理器
運行管理器可以用來替換你的認證,使你可以訪問更深層的安全資源。
11.2 管理認證
在Acegi中,認證管理器的工作是確定用戶的身份。認證管理器通過net.sf.acegisecurity.AuthenticationManager接口定義:
public interface AuthenticationManager {
public Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}
Authenticate方法接受一個Authentication對象,并試圖認證用戶。如果認證成功,則返回一個完整的Authentication對象,包含用戶的授權(quán)信息。如果認證失敗,則拋出AuthenticationException異常。Acegi提供了AuthenticationManager的一個實現(xiàn):ProviderManager。
11.2.1 配置ProviderManager
ProviderManager將認證的職責(zé)代理到一個或多個認證提供者。ProviderManager的思想是使你能夠?qū)Χ鄠€身份管理資源認證用戶。ProviderManager自身并不進行認證,而是遍歷所有的認證提供者,直到其中一個成功認證用戶,或者遍歷完所有的認證提供者??梢栽赟pring配置文件中以如下形式配置ProviderManager:
<bean id="authenticationManager"
class="net.sf.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<ref bean="daoAuthenticationProvider"/>
<ref bean="passwordDaoProvider"/>
</list>
</property>
</bean>
通過providers屬性給予ProviderManager一組認證提供者。認證提供者由net.sf.acegisecurity.provider.AuthenticationProvider接口定義。Spring提供了這個接口的多個實現(xiàn),如下表所示:
認證提供者
目的
net.sf.acegisecurity.adapters.
AuthByAdapterProvider
使用容器適配器認證。
net.sf.acegisecurity.providers.cas.
CasAuthenticationProvider
使用CAS(Yale Central Authentication Service)認證。
net.sf.acegisecurity.providers.dao.
DaoAuthenticationProvider
從數(shù)據(jù)庫獲取用戶信息,包括用戶名和密碼。
net.sf.acegisecurity.providers.jaas.
JaasAuthenticationProvider
從JAAS登錄配置獲取用戶信息。
net.sf.acegisecurity.providers.dao.
PasswordDaoAuthenticationProvider
從數(shù)據(jù)庫獲取用戶信息,但是讓底層的數(shù)據(jù)存儲執(zhí)行實際的認證。
net.sf.acegisecurity.providers.rcp.
RemoteAuthenticationProvider
使用遠程服務(wù)認證。
net.sf.acegisecurity.runas.
RunAsImplAuthenticationProvider
認證其身份由運行管理器支配的用戶。
net.sf.acegisecurity.providers.
TestingAuthenticationProvider
單元測試。自動認為TestingAuthenticationProvider是可用的,不應(yīng)在產(chǎn)品中使用。
11.2.2 使用數(shù)據(jù)庫認證
DaoAuthenticationProvider使用它的DAO獲取用戶名和密碼,并使用獲取的用戶名和密碼認證用戶。PasswordAuthentcationProvider將認證的責(zé)任推給了DAO。
聲明DAO認證提供者
<bean id="authenticationProvider" class="net.sf.acegisecurity.
providers.dao.DaoAuthenticationProvider">
<property name="authenticationDao">
<ref bean="authenticationDao"/>
</property>
</bean>
authenticationDao屬性標(biāo)識用來從數(shù)據(jù)庫獲取用戶信息的bean。這個屬性應(yīng)當(dāng)是net.sf.acegisecurity.providers.dao.AuthenticationDao。Acegi提供了AuthenticationDao的兩個實現(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)用來管理安全。
聲明JDBC DAO
<bean id="authenticationDao"
class="net.sf.acegisecurity.providers.dao.jdbc.JdbcDaoImpl">
<property name="dataSource">
<ref bean="dataSource"/>
</property>
</bean>
JdbcDaoImpl假定你在數(shù)據(jù)庫中使用特定的表來存儲用戶信息,特別地,使用“Users”表和“Authorities”表。但可以使用usersByUserNameQuery來設(shè)定如何獲取用戶信息。JdbcDaoImpl還提供了usersByUserNameMapping屬性來引用MappingSqlQuery接口。這個接口將ResultSet轉(zhuǎn)換成UserDetails對象。
同樣可以通過設(shè)定authoritiesByUserNameQuery和authoritiesByUserNameMapping屬性來獲取授權(quán)信息。
使用加密密碼
Acegi提供了三個密碼解碼器:
<!--[if !supportLists]-->n <!--[endif]-->PlainTextPasswordEncoder(缺?。?-不執(zhí)行任何解碼。
<!--[if !supportLists]-->n <!--[endif]-->Md5PasswordEncoder—執(zhí)行MD5解碼。
<!--[if !supportLists]-->n <!--[endif]-->ShaPasswordEncoder—執(zhí)行安全哈希算法解碼。
可以通過更改DaoAuthenticationProvider的passwordEncoder屬性來設(shè)定解碼器。還可以對解碼器使用salt source,即密鑰(encrytion key)。Acegi提供了兩個salt sources:ReflectionSaltSource和SystemWideSaltSource。
緩存用戶信息
DaoAuthenticationProvider通過UserCache接口的實現(xiàn)來支持用戶信息的緩存。
public interface UserCache {
public UserDetails getUserFromCache(String username);
public void putUserInCache(UserDetails user);
public void removeUserFromCache(String username);
}
Acegi提供了兩種實現(xiàn):NullUserCache和EhCacheBasedUserCache。前者不執(zhí)行任何緩存。后者基于開源的ehcache項目。
<bean id="userCache" class="net.sf.acegisecurity.
providers.dao.cache.EhCacheBasedUserCache">
<property name="minutesToIdle">15</property>
</bean>
minutesToIdle設(shè)定在沒有讀取緩存的情況下,用戶信息應(yīng)當(dāng)駐留多長時間。
11.2.3 使用LDAP中心庫進行認證
PasswordDaoAuthenticationProvider將實際的認證代理給它的Dao。
<bean id="authenticationProvider" class="net.sf.acegisecurity.
providers.dao.PasswordDaoAuthenticationProvider">
<property name="passwordAuthenticationDao">
<ref bean="passwordAuthenticationDao"/>
</property>
</bean>
這個Dao屬性應(yīng)當(dāng)實現(xiàn)PasswordAuthenticationDao接口。Acegi不提供這個接口的實現(xiàn),但是在它的sandbox中有LdapPasswordAuthenticationDao實現(xiàn)。
11.2.4 使用Acegi和Yale CAS支持單點登錄
當(dāng)在Acegi中使用CAS時,Acegi代表應(yīng)用程序驗證CAS ticket。它通過使用CasAuthenticationProvider來完成。
11.3 控制訪問
如同認證管理器負責(zé)建立用戶的身份一樣,訪問決策管理器負責(zé)決定用戶是否有適當(dāng)?shù)臋?quá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方法考量資源的類型和配置屬性(安全資源的訪問必需條件),據(jù)此決定訪問決策管理器是否可以對資源做出訪問決策。Decide方法是作出最終決策的地方,如果它正常返回不拋出異常的話,則授權(quán)對安全資源的訪問。否則,拒絕訪問。
11.3.1 投票訪問決策
Acegi提供了AcessDecisionManager的三個實現(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ū)別之處在于訪問決策管理器如何作出最后的決策,如下表所示:
訪問決策管理器
如何決策
AffirmativeBased
如果至少有一個投票者投票授權(quán)訪問,則允許訪問。
ConsensusBased
如果大多數(shù)投票者投票授權(quán)訪問,則允許訪問。
UnanimousBased
只有當(dāng)沒有任何投票者拒絕訪問的時候,才允許訪問。
例如,以下對UnanimousBased的配置:
<bean id="accessDecisionManager"
class="net.sf.acegisecurity.vote.UnanimousBased">
<property name="decisionVoters">
<list>
<ref bean="roleVoter"/>
</list>
</property>
</bean>
11.3.2 決定如何投票
訪問決策投票者是任何實現(xiàn)接口net.sf.acegisecurity.vote.AccessDecisionVoter接口的對象:
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實現(xiàn)。處理基于角色的安全資源配置。
11.3.3 處理角色禁止
如果所有的投票都是拒絕訪問,則訪問決策管理器拒絕對安全資源的訪問。但是可以覆蓋這種缺省設(shè)置。
11.4 保護Web應(yīng)用
Acegi對Web安全性的支持主要基于servlet過濾器。這些過濾器截獲傳入的請求,應(yīng)用一些安全性處理,然后請求才有應(yīng)用程序處理。Acegi提供了六種過濾器。如下表所示:
過濾器
目的
通道處理過濾器
確保請求是由安全的通道傳輸(如https)。
認證處理過濾器
接受認證請求,將請求傳輸給認證管理器來執(zhí)行認證。
CAS處理過濾器
接受CAS票據(jù)作為一個Yale CAS已經(jīng)認證一個用戶的證明。
HTTP基本認證過濾器
處理由HTTP基本認證執(zhí)行的認證。
集成過濾器
處理請求間的認證存儲(例如,在HTTP Session中的)。
安全執(zhí)行過濾器
確保用戶已經(jīng)被認證,具有訪問受保護Web資源的屬性授權(quán)必要條件。
當(dāng)請求提交到Acegi保護的Web應(yīng)用時,請求被傳遞給所有Acegi的過濾器。
11.4.1 代理Acegi過濾器
Acegi提供了FilterToBeanProxy,它將工作代理給Spring應(yīng)用上下文中的bean。代理bean同其他servlet過濾器一樣,實現(xiàn)javax.servlet.Filter接口,只是在Spring配置文件中配置。
在web.xml中只包含對FilterToBeanProxy的聲明。最后,還要將過濾器與URL模式關(guān)聯(lián)。
11.4.2 執(zhí)行Web安全性
安全性執(zhí)行過濾器處理請求截獲,決定請求是否是安全的,并給認證和訪問決策管理器一個機會來驗證用戶的身份和特權(quán)。
使用過濾器安全截獲器
11.4.3 處理login
11.4.6 使用Acegi標(biāo)簽庫
只有一個標(biāo)簽<authz:authorize>。
11.5 方法調(diào)用安全性
11.5.1 創(chuàng)建安全性方面