欧美猛男男男激情videos,wwwxxx在线观看,色噜噜色狠狠狠狠狠综合色一http://www.aygfsteel.com/lanxin1020/category/38969.htmlzh-cnSat, 11 Apr 2009 04:33:37 GMTSat, 11 Apr 2009 04:33:37 GMT60Introducing to Spring Framework (轉(zhuǎn))http://www.aygfsteel.com/lanxin1020/archive/2009/04/07/264363.htmllanxin1020lanxin1020Tue, 07 Apr 2009 15:30:00 GMThttp://www.aygfsteel.com/lanxin1020/archive/2009/04/07/264363.htmlhttp://www.aygfsteel.com/lanxin1020/comments/264363.htmlhttp://www.aygfsteel.com/lanxin1020/archive/2009/04/07/264363.html#Feedback0http://www.aygfsteel.com/lanxin1020/comments/commentRss/264363.htmlhttp://www.aygfsteel.com/lanxin1020/services/trackbacks/264363.htmlIntroducing to Spring Framework

作者:Rod Johnson
譯者:yanger,taowen
校對(duì):taowen

關(guān)于Spring Framework,今年夏天你可能已經(jīng)聽見很多的議論。在本文中,我將試圖解釋Spring能完成什么,和我怎么會(huì)認(rèn)為它能幫助你開發(fā)J2EE應(yīng)用程序。

又來一個(gè)framework?

你可能正在想“不過是另外一個(gè)的framework”。當(dāng)已經(jīng)有許多開放源代碼(和專有) J2EE framework時(shí),為什么你還要耐下心子讀這篇文章或去下載Spring Framework?

我相信Spring是獨(dú)特的,有幾個(gè)原因:



它關(guān)注的領(lǐng)域是其他許多流行的Framework未曾關(guān)注的。Spring要提供的是一種管理你的業(yè)務(wù)對(duì)象的方法。

Spring既是全面的又是模塊化的。Spring有分層的體系結(jié)構(gòu),這意味著你能選擇僅僅使用它任何一個(gè)獨(dú)立的部分,而它的架構(gòu)又是內(nèi)部一致。因此你能從你的學(xué)習(xí)中,得到最大的價(jià)值。例如,你可能選擇僅僅使用Spring來簡(jiǎn)單化JDBC的使用,或用來管理所有的業(yè)務(wù)對(duì)象。

它的設(shè)計(jì)從一開始就是要幫助你編寫易于測(cè)試的代碼。Spring是使用測(cè)試驅(qū)動(dòng)開發(fā)的工程的理想框架。


Spring不會(huì)給你的工程添加對(duì)其他的框架依賴。Spring也許稱得上是個(gè)一站式解決方案,提供了一個(gè)典型應(yīng)用所需要的大部分基礎(chǔ)架構(gòu)。它還涉及到了其他framework沒有考慮到的內(nèi)容。

盡管它僅僅是一個(gè)從2003年2月才開始的開源項(xiàng)目,但Spring有深厚的歷史根基。這個(gè)開源工程是起源自我在2002年晚些時(shí)候出版的《Expert One-on-One J2EE設(shè)計(jì)與開發(fā)》書中的基礎(chǔ)性代碼。這本書展示了Spring背后的基礎(chǔ)性架構(gòu)思想。然而,對(duì)這個(gè)基礎(chǔ)架構(gòu)的概念可以追溯到2000年的早些時(shí)候,并且反映了我為一系列商業(yè)工程開發(fā)基礎(chǔ)結(jié)構(gòu)的成功經(jīng)驗(yàn)。

2003年1月,Spring已經(jīng)落戶于SourceForge上了?,F(xiàn)在有10個(gè)開發(fā)人員,其中6個(gè)是高度投入的積極分子。

Spring架構(gòu)上的好處

在我們進(jìn)入細(xì)節(jié)之前,讓我們來看看Spring能夠給工程帶來的種種好處:



Spring能有效地組織你的中間層對(duì)象,不管你是否選擇使用了EJB。如果你僅僅使用了Struts或其他為J2EE的 API特制的framework,Spring致力于解決剩下的問題。

Spring能消除在許多工程中常見的對(duì)Singleton的過多使用。根據(jù)我的經(jīng)驗(yàn),這是一個(gè)很大的問題,它降低了系統(tǒng)的可測(cè)試性和面向?qū)ο蟮某潭取?

通過一種在不同應(yīng)用程序和項(xiàng)目間一致的方法來處理配置文件,Spring能消除各種各樣自定義格式的屬性文件的需要。曾經(jīng)對(duì)某個(gè)類要尋找的是哪個(gè)魔法般的屬性項(xiàng)或系統(tǒng)屬性感到不解,為此不得不去讀Javadoc甚至源編碼?有了Spring,你僅僅需要看看類的JavaBean屬性。 Inversion of Control的使用(在下面討論)幫助完成了這種簡(jiǎn)化。

通過把對(duì)接口編程而不是對(duì)類編程的代價(jià)幾乎減少到?jīng)]有,Spring能夠促進(jìn)養(yǎng)成好的編程習(xí)慣。

Spring被設(shè)計(jì)為讓使用它創(chuàng)建的應(yīng)用盡可能少的依賴于他的APIs。在Spring應(yīng)用中的大多數(shù)業(yè)務(wù)對(duì)象沒有依賴于Spring。

使用Spring構(gòu)建的應(yīng)用程序易于單元測(cè)試。

Spring能使EJB的使用成為一個(gè)實(shí)現(xiàn)選擇,而不是應(yīng)用架構(gòu)的必然選擇。你能選擇用POJOs或local EJBs來實(shí)現(xiàn)業(yè)務(wù)接口,卻不會(huì)影響調(diào)用代碼。

Spring幫助你解決許多問題而無需使用EJB。Spring能提供一種EJB的替換物,它們適用于許多web應(yīng)用。例如,Spring能使用AOP提供聲明性事務(wù)管理而不通過EJB容器,如果你僅僅需要與單個(gè)數(shù)據(jù)庫打交道,甚至不需要一個(gè)JTA實(shí)現(xiàn)。

Spring為數(shù)據(jù)存取提供了一個(gè)一致的框架,不論是使用的是JDBC還是O/R mapping產(chǎn)品(如Hibernate)。


Spring確實(shí)使你能通過最簡(jiǎn)單可行的解決辦法來解決你的問題。而這是有有很大價(jià)值的。

Spring做了些什么?

Spring提供許多功能,在此我將依次快速地展示其各個(gè)主要方面。

任務(wù)描述

首先,讓我們明確Spring范圍。盡管Spring覆蓋了許多方面,但我們對(duì)它應(yīng)該涉什么,什么不應(yīng)該涉及有清楚的認(rèn)識(shí)。

Spring的主要目的是使J2EE易用和促進(jìn)好編程習(xí)慣。

Spring不重新輪子。因此,你發(fā)現(xiàn)在Spring中沒有l(wèi)ogging,沒有連接池,沒有分布式事務(wù)調(diào)度。所有這些東西均有開源項(xiàng)目提供(例如我們用于處理所有日志輸出的Commons Logging以及Commons DBCP),或由你的應(yīng)用程序服務(wù)器提供了。出于同樣的的原因,我們沒有提供 O/R mapping層。對(duì)于這個(gè)問題已經(jīng)有了像Hibernate和JDO這樣的優(yōu)秀解決方案。

Spring的目標(biāo)就是讓已有的技術(shù)更加易用。例如,盡管我們沒有底層事務(wù)協(xié)調(diào)處理,但我們提供了一個(gè)抽象層覆蓋了JTA或任何其他的事務(wù)策略。

Spring沒有直接和其他的開源項(xiàng)目競(jìng)爭(zhēng),除非我們感到我們能提供新的一些東西。例如,象許多開發(fā)人員一樣,我們從來沒有對(duì)Struts感到高興過,并且覺得到在MVC web framework中還有改進(jìn)的余地。在某些領(lǐng)域,例如輕量級(jí)的IoC容器和AOP框架,Spring確實(shí)有直接的競(jìng)爭(zhēng),但是在這些領(lǐng)域還沒有已經(jīng)較為流行的解決方案。(Spring在這些領(lǐng)域是開路先鋒。)

Spring也得益于內(nèi)在的一致性。所有的開發(fā)者都在唱同樣的的贊歌,基礎(chǔ)想法依然與Expert One-on-One J2EE設(shè)計(jì)與開發(fā)中提出的差不多。 并且我們已經(jīng)能夠在多個(gè)領(lǐng)域中使用一些中心的概念,例如Inversion of Control。

Spring在應(yīng)用服務(wù)器之間是可移植的。當(dāng)然保證可移植性總是一種挑戰(zhàn),但是我們避免使用任何平臺(tái)特有或非標(biāo)準(zhǔn)的東西,并且支持在WebLogic,Tomcat,Resin,JBoss,WebSphere和其他的應(yīng)用服務(wù)器上的用戶。

Inversion of Control 容器

Spring設(shè)計(jì)的核心是 org.springframework.beans 包, 它是為與JavaBeans一起工作而設(shè)計(jì)的。 這個(gè)包一般不直接被用戶使用,而是作為許多其他功能的基礎(chǔ)。

下一個(gè)層面高一些的抽象是"Bean Factory"。一個(gè)Spring bean factory 是一個(gè)通用的Factory,它使對(duì)象能夠按名稱獲取,并且能管理對(duì)象之間的關(guān)系。

Bean factories 支持兩種模式的對(duì)象:



Singleton:在此模式中,有一個(gè)具有特定名稱的共享對(duì)象實(shí)例,它在查找時(shí)被獲取。這是默認(rèn)的,而且是最為經(jīng)常使用的。它對(duì)于無狀態(tài)對(duì)象是一種理想的模式。

Prototype:在此模式中,每次獲取將創(chuàng)建一個(gè)獨(dú)立的對(duì)象。例如,這可以被用于讓用戶擁有他們自己的對(duì)象。



由于 org.springframwork.beans.factory.BeanFactory是一個(gè)簡(jiǎn)單的接口,它能被大量底層存儲(chǔ)方法實(shí)現(xiàn)。你能夠方便地實(shí)現(xiàn)你自己的BeanFactory,盡管很少用戶需要這么做。最為常用的BeanFactory定義是:



XmlBeanFactory: 可解析簡(jiǎn)單直觀的定義類和命名對(duì)象屬性的XML結(jié)構(gòu)。 我們提供了一個(gè)DTD來使編寫更容易。

ListableBeanFactoryImpl:提供了解析存放在屬性文件中的bean定義的能力,并且可通過編程創(chuàng)建BeanFactories。


每個(gè)bean定義可能是一個(gè)POJO(通過類名和JavaBean初始屬性定義),或是一個(gè)FactoryBean。FactoryBean接口添加了一個(gè)間接層。通常,這用于創(chuàng)建使用AOP或其他方法的代理對(duì)象:例如,添加聲明性事務(wù)管理的代理。(這在概念上和EJB的interception相似,但實(shí)現(xiàn)得更簡(jiǎn)單。)

BeanFactories能在一個(gè)層次結(jié)構(gòu)中選擇性地參與,繼承ancestor(祖先)的定義。這使得在整個(gè)應(yīng)用中公共配置的共享成為可能,雖然個(gè)別資源,如controller servlets,還擁有他們自己的獨(dú)立的對(duì)象集合。

這種使用JavaBeans的動(dòng)機(jī)在《Expert One-on-One J2EE Design and Development》的第四章中有描述,在TheServerSide網(wǎng)站上的有免費(fèi)的PDF版本(http://www.theserverside.com/resources/article.jsp?l=RodJohnsonInterview)。

通過BeanFactory概念,Spring成為一個(gè)Inversion of Control的容器。(我不怎么喜歡container這個(gè)詞,因?yàn)樗谷寺?lián)想到重量級(jí)容器,如EJB容器。Spring的BeanFactory是一個(gè)可通過一行代碼創(chuàng)建的容器,并且不需要特殊的部署步驟。)

Inversion of Control背后的概念經(jīng)常表述為Hollywood原則的:“Don’t call me,  I’ll call you。” IoC將控制創(chuàng)建的職責(zé)搬進(jìn)了框架中,并把它從應(yīng)用代碼脫離開來。涉及到配置的地方,意思是說在傳統(tǒng)的容器體系結(jié)構(gòu)中,如EJB,一個(gè)組件可以調(diào)用容器并問“我需要它給我做工作的對(duì)象X在哪里?”;使用IoC容器則只需指出組件需要X對(duì)象,在運(yùn)行時(shí)容器會(huì)提供給它。容器是通過查看方法的參數(shù)表(例如JavaBean的屬性)做到的,也可能根據(jù)配置數(shù)據(jù)如XML。

IoC有幾個(gè)重要的好處,例如:



因?yàn)榻M件不需要在運(yùn)行時(shí)間尋找合作者,所以他們可以更簡(jiǎn)單的編寫和維護(hù)。在Spring版的IoC里,組件通過暴露JavaBean的setter方法表達(dá)他們依賴的其他組件。這相當(dāng)于EJB通過JNDI來查找,EJB查找需要開發(fā)人員編寫代碼。

同樣原因,應(yīng)用代碼更容易測(cè)試。JavaBean屬性是簡(jiǎn)單的,屬于Java核心的,并且是容易測(cè)試的:僅編寫一個(gè)自包含的Junit測(cè)試方法用來創(chuàng)建對(duì)象和設(shè)置相關(guān)屬性即可。

一個(gè)好的IoC實(shí)現(xiàn)保留了強(qiáng)類型。如果你需要使用一個(gè)通用的factory來尋找合作者,你必須通過類型轉(zhuǎn)換將返回結(jié)果轉(zhuǎn)變?yōu)橄胍念愋汀_@不是一個(gè)大不了的問題,但是不雅觀。使用IoC,你在你的代碼中表達(dá)了強(qiáng)類型依賴,框架將負(fù)責(zé)類型轉(zhuǎn)換。這意味著在框架配置應(yīng)用時(shí),類型不匹配將導(dǎo)致錯(cuò)誤;在你的代碼中,你無需擔(dān)心類型轉(zhuǎn)換異常。

大部分業(yè)務(wù)對(duì)象不依賴于IoC容器的APIs。這使得很容易使用遺留下來的代碼,且很容易的使用對(duì)象無論在容器內(nèi)或不在容器內(nèi)。例如,Spring用戶經(jīng)常配置Jakarta Commons DBCP數(shù)據(jù)源為一個(gè)Spring bean:不需要些任何定制代碼去做這件事。我們說一個(gè)IoC容器不是侵入性的:使用它并不會(huì)使你的代碼依賴于它的APIs。任何JavaBean在Spring bean factory中都能成為一個(gè)組件。


最后應(yīng)該強(qiáng)調(diào)的是,IoC 不同于傳統(tǒng)的容器的體系結(jié)構(gòu),如EJB,應(yīng)用代碼最小程度地依靠于容器。這意味著你的業(yè)務(wù)對(duì)象可以潛在的被運(yùn)行在不同的IoC 框架上——或者在任何框架之外——不需要任何代碼的改動(dòng)。

以我和其他Spring用戶的經(jīng)驗(yàn)來說,再怎么強(qiáng)調(diào)IoC給應(yīng)用程序代碼帶來的好處也不為過。

IoC不是一個(gè)新概念,但是它在J2EE團(tuán)體里面剛剛到達(dá)黃金時(shí)間。 有一些可供選擇的IoC 容器: 例如 Apache Avalon,  PicoContainer 和 HiveMind。Avalon 從沒怎么流行,盡管它很強(qiáng)大而且有很長(zhǎng)的歷史。Avalon相當(dāng)?shù)闹睾蛷?fù)雜,并且看起來比新的IoC解決方案更具侵入性。 PicoContainer是一個(gè)輕量級(jí)而且更強(qiáng)調(diào)通過構(gòu)造函數(shù)表達(dá)依賴性而不是JavaBean 屬性。 與 Spring不同,它的設(shè)計(jì)允許每個(gè)類型一個(gè)對(duì)象的定義(可能是因?yàn)樗芙^任何Java代碼外的元數(shù)據(jù)導(dǎo)致的局限性)。在Spring,  PicoContainer 和其他 IoC frameworks之間做比較,可參看文章Spring網(wǎng)站上的 "The Spring Framework - A Lightweight Container"位于http://www.springframework.org/docs/lightweight_container.html。這個(gè)頁面里面包含了PicoContainer站點(diǎn)的鏈接 。

Spring BeanFactories 是非常輕量級(jí)的。用戶已經(jīng)成功地將他們應(yīng)用在applets和單獨(dú)的Swing應(yīng)用中。(它們也很好地工作在 EJB容器中。) 沒有特殊的部署步驟和察覺得到的啟動(dòng)時(shí)間。這個(gè)能力表明一個(gè)容器在應(yīng)用的任何層面幾乎立即可以發(fā)揮非常大的價(jià)值。

Spring BeanFactory 概念貫穿于Spring始終, 而且是Spring如此內(nèi)在一致的關(guān)鍵原因。在IoC容器中,Spring也是唯一的,它使用IoC作為基礎(chǔ)概念貫穿于整個(gè)功能豐富的框架。

對(duì)應(yīng)用開發(fā)人員,最重要的是,一個(gè)或多個(gè)BeanFactory提供了一個(gè)定義明確的業(yè)務(wù)對(duì)象層。這類似于local session bean層,但比它更簡(jiǎn)單。與EJBs不同,在這個(gè)層中的對(duì)象可能是相關(guān)的,并且他們的關(guān)系被擁有它們的factory管理。有一個(gè)定義明確的業(yè)務(wù)對(duì)象層對(duì)于成功的體系結(jié)構(gòu)是非常重要的。

Spring ApplicationContext 是BeanFactory的子接口,為下列東西提供支持:



信息查找,支持著國際化

事件機(jī)制,允許發(fā)布應(yīng)用對(duì)象以及可選的注冊(cè)以接收到事件

可移植的文件和資源訪問


XmlBeanFactory 例子

Spring用戶通常在XML的“bean定義”文件中配置他們的應(yīng)用。Spring的XML bean定義文檔的根是<beans> 元素。該元素包含一個(gè)或多個(gè) <bean>定義。我們一般給每個(gè)bean定義的指定類和屬性。我們還必須指定ID作為標(biāo)識(shí),這將成為在代碼中使用該bean的名字。

讓我們來看一個(gè)簡(jiǎn)單的例子,它配置了三個(gè)應(yīng)用程序?qū)ο?,之間的關(guān)系在J2EE應(yīng)用中常常能夠看到:



J2EE DataSource

使用DataSource的DAO

在處理過程中使用DAO的業(yè)務(wù)對(duì)象


在下面的例子中,我們使用一個(gè)來自Jakarta Commons DBCP項(xiàng)目的BasicDataSource。這個(gè)class(和其他許多已有的 class一樣)可以簡(jiǎn)單地被應(yīng)用在Spring bean factory中,只要它提供了JavaBean格式的配置。需要在shutdown時(shí)被調(diào)用的Close方法可通過Spring的"destroy-method"屬性被注冊(cè),以避免BasicDataSource需要實(shí)現(xiàn)任何Spring  的接口。

代碼:
<beans>

  <bean id="myDataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
    <property name="driverClassName"><value>com.mysql.jdbc.Driver</value></property>
    <property name="url"><value>jdbc:mysql://localhost:3306/mydb</value></property>
    <property name="username"><value>root</value></property>
  </bean>

BasicDataSource中我們感興趣的所有屬性都是String類型的,因此我們用<value>元素來指定他們的值。如果必要的話,Spring使用標(biāo)準(zhǔn)的 JavaBean屬性編輯器機(jī)制來把String轉(zhuǎn)換為其他的類型。

現(xiàn)在,我們定義DAO,它有一個(gè)對(duì)DataSource的bean引用。Bean間關(guān)系通過<ref>元素來指定:

代碼:
<bean id="exampleDataAccessObject"
      class="example.ExampleDataAccessObject">
    <property name="dataSource"><ref bean="myDataSource"/></property>
  </bean>

The business object has a reference to the DAO, and an int property (exampleParam):
<bean id="exampleBusinessObject"
      class="example.ExampleBusinessObject">
    <property name="dataAccessObject"><ref bean="exampleDataAccessObject"/></property>
    <property name="exampleParam"><value>10</value></property>
  </bean>

</beans>

對(duì)象間的關(guān)系一般在配置中明確地設(shè)置,象這個(gè)例子一樣。我們認(rèn)為這樣做是件好事情。然而Spring還提供了我們稱做"autowire"的支持, 一個(gè) la PicoContainer,其中它指出了bean間的依賴關(guān)系。這樣做的局限性——PicoContainer也是如此——是如果有一個(gè)特殊類型的多個(gè)Bean,要確定那個(gè)類型所依賴的是哪個(gè)實(shí)例是不可能。好的方面是,不滿足的依賴可以在factory初始化后被捕獲到。(Spring 也為顯式的配置提供了一種可選的依賴檢查,它可以完成這個(gè)目的)

在上面的例子中,如果我們不想顯式的編寫他們的關(guān)系,可使用如下的autowire特性:

代碼:
<bean id="exampleBusinessObject"
   class="example.ExampleBusinessObject"
   autowire="byType">

    <property name="exampleParam"><value>10</value></property>
</bean>


使用這個(gè)特性,Spring會(huì)找出exampleBusinessObject的dataSource屬性應(yīng)該被設(shè)置為在當(dāng)前BeanFactory中找到的DataSource實(shí)現(xiàn)。在當(dāng)前的BeanFactory中,如果所需要類型的bean不存在或多于一個(gè),將產(chǎn)生一個(gè)錯(cuò)誤。我們依然要設(shè)置 exampleParam屬性,因?yàn)樗皇且粋€(gè)引用。

Autowire支持和依賴檢查剛剛加入CVS并將在Spring 1.0 M2(到10/20,2003)中提供。本文中所討論的所有其他特性都包含在當(dāng)前1.0 M1版本中。

把管理從Java代碼中移出來比硬編碼有很大的好處,因?yàn)檫@樣可以只改變XML文件而無需改變一行Java代碼。例如,我們可以簡(jiǎn)單地改變 myDataSource的bean定義引用不同的bean class以使用別的連接池,或者一個(gè)用于測(cè)試的數(shù)據(jù)源。 XML節(jié)變成另一種,我們可以用 Spring的JNDI location FactoryBean從應(yīng)用服務(wù)器獲取一個(gè)數(shù)據(jù)源。

現(xiàn)在讓我們來看看例子中業(yè)務(wù)對(duì)象的java 代碼。注意下面列出的代碼中沒有對(duì)Spring的依賴。不像EJB容器,Spring BeanFactory不具有侵入性:在應(yīng)用對(duì)象里面你通常不需要對(duì)Spring的存在硬編碼。

代碼:
public class ExampleBusinessObject implements MyBusinessObject {

   private ExampleDataAccessObject dao;
   private int exampleParam;

   public void setDataAccessObject(ExampleDataAccessObject dao) {
      this.dao = dao;
   }

   public void setExampleParam(int exampleParam) {
      this.exampleParam = exampleParam;
   }

   public void myBusinessMethod() {
      // do stuff using dao
   }
}

注意那些property setter,它們對(duì)應(yīng)于bean定義文檔中的XML引用。這些將在對(duì)象被使用之前由Spring調(diào)用。

這些應(yīng)用程序的bean不需要依賴于Spring:他們不需要實(shí)現(xiàn)任何Spring的接口或者繼承Spring的類。他們只需要遵守JavaBeans的命名習(xí)慣。在Spring 應(yīng)用環(huán)境之外重用它們是非常簡(jiǎn)單的,例如,在一個(gè)測(cè)試環(huán)境中。只需要用它們的缺省構(gòu)造函數(shù)實(shí)例化它們,并且通過調(diào)用 setDataSource()和setExampleParam()手工設(shè)置它的屬性。如果你想以一行代碼支持程序化的創(chuàng)建,只要你有一個(gè)無參數(shù)的構(gòu)造器,你就可以自由定義其他需要多個(gè)屬性的構(gòu)造函數(shù)。

注意在業(yè)務(wù)接口中沒有聲明將會(huì)一起使用的JavaBean屬性。 他們是一個(gè)實(shí)現(xiàn)細(xì)節(jié)。我們可以“插入”帶有不同bean屬性的不同的實(shí)現(xiàn)類而不影響連接著的對(duì)象或者調(diào)用的代碼。

當(dāng)然,Spring XML bean factories 有更多的功能沒有在這里描述,但是,應(yīng)當(dāng)讓你對(duì)基本使用有了一些感覺。以及,簡(jiǎn)單的屬性,有 JavaBean屬性編輯器的屬性,Spring可以自動(dòng)處理lists,maps和java.util.Properties。

Bean factories 和application contexts 通常和J2EE server定義的一個(gè)范圍相關(guān)聯(lián),例如:



Servlet context.:在spring 的MVC 框架里, 每一個(gè)包含common objects的web 應(yīng)用都定義有一個(gè)應(yīng)用程序的 context。Spring提供了通過listener或者servlet實(shí)例化這樣的context的能力而不需要依賴于Spring 的MVC 框架,因而它也可以用于Struts,WebWork 或者其他的web框架之中。

A Servlet:在Spring MVC 框架里每一個(gè)servlet控制器都有它自己的應(yīng)用程序context,派生于根(全應(yīng)用程序范圍的)應(yīng)用程序context。在Struts或者其他MVC框架中實(shí)現(xiàn)這些也很容意。

EJB:Spring 為EJB提供方便的超類,它們簡(jiǎn)化了EJB的創(chuàng)建并且提供了一個(gè)從EJB Jar 文件中的XML文檔載入的BeanFactory。


這些J2EE規(guī)范提供的hook通常避免了使用Singleton來創(chuàng)造一個(gè)bean factory。

然而,如果我們?cè)敢獾脑捒梢杂么a創(chuàng)建一個(gè)BeanFactory,雖然是沒有什么意義的。例如,我們?cè)谝韵氯写a中可以創(chuàng)建bean factory并且得到一個(gè)業(yè)務(wù)對(duì)象的引用:

代碼:
InputStream is = getClass().getResourceAsStream("myFile.xml");
XmlBeanFactory bf = new XmlBeanFactory(is);
MyBusinessObject mbo = (MyBusinessObject) bf.getBean("exampleBusinessObject");



這段代碼將能工作在一個(gè)應(yīng)用服務(wù)器之外:甚至不依賴J2EE,因?yàn)镾pring 的IoC容器是純java的。

JDBC 抽象和數(shù)據(jù)存儲(chǔ)異常層次

數(shù)據(jù)訪問是Spring 的另一個(gè)閃光點(diǎn)。

JDBC 提供了還算不錯(cuò)的數(shù)據(jù)庫抽象,但是需要用痛苦的API。這些問題包括:



需要冗長(zhǎng)的錯(cuò)誤處理代碼來確保ResultSets,Statements以及(最重要的)Connections在使用后關(guān)閉。這意味著對(duì)JDBC的正確使用可以快速地導(dǎo)致大量的代碼量。它還是一個(gè)常見的錯(cuò)誤來源。Connection leak可以在有負(fù)載的情況下快速宕掉應(yīng)用程序。

SQLException相對(duì)來說不能說明任何問題。JDBC不提供異常的層次,而是用拋出SQLException來響應(yīng)所有的錯(cuò)誤。找出到底哪里出錯(cuò)了——例如,問題是死鎖還是無效的SQL?——要去檢查SQLState或錯(cuò)誤代碼。這意味著這些值在數(shù)據(jù)庫之間是變化的。

Spring用兩種方法解決這些問題:



提供API,把冗長(zhǎng)乏味和容易出錯(cuò)的異常處理從程序代碼移到框架之中??蚣芴幚硭械漠惓L幚?;程序代碼能夠集中精力于編寫恰當(dāng)?shù)腟QL和提取結(jié)果上。

為你本要處理SQLException程序代碼提供有意義的異常層次。當(dāng)Spring第一次從數(shù)據(jù)源取得一個(gè)連接時(shí),它檢查元數(shù)據(jù)以確定數(shù)據(jù)庫。它使用這些信息把SQLException映射為自己從org.springframework.dao.DataAccessException派生下來的類層次中正確的異常。因而你的代碼可以與有意義的異常打交道,并且不需要為私有的SQLState或者錯(cuò)誤碼擔(dān)心。Spring的數(shù)據(jù)訪問異常不是JDBC特有的,因而你的DAO并不一定會(huì)因?yàn)樗鼈兛赡軖伋龅漠惓6壦涝贘DBC上。

Spring提供兩層JDBC API。第一個(gè)時(shí),在org.springframework.jdbc.core包中,使用回調(diào)機(jī)制移動(dòng)控制權(quán)——并且因而把錯(cuò)誤處理和連接獲取和釋放——從程序的代碼移到了框架之中。這是一種不同的Inversion of Control,但是和用于配置管理的幾乎有同等重要的意義。

Spring使用類似的回調(diào)機(jī)制關(guān)注其他包含特殊獲取和清理資源步驟的API,例如JDO(獲取和釋放是由PersistenceManager完成的),事務(wù)管理(使用JTA)和JNDI。Spring中完成這些回調(diào)的類被稱作template。

例如,Spring的JdbcTemplate對(duì)象能夠用于執(zhí)行SQL查詢并且在如下的列表中保存結(jié)果:

代碼:
JdbcTemplate template = new JdbcTemplate(dataSource);
final List names = new LinkedList();
template.query("SELECT USER.NAME FROM USER",
   new RowCallbackHandler() {
      public void processRow(ResultSet rs) throws SQLException {
         names.add(rs.getString(1));
      }
   });


注意回調(diào)中的程序代碼是能夠自由拋出SQLException的:Spring將會(huì)捕捉到這些異常并且用自己的類層次重新拋出。程序的開發(fā)者可以選擇哪個(gè)異常,如果有的話,被捕捉然后處理。

JdbcTemplate提供許多支持不同情景包括prepared statements和批量更新的方法。Spring的JDBC抽象有比起標(biāo)準(zhǔn)JDBC來說性能損失非常小,甚至在當(dāng)應(yīng)用中需要的結(jié)果集數(shù)量很大的時(shí)候。

在org.springframework.jdbc.object包中是對(duì)JDBC的更高層次的抽象。這是建立在核心的JDBC回調(diào)功能基礎(chǔ)紙上的,但是提供了一個(gè)能夠?qū)DBMS操作——無論是查詢,更新或者是存儲(chǔ)過程——使用Java對(duì)象來建模的API。這個(gè)API部分是受到JDO查詢API的影響,我發(fā)現(xiàn)它直觀而且非常有用。

一個(gè)用于返回User對(duì)象的查詢對(duì)象可能是這樣的:

代碼:

class UserQuery extends MappingSqlQuery {

   public UserQuery(DataSource datasource) {
      super(datasource, "SELECT * FROM PUB_USER_ADDRESS WHERE USER_ID = ?");
      declareParameter(new SqlParameter(Types.NUMERIC));
      compile();
   }

   // Map a result set row to a Java object
   protected Object mapRow(ResultSet rs, int rownum) throws SQLException {
      User user = new User();
      user.setId(rs.getLong("USER_ID"));
      user.setForename(rs.getString("FORENAME"));
      return user;
   }

   public User findUser(long id) {
      // Use superclass convenience method to provide strong typing
      return (User) findObject(id);
   }
}


這個(gè)類可以在下面用上:
代碼:

User user = userQuery.findUser(25);


這樣的對(duì)象經(jīng)??梢杂米鱀AO的inner class。它們是線程安全的,除非子類作了一些超出常規(guī)的事情。

在org.springframework.jdbc.object包中另一個(gè)重要的類是StoredProcedure類。Spring讓存儲(chǔ)過程通過帶有一個(gè)業(yè)務(wù)方法的Java類進(jìn)行代理。如果你喜歡的話,你可以定義一個(gè)存儲(chǔ)過程實(shí)現(xiàn)的接口,意味著你能夠把你的程序代碼從對(duì)存儲(chǔ)過程的依賴中完全解脫出來。

Spring數(shù)據(jù)訪問異常層次是基于unchecked(運(yùn)行時(shí))exception的。在幾個(gè)工程中使用了Spring之后,我越來越確信這個(gè)決定是正確的。

數(shù)據(jù)訪問異常一般是不可恢復(fù)的。例如,如果我們不能鏈接到數(shù)據(jù)庫,某個(gè)業(yè)務(wù)對(duì)象很有可能就不能完成要解決的問題了。一個(gè)可能的異常是 optimistic locking violation,但是不是所有的程序使用optimistic locking。強(qiáng)制編寫捕捉其無法有效處理的致命的異常通常是不好的。讓它們傳播到上層的handler,比如servlet或者EJB 容器通常更加合適。所有的Spring對(duì)象訪問異常都是 DataAccessException的子類,因而如果我們確實(shí)選擇了捕捉所有的Spring數(shù)據(jù)訪問異常,我們可以很容易做到這點(diǎn)。

注意如果我們確實(shí)需要從unchecked數(shù)據(jù)訪問異常中恢復(fù),我們?nèi)匀豢梢赃@么做。我們可以編寫代碼僅僅處理可恢復(fù)的情況。例如,如果我們認(rèn)為只有optimistic locking violation是可恢復(fù)的,我們可以在Spring的DAO中如下這么寫:

代碼:

try {
   // do work
}
catch (OptimisticLockingFailureException ex) {
   // I'm interested in this
}


如果Spring的數(shù)據(jù)訪問異常是checked的,我們需要編寫如下的代碼。注意我們還是可以選擇這么寫:
代碼:

try {
   // do work
}
catch (OptimisticLockingFailureException ex) {
   // I'm interested in this
}
catch (DataAccessException ex) {
   // Fatal; just rethrow it
}


第一個(gè)例子的潛在缺陷是——編譯器不能強(qiáng)制處理可能的可恢復(fù)的異?!@對(duì)于第二個(gè)也是如此。因?yàn)槲覀儽粡?qiáng)制捕捉base exception (DataAccessException),編譯器不會(huì)強(qiáng)制對(duì)子類(OptimisticLockingFailureException)的檢查。因而編譯器可能強(qiáng)制我們編寫處理不可恢復(fù)問題的代碼,但是對(duì)于強(qiáng)制我們處理可恢復(fù)的問題并未有任何幫助。

Spring對(duì)于數(shù)據(jù)訪問異常的unchecked使用和許多——可能是大多數(shù)——成功的持久化框架是一致的。(確實(shí),它部分是受到JDO的影響。) JDBC是少數(shù)幾個(gè)使用checked exception的數(shù)據(jù)訪問API之一。例如TopLink和JDO大量使用 unchecked exception。Gavin King現(xiàn)在相信Hibernate也應(yīng)該選擇使用unchecked exception。

Spring的JDBC能夠用以下辦法幫助你:




你決不需要在使用JDBC時(shí)再編寫finally block。

總的來說你需要編寫的代碼更少了

你再也不需要挖掘你的RDBMS的文檔以找出它為錯(cuò)誤的列名稱返回的某個(gè)罕見的錯(cuò)誤代碼。你的程序不再依賴于RDBMS特有的錯(cuò)誤處理代碼。

無論使用的是什么持久化技術(shù),你都會(huì)發(fā)現(xiàn)容易實(shí)現(xiàn)DAO模式,讓業(yè)務(wù)代碼無需依賴于任何特定的數(shù)據(jù)訪問API。


在實(shí)踐中,我們發(fā)現(xiàn)所有這些都確實(shí)有助于生產(chǎn)力的提高和更少的bug。我過去常常厭惡編寫JDBC代碼;現(xiàn)在我發(fā)現(xiàn)我能夠集中精力于我要執(zhí)行的SQL,而不是煩雜的JDBC資源管理。

如果需要的話Spring的JDBC抽象可以獨(dú)立使用——不強(qiáng)迫你把它們用作Spring的一部分。
O/R mapping 集成

當(dāng)然你經(jīng)常需要使用O/R mapping,而不是使用關(guān)系數(shù)據(jù)訪問。你總體的應(yīng)用程序框架也必須支持它。因而提供了對(duì)Hibernate 2.x和 JDO的集成支持。它的數(shù)據(jù)訪問架構(gòu)使得它能和任何底層的數(shù)據(jù)訪問技術(shù)集成。Spring和Hibernate集成得尤其好。

為什么你要使用Hibernate加Spring,而不是直接使用Hibernate?




Session 管理 Spring提供有效率的,簡(jiǎn)單的以并且是安全的處理Hibernate Session。使用Hibernate的相關(guān)代碼為了效率和恰當(dāng)?shù)氖聞?wù)處理一般需要使用相同的Hibernate “Session”對(duì)象。Spring讓它容易透明地創(chuàng)建和綁定Session到當(dāng)前的線程,要么使用聲明式,AOP的method interceptor方法,要么在Java代碼層面使用顯式的,“template”包裝類。因而 Spring解決了在Hibernate論壇上經(jīng)常出現(xiàn)的用法問題。

資源管理 Spring的應(yīng)用程序context能夠處理Hiberante SessionFactories的位置和配置,JDBC數(shù)據(jù)源和其他相關(guān)資源。這使得這些值易于管理和改變。

集成的事務(wù)管理 Spring讓你能夠把你的Hibernate代碼包裝起來,要么使用聲明式,AOP風(fēng)格的method interceptor,要么在Java代碼層面顯式使用“template”包裝類。在兩種做法中,事務(wù)語義都為你處理了,并且在異常時(shí)也做好了恰當(dāng)?shù)氖聞?wù)處理(回滾,等)。如下面討論的,你還獲得了能夠使用和替換不同transaction manager,而不會(huì)讓你相關(guān)Hibernate代碼受到影響的能力。額外的,JDBC 相關(guān)的代碼能夠完全事務(wù)性的和Hibernate代碼集成。這對(duì)于處理沒有在Hibernate實(shí)現(xiàn)的功能很有用。

如上描述的異常包裝 Spring能夠包裝Hibernate異常,把它們從私有的,checked異常轉(zhuǎn)換為一套抽象的運(yùn)行時(shí)異常。這使得你能夠僅僅在恰當(dāng)?shù)膶用嫣幚泶蟛糠植豢苫謴?fù)的持久化異常,而不影響樣板catch/throw,和異常聲明。你仍然能夠在任何你需要的地方捕捉和處理異常。記住 JDBC異常(包括DB特有的方言)也被轉(zhuǎn)換到相同的層次中,意味著你能在一致的編程模型中對(duì)JDBC執(zhí)行相同的操作。

為了避免和廠商綁定 Hibernate是強(qiáng)大的,靈活的,開放源代碼并且免費(fèi),但是它仍然使用私有的API。給出了一些選擇,使用標(biāo)準(zhǔn)或者抽象API實(shí)現(xiàn)主要的程序功能通常是你想要的,當(dāng)你需要因?yàn)楣δ?,性能,或者其他考慮要轉(zhuǎn)換到使用其他實(shí)現(xiàn)時(shí)。

讓測(cè)試變簡(jiǎn)單 Spring的Inversion of Control方法使得改變Hibernate的session factories,數(shù)據(jù)源, transaction manager的實(shí)現(xiàn)和位置很容易,如果需要的話還能改變mapper object的實(shí)現(xiàn)。這使得更加容易分離和測(cè)試持久化相關(guān)的代碼。
事務(wù)管理
抽象出一個(gè)數(shù)據(jù)訪問的API是不夠的;我們還需要考慮事務(wù)管理。JTA是顯而易見的選擇,但是它是一個(gè)直接用起來很笨重的API,因而許多J2EE開發(fā)者感到EJB CMT是對(duì)于事務(wù)管理唯一合理的選擇。

Spring提供了它自己對(duì)事務(wù)管理的抽象。Spring提供了這些:



通過類似于JdbcTemplate的回調(diào)模板編程管理事務(wù),比起直接使用JTA要容易多了

類似于EJB CMT的聲明式事務(wù)管理,但是不需要EJB容器


Spring的事務(wù)抽象式唯一的,它不綁定到JTA或者任何其他事務(wù)管理技術(shù)。Spring使用事務(wù)策略的概念把程序代碼和底層的事務(wù)架構(gòu)(例如JDBC)解藕。

為什么你要關(guān)心這些?JTA不是所有事務(wù)管理的最好答案嗎?如果你正在編寫僅僅使用一個(gè)數(shù)據(jù)庫的程序,你不需要JTA的復(fù)雜度。你不關(guān)心XA事務(wù)或者兩階段提交。你甚至不需要提供這些東西的高端應(yīng)用服務(wù)器。但是另一方面,你不會(huì)希望在需要和多個(gè)數(shù)據(jù)源打交道的時(shí)候重寫你的代碼。

假定你決定通過直接使用JDBC或者Hibernate的事務(wù)以避免JTA帶來的額外負(fù)擔(dān)。一旦你需要處理多個(gè)數(shù)據(jù)源,你必須剝開所有的事務(wù)管理代碼并且使用JTA事務(wù)來替代。這不是非常有吸引力的并且導(dǎo)致大部分J2EE程序員,包括我自己,推薦只使用全局JTA事務(wù)。然而使用Spring事務(wù)抽象,你只需要重新配置Spring讓它使用JTA,而不是JDBC或者Hibernate的事務(wù)策略,就一切OK了。這是一個(gè)配置上的改變,而不是代碼的改動(dòng)。因而,Spring使得你能夠自由縮放應(yīng)用。
AOP

最近在應(yīng)用AOP來解決企業(yè)關(guān)注點(diǎn)方面大家有了很大的興趣,例如事務(wù)管理,這些都是EJB所要解決的。

Spring的AOP支持的首要目標(biāo)是要給POJOs提供J2EE服務(wù)。這類似于JBoss 4的目標(biāo),Spring AOP由它能夠在應(yīng)用服務(wù)器之間移植的優(yōu)勢(shì),因而沒有綁死在廠商身上的風(fēng)險(xiǎn)。它既可以在web或者EJB容器中使用,也能夠在WebLogic,Tomcat,JBoss,Resin, Jetty,Orion和許多其他應(yīng)用服務(wù)器和web容器上使用。

Spring AOP支持method interception。所支持關(guān)鍵的AOP概念包括:



Interception:自定義行為能夠在對(duì)接口和類的調(diào)用之前和之后插入。這類似于AspectJ術(shù)語中類似的“around advice”。

Introduction:指定advice會(huì)導(dǎo)致對(duì)象實(shí)現(xiàn)額外的接口。這混亂了繼承。

靜態(tài)和動(dòng)態(tài)的pointcuts:在interception發(fā)生的程序執(zhí)行處指定points。靜態(tài)pointcuts concern函數(shù)簽名;動(dòng)態(tài) pointcuts也可以在point被求值的地方考慮函數(shù)的參數(shù)。Pointcuts獨(dú)立interceptors單獨(dú)定義,使得標(biāo)準(zhǔn) interceptor可以應(yīng)用于不同應(yīng)用程序和代碼上下文。


Spring既支持有狀態(tài)(一個(gè)advised對(duì)象一個(gè)實(shí)例)也支持無狀態(tài)的interceptors(所有advice使用一個(gè)實(shí)例)。

Spring不支持field interception。這是一個(gè)經(jīng)過深思熟慮的設(shè)計(jì)決定。我總是感覺field interception違反了封裝。我比較傾向于把AOP作為補(bǔ)全物,而不是與OOP沖突的東西。如果在5年或者10年后,我們?cè)贏OP學(xué)習(xí)曲線上走得更遠(yuǎn)了并且覺得應(yīng)該在程序設(shè)計(jì)的桌面上給AOP一個(gè)位置,我不會(huì)驚訝的。(然而在那個(gè)時(shí)候基于語言的解決方案例如AspectJ可能比它們今天看來更加具有吸引力。)

Spring使用動(dòng)態(tài)代理實(shí)現(xiàn)AOP(其中存在一個(gè)接口)或者在運(yùn)行時(shí)使用CGLIB生成字節(jié)碼(這使得能夠代理類)。兩種方法都能夠在任何應(yīng)用服務(wù)器中使用。

Spring是第一個(gè)實(shí)現(xiàn)AOP Alliance interfaces的AOP 框架(www.sourceforge.net/projects/aopalliance)。這些是定義在不同AOP框架中能夠互操作interceptors的嘗試。

在TheServerSide和其他地方有一個(gè)正在進(jìn)行但是不是那么引人注目的爭(zhēng)論,就是這種interception是不是“true AOP”。我倒不怎么在意它叫什么;僅僅需要知道它是否在實(shí)踐中有用就好了。我也樂于稱它為“declarative middleware”(聲明式中間件)。把 Spring AOP認(rèn)做簡(jiǎn)單,輕量級(jí)的無狀態(tài)beans的替代物,這樣就不需要monolithic EJB容器了,而這些僅僅是讓你能夠構(gòu)建有你需要的服務(wù)的容器。我不推薦advising任何一個(gè)POJO,對(duì)local SLSBs的類比有助于你理解推薦的粒度。(然而,與EJB不同的是,在恰當(dāng)?shù)巧僖姷那闆r下,你可以自由地把Spring的AOP應(yīng)用到粒度更好的對(duì)象上。)

因?yàn)镾pring在實(shí)例上advises 對(duì)象,而不是在class loader層面上,使用有不同advice的同一個(gè)類的多個(gè)實(shí)例是可能的,或者與advised實(shí)例一道使用unadvised 實(shí)例。

可能Spring AOP最常見的應(yīng)用是聲明式事務(wù)管理。這是基于前面描述的TansactionTemplate抽象上的,并且可以給任何POJO提供聲明式事務(wù)管理。取決于事務(wù)策略,底層的機(jī)制可以是JTA,JDBC,Hibernate或者任何其他提供事務(wù)管理的API。

Spring的聲明式事務(wù)管理類似于EJB CMT,在以下方面有些不同:



事務(wù)管理能夠應(yīng)用于任何POJO。我們推薦業(yè)務(wù)對(duì)象實(shí)現(xiàn)接口,但是這只是一個(gè)好的編程習(xí)慣的問題,而不是由框架強(qiáng)制的。

通過使用Spring的事務(wù)API能夠在事務(wù)性POJO中實(shí)現(xiàn)編程回調(diào)。我們?yōu)榇颂峁╈o態(tài)的方法,使用ThreadLoacal變量,因而你不需要傳播諸如EJBContext這樣的context對(duì)象來確?;貪L。

你可以聲明式地定義“回滾規(guī)則”。EJB不會(huì)在未捕捉程序異常的時(shí)候自動(dòng)回滾(僅僅在unchecked exceptions和其他 Throwables的時(shí)候),應(yīng)用程序開發(fā)者經(jīng)常需要在任何異常發(fā)生時(shí)回滾。Spring事務(wù)管理讓你能夠聲明式地指定什么異常什么子類能夠?qū)е伦詣?dòng)回滾。缺省的行為和EJB是一致的,但是你能夠在checked和unchecked異常時(shí)自動(dòng)回滾。這個(gè)在最少化自編程回調(diào)代碼方面有很大好處,而回調(diào)依賴于Spring的事務(wù)API(因?yàn)镋JB的編程回調(diào)時(shí)在EJBContext中完成的)。

事務(wù)管理不綁定于JTA。如前面解釋過的,Spring的事務(wù)管理能夠在不同事務(wù)策略中使用。


當(dāng)然還可以使用Spring AOP實(shí)現(xiàn)程序特有的aspects。取決于你對(duì)AOP概念的接受程度,決定你是否選擇這么做,而不是Spring的能力,但是它確實(shí)非常有用。我們所見過的成功例子包括:



自定義的security interception,當(dāng)安全檢查的復(fù)雜度超出了J2EE安全架構(gòu)的能力的時(shí)候

在開發(fā)中使用的調(diào)試和profiling aspects

發(fā)送email通知管理員用戶不尋常的舉動(dòng)的Interceptors


程序自定的aspects能夠成為消除需要許多函數(shù)的樣板代碼的有利武器。

Spring AOP透明地與Spring BeanFactory概念集成。包含一個(gè)來自Spring BeanFactory對(duì)象地代碼不需要知道它是還是不是advised。和任何對(duì)象一樣,契約實(shí)在接口和對(duì)象實(shí)現(xiàn)中定義的。

下面的XML片斷展示了如何定義一個(gè)AOP代理:
代碼:

<bean id="myTest"
   class="org.springframework.aop.framework.ProxyFactoryBean">
   <property name="proxyInterfaces">
      <value>org.springframework.beans.ITestBean</value>
   </property>
   <property name="interceptorNames">
      <list>
         <value>txInterceptor</value>
         <value>target</value>
      </list>
   </property>
</bean>


注意bean類的定義總是AOP框架的ProxyFactoryBean,雖然bean的類型在引用中使用或者由BeanFactory的getBean ()方法返回時(shí)依賴的是代理接口。(多個(gè)代理方法是被支持的。)ProxyFactoryBean的“interceptorNames”屬性需要一個(gè)字符串列表。(因?yàn)槿绻硎且粋€(gè)“prototype”而不是singleton,有狀態(tài)interceptors可能需要?jiǎng)?chuàng)建新的實(shí)例,所以必須使用 Bean的名字而不是bean的引用。)列表中的名字可以是interceptor或者pointcuts(interceptors和有關(guān)它們合適被使用的信息)。列表中的“target”值自動(dòng)創(chuàng)建一個(gè)“invoker interceptor”封裝target對(duì)象。實(shí)現(xiàn)代理接口的是在 factory中的bean的名字。這個(gè)例子中的myTest可以和其他bean factory中的bean一樣使用。例如,其他對(duì)象可以使用< ref>元素引用它而且這些引用是由Spring IoC設(shè)置的。

還可以不用BeanFactory,編程構(gòu)建AOP代理,雖然這個(gè)很少用得上:

代碼:

TestBean target = new TestBean();
DebugInterceptor di = new DebugInterceptor();
MyInterceptor mi = new MyInterceptor();
ProxyFactory factory = new ProxyFactory(target);
factory.addInterceptor(0, di);
factory.addInterceptor(1, mi);
// An "invoker interceptor" is automatically added to wrap the target
ITestBean tb = (ITestBean) factory.getProxy();


我們相信最好把程序裝配從Java代碼中移出來,而AOP也不例外。

Spring在它的AOP能力方面的直接競(jìng)爭(zhēng)者是Jon Tirsen的Nanning Aspects(http://nanning.codehaus.org)。

我覺得AOP作為EJB的替代無提供企業(yè)服務(wù)這個(gè)用法方面的進(jìn)步是重要的。隨著時(shí)間,這將成為Spring很重要的關(guān)注點(diǎn)。
MVC web 框架

Spring包括一個(gè)強(qiáng)大而且高度可配置的MVC web 框架。

Spring的MVC model類似于Struts。在多線程服務(wù)對(duì)象這點(diǎn)上,Spring的Controller類似于Struts Action,只有一個(gè)實(shí)例處理所有客戶的請(qǐng)求。然而,我們相信Spring的MVC比起Struts有很多優(yōu)點(diǎn),例如:



Spring在controllers,JavaBean,models和views提供了一個(gè)非常清晰的劃分。

Spring的MVC是非常靈活的。不像Struts,它強(qiáng)制你的Action和Form對(duì)象進(jìn)入固化的層次之中(因而你迫使你使用Java的實(shí)體繼承),Spring MVC完全是基于接口的。而且,通過插入你自己的接口幾乎Spring MVC 框架的所有部分都是可配置的。當(dāng)然我們也提供了方便的類作為實(shí)現(xiàn)選擇。

Spring MVC是真正的view無關(guān)的。你不會(huì)被強(qiáng)制使用JSP,如果你不想那么做的話。你可以使用Velocity,XSLT或其他view技術(shù)。如果你想要使用自定義的view機(jī)制——例如,你自己的模板語言——你可以簡(jiǎn)單實(shí)現(xiàn)Spring的View接口并且把它集成進(jìn)來。

和其他對(duì)象一樣,Spring的Controllers是通過IoC配置的。著使得它們易于測(cè)試,并且完美地和其他由Spring管理的對(duì)象集成。

Web層變成了業(yè)務(wù)對(duì)象層之上的薄薄一層。這鼓勵(lì)了好的習(xí)慣。Struts和其他專門的web框架讓你去實(shí)現(xiàn)你自己的業(yè)務(wù)對(duì)象;Spring提供了你應(yīng)用程序所有層的集成。


如在Struts 1.1中所見的,你可以有和你在Spring MVC 應(yīng)用程序中所需要的一樣多的dispatcher servlets。

下面的例子展示了一個(gè)簡(jiǎn)單的Spring Controller如何能夠訪問定義在應(yīng)用程序context中的業(yè)務(wù)對(duì)象。這個(gè)controller在它的handleRequest()方法中執(zhí)行了Google搜索:

代碼:

public class GoogleSearchController
      implements Controller {

   private IGoogleSearchPort google;

   private String googleKey;

   public void setGoogle(IGoogleSearchPort google) {
      this.google = google;
   }

   public void setGoogleKey(String googleKey) {
      this.googleKey = googleKey;
   }

   public ModelAndView handleRequest(
            HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
      String query = request.getParameter("query");
      GoogleSearchResult result =
         // Google property definitions omitted...

         // Use google business object
         google.doGoogleSearch(this.googleKey, query,
            start, maxResults, filter, restrict,
            safeSearch, lr, ie, oe);

      return new ModelAndView("googleResults", "result", result);
   }
}


這段代碼使用的prototype中,IGoogleSearchPort是一個(gè)GLUE web services代理,由 Spring FActoryBean返回。然而,Spring把controller從底層web service庫中分離出來。接口可以使用普通的 Java對(duì)象,test stub,mock對(duì)象或者如下面要討論的EJB代理實(shí)現(xiàn)。這個(gè)contorller不包括資源查找;除了支持它的web交互的必要代碼之外沒有別的什么了。

Spring還提供了數(shù)據(jù)綁定,forms,wizards和更復(fù)雜的工作流的支持。

對(duì)Spring MVC 框架的優(yōu)秀簡(jiǎn)介是Thomas Risberg的Spring MVC 教程(http://www.springframework.org/docs/MVC-step-by-step/Spring-MVC-step-by-step.html)。還可以參見“Web MVC with the Spring Framework”(http://www.springframework.org/docs/web_mvc.html)。

如果你樂于使用你鐘情的MVC框架,Spring的分層架構(gòu)使得你能夠使用Spring的其他部分而不用MVC層。我們有使用Spring做中間層管理和數(shù)據(jù)訪問,但是在web層使用Struts,WebWork或者Tapestry的用戶。
實(shí)現(xiàn)EJB

如果你選擇使用EJB,Spring能在EJB實(shí)現(xiàn)和客戶端訪問EJB兩方面都提供很大的好處。

對(duì)業(yè)務(wù)邏輯進(jìn)行重構(gòu),把它從EJB facades中取出到POJO已經(jīng)得到了廣泛的認(rèn)同。(不講別的,這使得業(yè)務(wù)邏輯更容易單元測(cè)試,因?yàn)镋JB嚴(yán)重依賴于容器而難于分離測(cè)試。)Spring為session bean和message driver bean提供了方便的超類,使得通過自動(dòng)載入基于包含在EJB Jar文件中的XML文檔BeanFactory讓這變得很容易。

這意味著stateless session EJB可以這么獲得和使用所需對(duì)象:

代碼:

import org.springframework.ejb.support.AbstractStatelessSessionBean;

public class MyEJB extends AbstractStatelessSessionBean
         implements MyBusinessInterface {
   private MyPOJO myPOJO;

   protected void onEjbCreate() {
      this.myPOJO = getBeanFactory().getBean("myPOJO");
   }

   public void myBusinessMethod() {
      this.myPOJO.invokeMethod();
   }
}


假定MyPOJO是一個(gè)接口,它的實(shí)現(xiàn)類——以及任何它需要的配置,注入基本的屬性和更多的合作者——在XML bean factory 定義中隱藏。

我們通過在ejb-jar.xmldeployment descriptor中名為ejb/BeanFactoryPath的環(huán)境變量定義告訴Spring去哪兒裝載XML文檔。如下:

代碼:

<session>
   <ejb-name>myComponent</ejb-name>
   <local-home>com.test.ejb.myEjbBeanLocalHome</local-home>
   <local>com.mycom.MyComponentLocal</local>
   <ejb-class>com.mycom.MyComponentEJB</ejb-class>
   <session-type>Stateless</session-type>
   <transaction-type>Container</transaction-type>

   <env-entry>
      <env-entry-name>ejb/BeanFactoryPath</env-entry-name>
      <env-entry-type>java.lang.String</env-entry-type>
      <env-entry-value>/myComponent-ejb-beans.xml</env-entry-value></env-entry>
   </env-entry>
</session>


myComponent-ejb-beans.xml 文件將會(huì)從classpath裝載:在本例中,是EJB Jar文件的根目錄。每個(gè)EJB都能指定自己的XML文檔,因而這個(gè)機(jī)制能在每個(gè)EJB Jar文件中使用多次。

Spring 的超類實(shí)現(xiàn)了EJB中諸如setSessionContext()和ejbCreate()的生命周期管理的方法,讓應(yīng)用程序開發(fā)者只需選擇是否實(shí)現(xiàn)Spring的onEjbCreate()方法。
使用EJB

Spring還讓實(shí)現(xiàn)EJB變得更加容易。許多EJB程序使用Service Locator和Business Delegate模式。這些比在客戶代碼中遍布JNDI查找強(qiáng)多了,但是它們常見的實(shí)現(xiàn)方式有顯著的缺點(diǎn),例如:



使用EJB的典型代碼依賴Service Locator或者Business Delegate singletons,使得測(cè)試難于進(jìn)行。

在Service Locator模式?jīng)]有使用Business Delegate的情況下,程序代碼還要在EJB home中調(diào)用create()方法,并且處理可能導(dǎo)致的異常。因而仍然綁定在EJB API身上,忍受著EJB 編程模型的復(fù)雜度。

實(shí)現(xiàn)Business Delegate模式通常導(dǎo)致顯著的代碼重復(fù),其中我們必須編寫大量?jī)H僅是調(diào)用EJB同等方法的方法。


基于這些和其他原因,傳統(tǒng)的EJB訪問,如在Sun Adventure Builder和OTN J2EE Virtual Shopping Mall中展示的那樣,會(huì)降低生產(chǎn)率并且?guī)盹@著的復(fù)雜度。

Spring通過引入codeless business delegate前進(jìn)了一步。有了Spring,你不再需要再編寫另一個(gè) Service Locator,另一個(gè)JNDI查找,或者在硬編碼的Business Delegate中重復(fù)代碼,除非你肯定這增加了價(jià)值。

例如,假定我們有使用local EJB的web controller。我們將遵循最佳實(shí)踐,使用 EJB Business Methods Interface模式,EJB的local interface extend非EJB專有的業(yè)務(wù)方法接口。(這么做的主要的一個(gè)原因是確保在本地接口和bean實(shí)現(xiàn)類中方法簽名的自動(dòng)同步。)讓我們調(diào)用這個(gè)業(yè)務(wù)方法接口MyComponent。當(dāng)然我們還需要實(shí)現(xiàn)local home接口并且提供實(shí)現(xiàn)SessionBean和MyComponent業(yè)務(wù)方法的bean的實(shí)現(xiàn)類。

用了Spring EJB 訪問,我們把我們的web層controller和EJB實(shí)現(xiàn)掛接上所需要進(jìn)行的Java編碼僅僅是在我們的controller中暴露一個(gè)類型MyComponent的setter方法。這將如下保存作為實(shí)例變量的引用:

代碼:

private MyComponent myComponent;

public void setMyComponent(MyComponent myComponent) {
   this.myComponent = myComponent;
}


我們隨后在任何業(yè)務(wù)方法中使用這個(gè)實(shí)例變量。

Spring自動(dòng)完稱剩下的工作,通過像這樣的XML bean定義。LocalStatelessSessionProxyFactoryBean是一個(gè)可以用于任何EJB的通用factory bean。它創(chuàng)建的對(duì)象能夠自動(dòng)被Spring轉(zhuǎn)型為MyComponent類型。

代碼:

<bean id="myComponent"
class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean">

   <property name="jndiName"><value>myComponent</value></property>
   <property name="businessInterface"><value>com.mycom.MyComponent</value></property>
</bean>

<bean id="myController"
   class = "com.mycom.myController"
>
   <property name="myComponent"><ref bean="myComponent"/></property>
</bean>


在幕后有許多魔法般的事情發(fā)生,Spring AOP framework的殷勤,雖然不強(qiáng)迫你使用AOP的概念享受這些結(jié)果。 “myComponent”bean定義為EJB創(chuàng)建一個(gè)代理,它實(shí)現(xiàn)了業(yè)務(wù)方法的接口。EJB local home在啟動(dòng)的時(shí)候被緩存,因而只需要一次JNDI查找。每次EJB被調(diào)用的時(shí)候,代理調(diào)用local EJB中的create()方法并且調(diào)用EJB中對(duì)應(yīng)的業(yè)務(wù)方法。

myController bean定義為這個(gè)代理設(shè)置controller類的myController屬性。

這個(gè)EJB訪問機(jī)制極大簡(jiǎn)化了應(yīng)用程序的代碼:



Web層的代碼不依賴于EJB的使用。如果你想要使用POJO,mock object或者其他test stub替代EJB引用,我們可以簡(jiǎn)單地改動(dòng)一下myComponent bean定義而不影響一行Java代碼

我們還不需要寫一行JNDI查找或者其他EJB plumbing code。


在實(shí)際程序中的性能測(cè)試和經(jīng)驗(yàn)標(biāo)明這種方法(包括對(duì)目標(biāo)EJB的反射調(diào)用)的性能影響是很小的,在典型的應(yīng)用中檢測(cè)不出。記住無論如何我們都不希望使用fine-grained的EJB調(diào)用,因?yàn)闀?huì)有有關(guān)應(yīng)用服務(wù)器上的EJB的底層架構(gòu)方面的代價(jià)。

我們可以把相同方法應(yīng)用于遠(yuǎn)程EJB,通過類似 org.springframework.ejb.access.SimpleRemoteStatelessSessionProxyFactoryBean factory bean 的方法。然而我們無法隱藏遠(yuǎn)程EJB的業(yè)務(wù)方法接口中的RemoteException。
測(cè)試

如你可能已經(jīng)注意到的,我和其他Spring開發(fā)這是全面單元測(cè)試重要性的堅(jiān)定支持者。我們相信框架被徹底單元測(cè)試過的是非常重要的,而且我們框架設(shè)計(jì)的主要目標(biāo)是讓建立在框架之上的程序易于單元測(cè)試。

Spring自身有一個(gè)極好的單元測(cè)試包。我們的1.0 M1的單元測(cè)試覆蓋率是75%,而且我們希望在1.0 RC1的時(shí)候能夠達(dá)到80%的單元測(cè)試覆蓋率。我們發(fā)現(xiàn)在這個(gè)項(xiàng)目中測(cè)試優(yōu)先的開發(fā)帶來的好處是實(shí)實(shí)在在的。例如,它使得作為國際化分布式開發(fā)團(tuán)隊(duì)的工作極端有效率,而且用戶評(píng)論 CVS snapshots趨向于穩(wěn)定和使用安全。

因?yàn)橐韵吕碛?,我們相信用Spring構(gòu)建的應(yīng)用程序是非常易于測(cè)試的:



IoC推動(dòng)了單元測(cè)試

應(yīng)用程序不包括直接使用注入JNDI的J2EE服務(wù)的plumbing code,這些代碼一般讓測(cè)試難于進(jìn)行

Spring bean factories和contexts能夠在容器外設(shè)置


在容器外可以設(shè)置Spring bean factory的能力提供了對(duì)開發(fā)過程有趣的可選項(xiàng)。在幾個(gè)使用Spring的web應(yīng)用中,工作是從定義業(yè)務(wù)接口和在web容器外集成測(cè)試開始的。在業(yè)務(wù)功能已經(jīng)足夠完整之后,web接口不過是添加在其上的薄薄一層。
誰在使用Spring

雖然相對(duì)來說Spring還是一個(gè)新的項(xiàng)目,但是我們已經(jīng)有了一個(gè)令人印象深刻并且不斷增長(zhǎng)的用戶群。它們已經(jīng)有許多產(chǎn)品使用著Spring。用戶包括一個(gè)主要的全球投資銀行(做大型項(xiàng)目的),一些知名的網(wǎng)絡(luò)公司,幾個(gè)web開發(fā)顧問機(jī)構(gòu),衛(wèi)生保健公司,以及學(xué)院機(jī)構(gòu)。

許多用戶完整地使用Spring,但是一些只單獨(dú)使用一些組件。例如,大量用戶使用我們地JDBC或者其他數(shù)據(jù)訪問功能。
Roadmap

在今年晚些時(shí)候我們主要要做的是讓Spring發(fā)布release 1.0。然而,我們還有一些更長(zhǎng)遠(yuǎn)的目標(biāo)。

為1.0 final規(guī)劃地主要改進(jìn)式源代碼級(jí)地元數(shù)據(jù)支持,它主要用于(但不局限于)AOP框架。這將使得C#風(fēng)格的attribute驅(qū)動(dòng)的事務(wù)管理,并且讓聲明式企業(yè)服務(wù)在典型應(yīng)用情況下非常容易配置。Attribute支持將會(huì)在Spring的1.0 final release支持中加入,并且設(shè)計(jì)的是在發(fā)布的那個(gè)時(shí)候能與JSR-175集成。

1.0之后,一些可能的改進(jìn)地方包括:



通過對(duì)我們的JDBC和事務(wù)支持的一個(gè)相當(dāng)抽象來支持JMS

支持bean factories的動(dòng)態(tài)重配置

提供web services的能力

IDE和其他工具支持


作為一個(gè)敏捷項(xiàng)目,我們主要是受到用戶需求的驅(qū)動(dòng)。因而我們不會(huì)開發(fā)沒有一個(gè)用戶需要的特性,并且我們會(huì)仔細(xì)傾聽來自用戶群的聲音。
總結(jié)

Spring是一個(gè)解決了許多在J2EE開發(fā)中常見的問題的強(qiáng)大框架。

Spring提供了管理業(yè)務(wù)對(duì)象的一致方法并且鼓勵(lì)了注入對(duì)接口編程而不是對(duì)類編程的良好習(xí)慣。Spring的架構(gòu)基礎(chǔ)是基于使用JavaBean屬性的 Inversion of Control容器。然而,這僅僅是完整圖景中的一部分:Spring在使用IoC容器作為構(gòu)建完關(guān)注所有架構(gòu)層的完整解決方案方面是獨(dú)一無二的。

Spring提供了唯一的數(shù)據(jù)訪問抽象,包括簡(jiǎn)單和有效率的JDBC框架,極大的改進(jìn)了效率并且減少了可能的錯(cuò)誤。Spring的數(shù)據(jù)訪問架構(gòu)還集成了Hibernate和其他O/R mapping解決方案。

Spring還提供了唯一的事務(wù)管理抽象,它能夠在各種底層事務(wù)管理技術(shù),例如JTA或者JDBC紙上提供一個(gè)一致的編程模型。

Spring提供了一個(gè)用標(biāo)準(zhǔn)Java語言編寫的AOP框架,它給POJOs提供了聲明式的事務(wù)管理和其他企業(yè)事務(wù)——如果你需要——還能實(shí)現(xiàn)你自己的aspects。這個(gè)框架足夠強(qiáng)大,使得應(yīng)用程序能夠拋開EJB的復(fù)雜性,同時(shí)享受著和傳統(tǒng)EJB相關(guān)的關(guān)鍵服務(wù)。

Spring還提供了可以和總體的IoC容器集成的強(qiáng)大而靈活的MVC web框架。
更多信息

參見以下資源獲得關(guān)于Spring的更多信息:



Expert One-on-One J2EE Design and Development(Rod Johnson,Wrox,2002)。雖然Spring在書出版之后已經(jīng)極大地進(jìn)步和改進(jìn)了,它仍然是理解Spring動(dòng)機(jī)的極佳途徑。

Spring的主頁:http://www.springframework.org。這里包括Javadoc和幾個(gè)教程。

在Sourceforge上的論壇和下載

Spring用戶和Spring開發(fā)者的郵件列表


我們正在盡我們可能去改進(jìn)Spring的文檔和示例。我們還為在信件和郵件列表中極好的回復(fù)率自豪。我們希望你能快速融入我們的社區(qū)!
關(guān)于作者

Rod Johnson 作為Java開發(fā)者和架構(gòu)師已經(jīng)有了7年的經(jīng)驗(yàn)了并且在J2EE平臺(tái)出現(xiàn)之初就在其上進(jìn)行開發(fā)了。他是《Expert One- on-One J2EE Design and Development》(Wrox,2002)的作者并且貢獻(xiàn)了其他好幾本關(guān)于J2EE的書。他當(dāng)前正在為Wiley撰寫另外一本有關(guān)J2EE架構(gòu)的書。Rod在兩個(gè)Java標(biāo)準(zhǔn)委員會(huì)服務(wù)并且經(jīng)常師大會(huì)發(fā)言人?,F(xiàn)在他在UK做一個(gè)咨詢顧問。

]]>
spring 容器 內(nèi)部工作機(jī)制(轉(zhuǎn))http://www.aygfsteel.com/lanxin1020/archive/2009/04/07/264360.htmllanxin1020lanxin1020Tue, 07 Apr 2009 15:06:00 GMThttp://www.aygfsteel.com/lanxin1020/archive/2009/04/07/264360.htmlhttp://www.aygfsteel.com/lanxin1020/comments/264360.htmlhttp://www.aygfsteel.com/lanxin1020/archive/2009/04/07/264360.html#Feedback0http://www.aygfsteel.com/lanxin1020/comments/commentRss/264360.htmlhttp://www.aygfsteel.com/lanxin1020/services/trackbacks/264360.html
1.初始化BeanFactory:根據(jù)配置文件實(shí)例化BeanFactory,getBeanFactory()方法由具體子類實(shí)現(xiàn)。在這一步里,Spring將配置文件的信息裝入到容器的Bean定義注冊(cè)表(BeanDefinitionRegistry)中,但此時(shí)Bean還未初始化;
2.調(diào)用工廠后處理器:根據(jù)反射機(jī)制從BeanDefinitionRegistry中找出所有BeanFactoryPostProcessor類型的Bean,并調(diào)用其postProcessBeanFactory()接口方法;
3.注冊(cè)Bean后處理器:根據(jù)反射機(jī)制從BeanDefinitionRegistry中找出所有BeanPostProcessor類型的Bean,并將它們注冊(cè)到容器Bean后處理器的注冊(cè)表中;
4.初始化消息源:初始化容器的國際化信息資源;
5.初始化應(yīng)用上下文事件廣播器;
6.初始化其他特殊的Bean:這是一個(gè)鉤子方法,子類可以借助這個(gè)鉤子方法執(zhí)行一些特殊的操作:如AbstractRefreshableWebApplicationContext就使用該鉤子方法執(zhí)行初始化ThemeSource的操作;
7.注冊(cè)事件監(jiān)聽器;
8.初始化singleton的Bean:實(shí)例化所有singleton的Bean,并將它們放入Spring容器的緩存中;
9.發(fā)布上下文刷新事件:創(chuàng)建上下文刷新事件,事件廣播器負(fù)責(zé)將些事件廣播到每個(gè)注冊(cè)的事件監(jiān)聽器中。
在第3.4節(jié)中,我們觀摩了Bean從創(chuàng)建到銷毀的生命歷程,這些過程都可以在上面的過程中找到對(duì)應(yīng)的步驟。Spring協(xié)調(diào)多個(gè)組件共同完成這個(gè)復(fù)雜的工程流程,圖5-1描述了Spring容器從加載配置文件到創(chuàng)建出一個(gè)完整Bean的作業(yè)流程以及參與的角色。
圖5-1  IoC的流水線
1.ResourceLoader從存儲(chǔ)介質(zhì)中加載Spring配置文件,并使用Resource表示這個(gè)配置文件的資源;
2.BeanDefinitionReader讀取Resource所指向的配置文件資源,然后解析配置文件。配置文件中每一個(gè)<bean>解析成一個(gè)BeanDefinition對(duì)象,并保存到BeanDefinitionRegistry中;
3.容器掃描BeanDefinitionRegistry中的BeanDefinition,使用Java的反射機(jī)制自動(dòng)識(shí)別出Bean工廠后處理器(實(shí)現(xiàn)BeanFactoryPostProcessor接口)的Bean,然后調(diào)用這些Bean工廠后處理器對(duì)BeanDefinitionRegistry中的BeanDefinition進(jìn)行加工處理。主要完成以下兩項(xiàng)工作:
1)對(duì)使用到占位符的<bean>元素標(biāo)簽進(jìn)行解析,得到最終的配置值,這意味對(duì)一些半成品式的BeanDefinition對(duì)象進(jìn)行加工處理并得到成品的BeanDefinition對(duì)象;
2)對(duì)BeanDefinitionRegistry中的BeanDefinition進(jìn)行掃描,通過Java反射機(jī)制找出所有屬性編輯器的Bean(實(shí)現(xiàn)java.beans.PropertyEditor接口的Bean),并自動(dòng)將它們注冊(cè)到Spring容器的屬性編輯器注冊(cè)表中(PropertyEditorRegistry);
4.Spring容器從BeanDefinitionRegistry中取出加工后的BeanDefinition,并調(diào)用InstantiationStrategy著手進(jìn)行Bean實(shí)例化的工作;
5.在實(shí)例化Bean時(shí),Spring容器使用BeanWrapper對(duì)Bean進(jìn)行封裝,BeanWrapper提供了很多以Java反射機(jī)制操作Bean的方法,它將結(jié)合該Bean的BeanDefinition以及容器中屬性編輯器,完成Bean屬性的設(shè)置工作;
6.利用容器中注冊(cè)的Bean后處理器(實(shí)現(xiàn)BeanPostProcessor接口的Bean)對(duì)已經(jīng)完成屬性設(shè)置工作的Bean進(jìn)行后續(xù)加工,直接裝配出一個(gè)準(zhǔn)備就緒的Bean。
Spring容器確實(shí)堪稱一部設(shè)計(jì)精密的機(jī)器,其內(nèi)部擁有眾多的組件和裝置。Spring的高明之處在于,它使用眾多接口描繪出了所有裝置的藍(lán)圖,構(gòu)建好Spring的骨架,繼而通過繼承體系層層推演,不斷豐富,最終讓Spring成為有血有肉的完整的框架。所以查看Spring框架的源碼時(shí),有兩條清晰可見的脈絡(luò):
1)接口層描述了容器的重要組件及組件間的協(xié)作關(guān)系;
2)繼承體系逐步實(shí)現(xiàn)組件的各項(xiàng)功能。
接口層清晰地勾勒出Spring框架的高層功能,框架脈絡(luò)呼之欲出。有了接口層抽象的描述后,不但Spring自己可以提供具體的實(shí)現(xiàn),任何第三方組織也可以提供不同實(shí)現(xiàn), 可以說Spring完善的接口層使框架的擴(kuò)展性得到了很好的保證??v向繼承體系的逐步擴(kuò)展,分步驟地實(shí)現(xiàn)框架的功能,這種實(shí)現(xiàn)方案保證了框架功能不會(huì)堆積在某些類的身上,造成過重的代碼邏輯負(fù)載,框架的復(fù)雜度被完美地分解開了。
Spring組件按其所承擔(dān)的角色可以劃分為兩類:
1)物料組件:Resource、BeanDefinition、PropertyEditor以及最終的Bean等,它們是加工流程中被加工、被消費(fèi)的組件,就像流水線上被加工的物料;
2)加工設(shè)備組件:ResourceLoader、BeanDefinitionReader、BeanFactoryPostProcessor、InstantiationStrategy以及BeanWrapper等組件像是流水線上不同環(huán)節(jié)的加工設(shè)備,對(duì)物料組件進(jìn)行加工處理。
我們?cè)诘?章中已經(jīng)介紹了Resource和ResourceLoader這兩個(gè)組件。在本章中,我們將對(duì)其他的組件進(jìn)行講解。


]]>
利用Java的反射與代理實(shí)現(xiàn)AOP(轉(zhuǎn))http://www.aygfsteel.com/lanxin1020/archive/2009/04/06/264075.htmllanxin1020lanxin1020Mon, 06 Apr 2009 03:50:00 GMThttp://www.aygfsteel.com/lanxin1020/archive/2009/04/06/264075.htmlhttp://www.aygfsteel.com/lanxin1020/comments/264075.htmlhttp://www.aygfsteel.com/lanxin1020/archive/2009/04/06/264075.html#Feedback0http://www.aygfsteel.com/lanxin1020/comments/commentRss/264075.htmlhttp://www.aygfsteel.com/lanxin1020/services/trackbacks/264075.html利用Java的反射與代理實(shí)現(xiàn)AOP

 

一.AOP概述
       AOP(Aspect Oriented Programing),即面向切面編程,它主要用于日志記錄、性能統(tǒng)計(jì)、控制、事務(wù)處理、異常處理等方面。它的主要意圖就要將日志記錄,性能統(tǒng)計(jì),安全控制、事務(wù)處理、異常處理等等代碼從業(yè)務(wù)邏輯代碼中清楚地劃分出來。通過對(duì)這些行為的分離,我們希望可以將它們獨(dú)立地配置到業(yè)務(wù)邏輯方法中,而要改變這些行為的時(shí)候也不需要影響到業(yè)務(wù)邏輯方法代碼。
       下面讓我們來看一個(gè)利用AOP來實(shí)現(xiàn)日志記錄的例子,在沒有使用AOP之前,我們的代碼如下面所講述。
       下面這段代碼為業(yè)務(wù)的接口類代碼:

package org.amigo.proxy; /** * 業(yè)務(wù)邏輯類接口. * @author <a href="mailto:xiexingxing1121@126.com">AmigoXie</a> * Creation date: 2007-10-7 - 上午09:09:53 */ public interface BusinessObj { /** * 執(zhí)行業(yè)務(wù). */ public void process(); } BusinessObj接口的某個(gè)實(shí)現(xiàn)類代碼如下: package org.amigo.proxy; /** * 業(yè)務(wù)邏輯對(duì)象實(shí)現(xiàn)類. * @author <a href="mailto:xiexingxing1121@126.com">AmigoXie</a> * Creation date: 2007-10-7 - 上午09:11:49 */ public class BusinessObjImpl implements BusinessObj { /** * 執(zhí)行業(yè)務(wù). */ public void process() { try { System.out.println("before process"); System.out.println("執(zhí)行業(yè)務(wù)邏輯"); System.out.println("after process"); } catch (Exception e) { System.err.println("發(fā)生異常:" + e.toString()); } } }
    在上例中我們可以看到,在執(zhí)行業(yè)務(wù)方法前、執(zhí)行業(yè)務(wù)方法后以及異常發(fā)生時(shí)的日志記錄,我們都是通過在對(duì)應(yīng)的類中寫入記錄日志的代碼來實(shí)現(xiàn)的,當(dāng)有這種日志記錄需求的業(yè)務(wù)邏輯類不斷增多時(shí),將會(huì)給我們的維護(hù)帶來很大困難,而且,在上面的例子中,日志代碼和業(yè)務(wù)邏輯代碼混合在一起,為日后的維護(hù)工作又抹上了一層“恐怖”色彩。
       按照AOP的思想,我們首先需要尋找一個(gè)切面,在這里我們已經(jīng)找到,即在業(yè)務(wù)邏輯執(zhí)行前后以及異常發(fā)生時(shí),進(jìn)行相應(yīng)的日志記錄。我們需要將這部分日志代碼放入一個(gè)單獨(dú)的類中,以便為以后的修改提供方便。
       我們?cè)诮孬@某個(gè)業(yè)務(wù)邏輯方法時(shí),可以采用Java的動(dòng)態(tài)代理機(jī)制來實(shí)現(xiàn)。
 
 
二.Java的動(dòng)態(tài)代理機(jī)制
代理模式是常用的Java設(shè)計(jì)模式。代理類主要負(fù)責(zé)為委托類預(yù)處理消息、過濾信息、把消息轉(zhuǎn)發(fā)給委托類,以及事后處理信息等。
動(dòng)態(tài)代理類不僅簡(jiǎn)化了編程工作,而且提高了軟件系統(tǒng)的擴(kuò)展性和可維護(hù)性。我們可以通過實(shí)現(xiàn)java.lang.reflect.InvocationHandler接口提供一個(gè)執(zhí)行處理器,然后通過java.lang.reflect.Proxy得到一個(gè)代理對(duì)象,通過這個(gè)代理對(duì)象來執(zhí)行業(yè)務(wù)邏輯方法,在業(yè)務(wù)邏輯方法被調(diào)用的同時(shí),自動(dòng)調(diào)用會(huì)執(zhí)行處理器。
      
下面讓我們來創(chuàng)建一個(gè)日志攔截器類LogInterceptor.java文件,該類實(shí)現(xiàn)java.lang.reflect.InvocationHandler接口,其內(nèi)容如下所示:

package org.amigo.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 日志攔截器,用來進(jìn)行日志處理. * @author <a href="mailto:xiexingxing1121@126.com">AmigoXie</a> * Creation date: 2007-10-7 - 上午09:31:44 */ public class LogInterceptor implements InvocationHandler { private Object delegate; /** * 構(gòu)造函數(shù),設(shè)置代理對(duì)象. */ public LogInterceptor(Object delegate){ this.delegate = delegate; } /** * 方法的調(diào)用. * @param proxy * @param method 對(duì)應(yīng)的方法 * @param args 方法的參信息 * @return 返回操作結(jié)果對(duì)象 * @throws Throwable */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; try { System.out.println("before process" + method); //調(diào)用代理對(duì)象delegate的method方法,并將args作為參數(shù)信息傳入 result = method.invoke(delegate, args); System.out.println("after process" + method); } catch (Exception e){ System.err.println("發(fā)生異常:" + e.toString()); } return result; } /** * 測(cè)試方法. * @param args * @throws Exception */ public static void main(String[] args) throws Exception { BusinessObj obj = new BusinessObjImpl(); //創(chuàng)建一個(gè)日志攔截器 LogInterceptor interceptor = new LogInterceptor(obj); //通過Proxy類的newProxyInstance(...)方法來獲得動(dòng)態(tài)的代理對(duì)象 BusinessObj proxy = (BusinessObj) Proxy.newProxyInstance( BusinessObjImpl.class.getClassLoader(), BusinessObjImpl.class.getInterfaces(), interceptor); //執(zhí)行動(dòng)態(tài)代理對(duì)象的業(yè)務(wù)邏輯方法 proxy.process(); } }
 
此時(shí)還需要對(duì)BusinessObj的實(shí)現(xiàn)類BusinessObjImpl類進(jìn)行修改,去掉其日志記錄等內(nèi)容,修改后的文件如下:

package org.amigo.proxy; /** * 業(yè)務(wù)邏輯對(duì)象實(shí)現(xiàn)類. * @author <a href="mailto:xiexingxing1121@126.com">AmigoXie</a> * Creation date: 2007-10-7 - 上午09:11:49 */ public class BusinessObjImpl implements BusinessObj { /** * 執(zhí)行業(yè)務(wù). */ public void process() { System.out.println("執(zhí)行業(yè)務(wù)邏輯"); } }
運(yùn)行LogInterceptor類我們可以發(fā)現(xiàn),它實(shí)現(xiàn)了前面所需要的功能,但是很好的將業(yè)務(wù)邏輯方法的代碼和日志記錄的代碼分離開來,并且所有的業(yè)務(wù)處理對(duì)象都可以利用該類來完成日志的記錄,防止了重復(fù)代碼的出現(xiàn),增加了程序的可擴(kuò)展性和可維護(hù)性,從而提高了代碼的質(zhì)量。那么Spring中的AOP的實(shí)現(xiàn)是怎么樣的呢?接著讓我們來對(duì)Spring的AOP進(jìn)行探討,了解其內(nèi)部實(shí)現(xiàn)原理。
三.Spring中AOP的模擬實(shí)現(xiàn)
       在學(xué)習(xí)了Java的動(dòng)態(tài)代理機(jī)制后,我們?cè)诒竟?jié)中將學(xué)習(xí)Java的動(dòng)態(tài)代理機(jī)制在Spring中的應(yīng)用。首先我們創(chuàng)建一個(gè)名為AopHandler的類,該類可生成代理對(duì)象,同時(shí)可以根據(jù)設(shè)置的前置或后置處理對(duì)象分別在方法執(zhí)行前后執(zhí)行一些另外的操作,該類的內(nèi)容如下所示:

package org.amigo.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * AOP處理器. * @author <a href="mailto:xiexingxing1121@126.com">AmigoXie</a> * Creation date: 2007-10-7 - 上午10:13:28 */ public class AopHandler implements InvocationHandler { //需要代理的目標(biāo)對(duì)象 private Object target; //方法前置顧問 Advisor beforeAdvisor; //方法后置顧問 Advisor afterAdvisor; /** * 設(shè)置代理目標(biāo)對(duì)象,并生成動(dòng)態(tài)代理對(duì)象. * @param target 代理目標(biāo)對(duì)象 * @return 返回動(dòng)態(tài)代理對(duì)象 */ public Object setObject(Object target) { //設(shè)置代理目標(biāo)對(duì)象 this.target = target; //根據(jù)代理目標(biāo)對(duì)象生成動(dòng)態(tài)代理對(duì)象 Object obj = Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); return obj; } /** * 若定義了前置處理,則在方法執(zhí)行前執(zhí)行前置處理, * 若定義了后置處理,則在方法調(diào)用后調(diào)用后置處理. * @param proxy 代理對(duì)象 * @param method 調(diào)用的業(yè)務(wù)方法 * @param args 方法的參數(shù) * @return 返回結(jié)果信息 * @throws Throwable */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //進(jìn)行業(yè)務(wù)方法的前置處理 if (beforeAdvisor != null) { beforeAdvisor.doInAdvisor(proxy, method, args); } //執(zhí)行業(yè)務(wù)方法 Object result = method.invoke(target, args); //進(jìn)行業(yè)務(wù)方法的后置處理 if (afterAdvisor != null) { afterAdvisor.doInAdvisor(proxy, method, args); } //返回結(jié)果對(duì)象 return result; } /** * 設(shè)置方法的前置顧問. * @param advisor 方法的前置顧問 */ public void setBeforeAdvisor(Advisor advisor) { this.beforeAdvisor = advisor; } /** * 設(shè)置方法的后置顧問. * @param advisor 方法的后置顧問 */ public void setAfterAdvisor(Advisor advisor) { this.afterAdvisor = advisor; } }
    在上類中,前置和后置顧問對(duì)象都繼承Advisor接口,接下來讓我們來看看顧問接口類的內(nèi)容,該類定義了doInAdvisor(Object proxy, Method method, Object[] args)方法,如下所示:
package org.amigo.proxy; import java.lang.reflect.Method; /** * * 顧問接口類. * @author <a href="mailto:xiexingxing1121@126.com">AmigoXie</a> * Creation date: 2007-10-7 - 上午10:21:18 */ public interface Advisor { /** * 所做的操作. */ public void doInAdvisor(Object proxy, Method method, Object[] args); } BeforeMethodAdvisor和AfterMethodAdvisor都實(shí)現(xiàn)了Advisor接口,分別為方法的前置顧問和后置顧問類。 BeforeMethodAdvisor.java文件(前置顧問類)的內(nèi)容如下所示: package org.amigo.proxy; import java.lang.reflect.Method; /** * * 方法前置顧問,它完成方法的前置操作. * @author <a href="mailto:xiexingxing1121@126.com">AmigoXie</a> * Creation date: 2007-10-7 - 上午10:19:57 */ public class BeforeMethodAdvisor implements Advisor { /** * 在方法執(zhí)行前所進(jìn)行的操作. */ public void doInAdvisor(Object proxy, Method method, Object[] args) { System.out.println("before process " + method); } } AfterMethodAdvisor.java文件(后置顧問類)的內(nèi)容如下所示: package org.amigo.proxy; import java.lang.reflect.Method; /** * * 方法的后置顧問,它完成方法的后置操作. * @author <a href="mailto:xiexingxing1121@126.com">AmigoXie</a> * Creation date: 2007-10-7 - 上午10:20:43 */ public class AfterMethodAdvisor implements Advisor { /** * 在方法執(zhí)行后所進(jìn)行的操作. */ public void doInAdvisor(Object proxy, Method method, Object[] args) { System.out.println("after process " + method); } }
這兩個(gè)類分別在方法執(zhí)行前和方法執(zhí)行后做一些額外的操作。
       對(duì)于在配置文件中對(duì)某個(gè)bean配置前置或后置處理器,我們可以在bean中增加兩個(gè)屬性aop和aopType,aop的值為對(duì)應(yīng)的前置顧問類或后置顧問類的名稱,aopType用于指明該顧問類為前置還是后置顧問,為before時(shí)表示為前置處理器,為after時(shí)表示為后置處理器,這時(shí)候我們需要修改上一篇文章中的BeanFactory.java這個(gè)文件,添加其對(duì)aop和aopType的處理,修改后的該文件內(nèi)容如下:

package org.amigo.proxy; import java.io.InputStream; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.dom4j.Attribute; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.SAXReader; /** * bean工廠類. * @author <a href="mailto:xiexingxing1121@126.com">AmigoXie</a> * Creation date: 2007-10-7 - 下午04:04:34 */ public class BeanFactory { private Map<String, Object> beanMap = new HashMap<String, Object>(); /** * bean工廠的初始化. * @param xml xml配置文件 */ public void init(String xml) { try { //讀取指定的配置文件 SAXReader reader = new SAXReader(); ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); InputStream ins = classLoader.getResourceAsStream(xml); Document doc = reader.read(ins); Element root = doc.getRootElement(); Element foo; //創(chuàng)建AOP處理器 AopHandler aopHandler = new AopHandler(); //遍歷bean for (Iterator i = root.elementIterator("bean"); i.hasNext();) { foo = (Element) i.next(); //獲取bean的屬性id、class、aop以及aopType Attribute id = foo.attribute("id"); Attribute cls = foo.attribute("class"); Attribute aop = foo.attribute("aop"); Attribute aopType = foo.attribute("aopType"); //配置了aop和aopType屬性時(shí),需進(jìn)行攔截操作 if (aop != null && aopType != null) { //根據(jù)aop字符串獲取對(duì)應(yīng)的類 Class advisorCls = Class.forName(aop.getText()); //創(chuàng)建該類的對(duì)象 Advisor advisor = (Advisor) advisorCls.newInstance(); //根據(jù)aopType的類型來設(shè)置前置或后置顧問 if ("before".equals(aopType.getText())) { aopHandler.setBeforeAdvisor(advisor); } else if ("after".equals(aopType.getText())) { aopHandler.setAfterAdvisor(advisor); } } //利用Java反射機(jī)制,通過class的名稱獲取Class對(duì)象 Class bean = Class.forName(cls.getText()); //獲取對(duì)應(yīng)class的信息 java.beans.BeanInfo info = java.beans.Introspector.getBeanInfo(bean); //獲取其屬性描述 java.beans.PropertyDescriptor pd[] = info.getPropertyDescriptors(); //設(shè)置值的方法 Method mSet = null; //創(chuàng)建一個(gè)對(duì)象 Object obj = bean.newInstance(); //遍歷該bean的property屬性 for (Iterator ite = foo.elementIterator("property"); ite.hasNext();) { Element foo2 = (Element) ite.next(); //獲取該property的name屬性 Attribute name = foo2.attribute("name"); String value = null; //獲取該property的子元素value的值 for(Iterator ite1 = foo2.elementIterator("value"); ite1.hasNext();) { Element node = (Element) ite1.next(); value = node.getText(); break; } for (int k = 0; k < pd.length; k++) { if (pd[k].getName().equalsIgnoreCase(name.getText())) { mSet = pd[k].getWriteMethod(); //利用Java的反射極致調(diào)用對(duì)象的某個(gè)set方法,并將值設(shè)置進(jìn)去 mSet.invoke(obj, value); } } } //為對(duì)象增加前置或后置顧問 obj = (Object) aopHandler.setObject(obj); //將對(duì)象放入beanMap中,其中key為id值,value為對(duì)象 beanMap.put(id.getText(), obj); } } catch (Exception e) { System.out.println(e.toString()); } } /** * 通過bean的id獲取bean的對(duì)象. * @param beanName bean的id * @return 返回對(duì)應(yīng)對(duì)象 */ public Object getBean(String beanName) { Object obj = beanMap.get(beanName); return obj; } /** * 測(cè)試方法. * @param args */ public static void main(String[] args) { BeanFactory factory = new BeanFactory(); factory.init("config.xml"); BusinessObj obj = (BusinessObj) factory.getBean("businessObj"); obj.process(); } }
    觀察此類我們可以發(fā)現(xiàn),該類添加了對(duì)bean元素的aop和aopType屬性的處理。編寫完該文件后,我們還需要修改src目錄下的配置文件:config.xml文件,在該文件中添加名為businessObj的bean,我們?yōu)槠渑渲昧薬op和aopType屬性,增加了方法的前置顧問。增加的部分為:

<bean id="businessObj" class="org.amigo.proxy.BusinessObjImpl" aop="org.amigo.proxy.BeforeMethodAdvisor" aopType="before"/>
       此時(shí)運(yùn)行BeanFactory.java這個(gè)類文件,運(yùn)行結(jié)果如下:
before process public abstract void org.amigo.proxy.BusinessObj.process()
執(zhí)行業(yè)務(wù)邏輯
       由運(yùn)行結(jié)果可以看出,前置處理已經(jīng)生效。
       本節(jié)中的例子只是實(shí)現(xiàn)了Spring的AOP一小部分功能,即為某個(gè)bean添加前置或后置處理,在Spring中,考慮的比這多很多,例如,為多個(gè)bean配置動(dòng)態(tài)代理等等。但是究其根源,Spring中AOP的實(shí)現(xiàn),是基于Java中無比強(qiáng)大的反射和動(dòng)態(tài)代理機(jī)制。
 
四.總結(jié)
       本文講述了AOP的概念等信息,并詳細(xì)講解了Java的動(dòng)態(tài)代理機(jī)制,接著又通過一個(gè)簡(jiǎn)單的實(shí)例講解了Spring中AOP的模擬實(shí)現(xiàn),使得讀者能夠更好地學(xué)習(xí)Java的反射和代理機(jī)制。
通過這兩篇文章,使得我們能夠更加深入地理解Java的反射和動(dòng)態(tài)代理機(jī)制,同時(shí)對(duì)Spring中盛行的IOC和AOP的后臺(tái)實(shí)現(xiàn)原理有了更加清晰的理解,Java的反射和動(dòng)態(tài)代理機(jī)制的強(qiáng)大功能在這兩篇文章中可見一斑。有興趣的朋友可以通過學(xué)習(xí)Spring框架的源碼來進(jìn)一步的理解Java的反射和動(dòng)態(tài)代理機(jī)制,從而在實(shí)際的開發(fā)工作中更好地理解


]]>
利用java的反射實(shí)現(xiàn)IOC(轉(zhuǎn))http://www.aygfsteel.com/lanxin1020/archive/2009/04/06/264072.htmllanxin1020lanxin1020Mon, 06 Apr 2009 03:43:00 GMThttp://www.aygfsteel.com/lanxin1020/archive/2009/04/06/264072.htmlhttp://www.aygfsteel.com/lanxin1020/comments/264072.htmlhttp://www.aygfsteel.com/lanxin1020/archive/2009/04/06/264072.html#Feedback0http://www.aygfsteel.com/lanxin1020/comments/commentRss/264072.htmlhttp://www.aygfsteel.com/lanxin1020/services/trackbacks/264072.html閱讀全文

]]>
工廠模式 與 ioc(轉(zhuǎn))http://www.aygfsteel.com/lanxin1020/archive/2009/04/06/264043.htmllanxin1020lanxin1020Sun, 05 Apr 2009 16:13:00 GMThttp://www.aygfsteel.com/lanxin1020/archive/2009/04/06/264043.htmlhttp://www.aygfsteel.com/lanxin1020/comments/264043.htmlhttp://www.aygfsteel.com/lanxin1020/archive/2009/04/06/264043.html#Feedback0http://www.aygfsteel.com/lanxin1020/comments/commentRss/264043.htmlhttp://www.aygfsteel.com/lanxin1020/services/trackbacks/264043.htmlSpring的模塊化是很強(qiáng)的,各個(gè)功能模塊都是獨(dú)立的,我們可以選擇的使用。這一章先從Spring的IoC開始。所謂IoC就是一個(gè)用XML來定義生成對(duì)象的模式,我們看看如果來使用的。

   數(shù)據(jù)模型

   1、如下圖所示有三個(gè)類,Human(人類)是接口,Chinese(中國人)是一個(gè)子類,American(美國人)是另外一個(gè)子類。

源代碼如下:

java 代碼
  1. package cn.com.chengang.spring;   
  2. public interface Human {   
  3. void eat();   
  4. void walk();   
  5. }   
  6.   
  7. package cn.com.chengang.spring;   
  8. public class Chinese implements Human {   
  9. /* (非 Javadoc)  
  10. * @see cn.com.chengang.spring.Human#eat()  
  11. */  
  12. public void eat() {   
  13. System.out.println("中國人對(duì)吃很有一套");   
  14. }   
  15.   
  16. /* (非 Javadoc)  
  17. * @see cn.com.chengang.spring.Human#walk()  
  18. */  
  19. public void walk() {   
  20. System.out.println("中國人行如飛");   
  21. }   
  22. }   
  23.   
  24. package cn.com.chengang.spring;   
  25. public class American implements Human {   
  26. /* (非 Javadoc)  
  27. * @see cn.com.chengang.spring.Human#eat()  
  28. */  
  29. public void eat() {   
  30. System.out.println("美國人主要以面包為主");   
  31. }   
  32.   
  33. /* (非 Javadoc)  
  34. * @see cn.com.chengang.spring.Human#walk()  
  35. */  
  36. public void walk() {   
  37. System.out.println("美國人以車代步,有四肢退化的趨勢(shì)");   
  38. }   
  39. }  

 

2、對(duì)以上對(duì)象采用工廠模式的用法如下

   創(chuàng)建一個(gè)工廠類Factory,如下。這個(gè)工廠類里定義了兩個(gè)字符串常量,所標(biāo)識(shí)不同的人種。getHuman方法根據(jù)傳入?yún)?shù)的字串,來判斷要生成什么樣的人種。

java 代碼
  1. package cn.com.chengang.spring;   
  2. public class Factory {   
  3. public final static String CHINESE = "Chinese";   
  4. public final static String AMERICAN = "American";   
  5.   
  6. public Human getHuman(String ethnic) {   
  7. if (ethnic.equals(CHINESE))   
  8. return new Chinese();   
  9. else if (ethnic.equals(AMERICAN))   
  10. return new American();   
  11. else  
  12. throw new IllegalArgumentException("參數(shù)(人種)錯(cuò)誤");   
  13. }   
  14. }  

 

下面是一個(gè)測(cè)試的程序,使用工廠方法來得到了不同的“人種對(duì)象”,并執(zhí)行相應(yīng)的方法。

java 代碼
  1. package cn.com.chengang.spring;   
  2. public class ClientTest {   
  3. public static void main(String[] args) {   
  4. Human human = null;   
  5. human = new Factory().getHuman(Factory.CHINESE);   
  6. human.eat();   
  7. human.walk();   
  8. human = new Factory().getHuman(Factory.AMERICAN);   
  9. human.eat();   
  10. human.walk();   
  11. }   
  12. }  

 

 3、采用Spring的IoC的用法如下:

   在項(xiàng)目根目錄下創(chuàng)建一個(gè)bean.xml文件

xml 代碼
  1. <?xml version="1.0" encoding="UTF-8"?>   
  2. <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">   
  3. <beans>   
  4. <bean id="Chinese" class="cn.com.chengang.spring.Chinese"/>   
  5. <bean id="American" class="cn.com.chengang.spring.American"/>   
  6. </beans>  

修改ClientTest程序如下:

java 代碼
  1. package cn.com.chengang.spring;   
  2. import org.springframework.context.ApplicationContext;   
  3. import org.springframework.context.support.FileSystemXmlApplicationContext;   
  4. public class ClientTest {   
  5. public final static String CHINESE = "Chinese";   
  6. public final static String AMERICAN = "American";   
  7.   
  8. public static void main(String[] args) {   
  9. // Human human = null;   
  10. // human = new Factory().getHuman(Factory.CHINESE);   
  11. // human.eat();   
  12. // human.walk();   
  13. // human = new Factory().getHuman(Factory.AMERICAN);   
  14. // human.eat();   
  15. // human.walk();   
  16.   
  17. ApplicationContext ctx = new FileSystemXmlApplicationContext("bean.xml");   
  18. Human human = null;   
  19. human = (Human) ctx.getBean(CHINESE);   
  20. human.eat();   
  21. human.walk();   
  22. human = (Human) ctx.getBean(AMERICAN);   
  23. human.eat();   
  24. human.walk();   
  25. }   
  26. }  

 從這個(gè)程序可以看到,ctx就相當(dāng)于原來的Factory工廠,原來的Factory就可以刪除掉了。然后又把Factory里的兩個(gè)常量移到了ClientTest類里,整個(gè)程序結(jié)構(gòu)基本一樣。

   再回頭看原來的bean.xml文件的這一句:

<bean id="Chinese" class="cn.com.chengang.spring.Chinese"/>

   id就是ctx.getBean的參數(shù)值,一個(gè)字符串。class就是一個(gè)類(包名+類名)。然后在ClientTest類里獲得Chinese對(duì)象就是這么一句

human = (Human) ctx.getBean(CHINESE);

   因?yàn)間etBean方法返回的是Object類型,所以前面要加一個(gè)類型轉(zhuǎn)換。

   總結(jié)

   (1)也許有人說,IoC和工廠模式不是一樣的作用嗎,用IoC好象還麻煩一點(diǎn)。

   舉個(gè)例子,如果用戶需求發(fā)生變化,要把Chinese類修改一下。那么前一種工廠模式,就要更改Factory類的方法,并且重新編譯布署。而IoC只需要將class屬性改變一下,并且由于IoC利用了Java反射機(jī)制,這些對(duì)象是動(dòng)態(tài)生成的,這時(shí)我們就可以熱插撥Chinese對(duì)象(不必把原程序停止下來重新編譯布署)

   (2)也許有人說,即然IoC這么好,那么我把系統(tǒng)所有對(duì)象都用IoC方式來生成。

   注意,IoC的靈活性是有代價(jià)的:設(shè)置步驟麻煩、生成對(duì)象的方式不直觀、反射比正常生成對(duì)象在效率上慢一點(diǎn)。因此使用IoC要看有沒有必要,我認(rèn)為比較通用的判斷方式是:用到工廠模式的地方都可以考慮用IoC模式。

   (3)在上面的IoC的方式里,還有一些可以變化的地方。比如,bean.xml不一定要放在項(xiàng)目錄下,也可以放在其他地方,比如cn.com.chengang.spring包里。不過在使用時(shí)也要變化一下,如下所示:

new FileSystemXmlApplicationContext("src/cn/com/chengang/spring/bean.xml");

   另外,bean.xml也可以改成其他名字。這樣我們?cè)谙到y(tǒng)中就可以分門別類的設(shè)置不同的bean.xml。

   (4)關(guān)于IoC的低侵入性。

   什么是低侵入性?如果你用過Struts或EJB就會(huì)發(fā)現(xiàn),要繼承一些接口或類,才能利用它們的框架開發(fā)。這樣,系統(tǒng)就被綁定在Struts、EJB上了,對(duì)系統(tǒng)的可移植性產(chǎn)生不利的影響。如果代碼中很少涉及某一個(gè)框架的代碼,那么這個(gè)框架就可以稱做是一個(gè)低侵入性的框架。

   Spring的侵入性很低,Humen.java、Chinese.java等幾個(gè)類都不必繼承什么接口或類。但在ClientTest里還是有一些Spring的影子:FileSystemXmlApplicationContext類和ctx.getBean方式等。
現(xiàn)在,低侵入性似乎也成了判定一個(gè)框架的實(shí)現(xiàn)技術(shù)好壞的標(biāo)準(zhǔn)之一。

   (5)關(guān)于bean.xml的用法

   bean.xml的用法還有很多,其中內(nèi)容是相當(dāng)豐富的。假設(shè)Chinese類里有一個(gè)humenName屬性(姓名),那么原的bean.xml修改如下。此后生成Chinese對(duì)象時(shí),“陳剛”這個(gè)值將自動(dòng)設(shè)置到Chinese類的humenName屬性中。而且由于singleton為true這時(shí)生成Chinese對(duì)象將采用單例模式,系統(tǒng)僅存在一個(gè)Chinese對(duì)象實(shí)例。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="Chinese" class="cn.com.chengang.spring.Chinese" singleton="true">
<property name="humenName">
<value>陳剛</value>
</property>
</bean>
<bean id="American" class="cn.com.chengang.spring.American"/>
</beans>

   關(guān)于bean.xml的其它用法,不再詳細(xì)介紹了,大家自己拿Spring的文檔一看就明白了。

 Spring能有效地組織J2EE應(yīng)用各層的對(duì)象。不管是控制層的Action對(duì)象,還是業(yè)務(wù)層的Service對(duì)象,還是持久層的DAO對(duì)象,都可在Spring的管理下有機(jī)地協(xié)調(diào)、運(yùn)行。Spring將各層的對(duì)象以松耦合的方式組織在一起,Action對(duì)象無須關(guān)心Service對(duì)象的具體實(shí)現(xiàn),Service對(duì)象無須關(guān)心持久層對(duì)象的具體實(shí)現(xiàn),各層對(duì)象的調(diào)用完全面向接口。當(dāng)系統(tǒng)需要重構(gòu)時(shí),代碼的改寫量將大大減少。

  上面所說的一切都得宜于Spring的核心機(jī)制,依賴注入。依賴注入讓bean與bean之間以配置文件組織在一起,而不是以硬編碼的方式耦合在一起。理解依賴注入

  依賴注入(Dependency Injection)和控制反轉(zhuǎn)(Inversion of Control)是同一個(gè)概念。具體含義是:當(dāng)某個(gè)角色(可能是一個(gè)Java實(shí)例,調(diào)用者)需要另一個(gè)角色(另一個(gè)Java實(shí)例,被調(diào)用者)的協(xié)助時(shí),在傳統(tǒng)的程序設(shè)計(jì)過程中,通常由調(diào)用者來創(chuàng)建被調(diào)用者的實(shí)例。但在Spring里,創(chuàng)建被調(diào)用者的工作不再由調(diào)用者來完成,因此稱為控制反轉(zhuǎn);創(chuàng)建被調(diào)用者實(shí)例的工作通常由Spring容器來完成,然后注入調(diào)用者,因此也稱為依賴注入。

  不管是依賴注入,還是控制反轉(zhuǎn),都說明Spring采用動(dòng)態(tài)、靈活的方式來管理各種對(duì)象。對(duì)象與對(duì)象之間的具體實(shí)現(xiàn)互相透明。在理解依賴注入之前,看如下這個(gè)問題在各種社會(huì)形態(tài)里如何解決:一個(gè)人(Java實(shí)例,調(diào)用者)需要一把斧子(Java實(shí)例,被調(diào)用者)。

  (1)原始社會(huì)里,幾乎沒有社會(huì)分工。需要斧子的人(調(diào)用者)只能自己去磨一把斧子(被調(diào)用者)。對(duì)應(yīng)的情形為:Java程序里的調(diào)用者自己創(chuàng)建被調(diào)用者。

  (2)進(jìn)入工業(yè)社會(huì),工廠出現(xiàn)。斧子不再由普通人完成,而在工廠里被生產(chǎn)出來,此時(shí)需要斧子的人(調(diào)用者)找到工廠,購買斧子,無須關(guān)心斧子的制造過程。對(duì)應(yīng)Java程序的簡(jiǎn)單工廠的設(shè)計(jì)模式。

  (3)進(jìn)入“按需分配”社會(huì),需要斧子的人不需要找到工廠,坐在家里發(fā)出一個(gè)簡(jiǎn)單指令:需要斧子。斧子就自然出現(xiàn)在他面前。對(duì)應(yīng)Spring的依賴注入。

  第一種情況下,Java實(shí)例的調(diào)用者創(chuàng)建被調(diào)用的Java實(shí)例,必然要求被調(diào)用的Java類出現(xiàn)在調(diào)用者的代碼里。無法實(shí)現(xiàn)二者之間的松耦合。

  第二種情況下,調(diào)用者無須關(guān)心被調(diào)用者具體實(shí)現(xiàn)過程,只需要找到符合某種標(biāo)準(zhǔn)(接口)的實(shí)例,即可使用。此時(shí)調(diào)用的代碼面向接口編程,可以讓調(diào)用者和被調(diào)用者解耦,這也是工廠模式大量使用的原因。但調(diào)用者需要自己定位工廠,調(diào)用者與特定工廠耦合在一起。

  第三種情況下,調(diào)用者無須自己定位工廠,程序運(yùn)行到需要被調(diào)用者時(shí),系統(tǒng)自動(dòng)提供被調(diào)用者實(shí)例。事實(shí)上,調(diào)用者和被調(diào)用者都處于Spring的管理下,二者之間的依賴關(guān)系由Spring提供。

  所謂依賴注入,是指程序運(yùn)行過程中,如果需要調(diào)用另一個(gè)對(duì)象協(xié)助時(shí),無須在代碼中創(chuàng)建被調(diào)用者,而是依賴于外部的注入。Spring的依賴注入對(duì)調(diào)用者和被調(diào)用者幾乎沒有任何要求,完全支持對(duì)POJO之間依賴關(guān)系的管理。依賴注入通常有兩種:

  ·設(shè)值注入。

  ·構(gòu)造注入。
.......



]]>
AOP 動(dòng)態(tài)代理模式(轉(zhuǎn))http://www.aygfsteel.com/lanxin1020/archive/2009/04/05/264034.htmllanxin1020lanxin1020Sun, 05 Apr 2009 14:40:00 GMThttp://www.aygfsteel.com/lanxin1020/archive/2009/04/05/264034.htmlhttp://www.aygfsteel.com/lanxin1020/comments/264034.htmlhttp://www.aygfsteel.com/lanxin1020/archive/2009/04/05/264034.html#Feedback0http://www.aygfsteel.com/lanxin1020/comments/commentRss/264034.htmlhttp://www.aygfsteel.com/lanxin1020/services/trackbacks/264034.html閱讀全文

]]>
整合 Struts 和 Springhttp://www.aygfsteel.com/lanxin1020/archive/2009/04/05/263966.htmllanxin1020lanxin1020Sun, 05 Apr 2009 02:17:00 GMThttp://www.aygfsteel.com/lanxin1020/archive/2009/04/05/263966.htmlhttp://www.aygfsteel.com/lanxin1020/comments/263966.htmlhttp://www.aygfsteel.com/lanxin1020/archive/2009/04/05/263966.html#Feedback0http://www.aygfsteel.com/lanxin1020/comments/commentRss/263966.htmlhttp://www.aygfsteel.com/lanxin1020/services/trackbacks/263966.html閱讀全文

]]>
三種整合 Struts 應(yīng)用程序與 Spring 的方式(轉(zhuǎn))http://www.aygfsteel.com/lanxin1020/archive/2009/04/05/263963.htmllanxin1020lanxin1020Sun, 05 Apr 2009 01:37:00 GMThttp://www.aygfsteel.com/lanxin1020/archive/2009/04/05/263963.htmlhttp://www.aygfsteel.com/lanxin1020/comments/263963.htmlhttp://www.aygfsteel.com/lanxin1020/archive/2009/04/05/263963.html#Feedback0http://www.aygfsteel.com/lanxin1020/comments/commentRss/263963.htmlhttp://www.aygfsteel.com/lanxin1020/services/trackbacks/263963.html

三種整合 Struts 應(yīng)用程序與 Spring 的方式

 

Struts Recipes 的合著者 George Franciscus 將介紹另一個(gè)重大的 Struts 整合竅門 —— 這次是將 Struts 應(yīng)用程序?qū)?Spring 框架。請(qǐng)跟隨 George,他將向您展示如何改變 Struts 動(dòng)作,使得管理 Struts 動(dòng)作就像管理 Spring beans 那樣。結(jié)果是一個(gè)增強(qiáng)的 web 框架,這個(gè)框架可以方便地利用 Spring AOP 的優(yōu)勢(shì)。

您肯定已經(jīng)聽說過控制反轉(zhuǎn) (IOC) 設(shè)計(jì)模式,因?yàn)楹荛L(zhǎng)一段時(shí)間以來一直在流傳關(guān)于它的信息。如果您在任何功能中使用過 Spring 框架,那么您就知道其原理的作用。在本文中,我利用這一原理把一個(gè) Struts 應(yīng)用程序注入 Spring 框架,您將親身體會(huì)到 IOC 模式的強(qiáng)大。

將一個(gè) Struts 應(yīng)用程序整合進(jìn) Spring 框架具有多方面的優(yōu)點(diǎn)。首先,Spring 是為解決一些關(guān)于 JEE 的真實(shí)世界問題而設(shè)計(jì)的,比如復(fù)雜性、低性能和可測(cè)試性,等等。第二,Spring 框架包含一個(gè) AOP 實(shí)現(xiàn),允許您將面向方面技術(shù)應(yīng)用于面向?qū)ο蟮拇a。第三,一些人可能會(huì)說 Spring 框架只有處理 Struts 比 Struts 處理自己好。但是這是觀點(diǎn)問題,我演示三種將 Struts 應(yīng)用程序整合到 Spring 框架的方法后,具體由您自己決定使用哪一種。

我所演示的方法都是執(zhí)行起來相對(duì)簡(jiǎn)單的,但是它們卻具有明顯不同的優(yōu)點(diǎn)。我為每一種方法創(chuàng)建了一個(gè)獨(dú)立而可用的例子,這樣您就可以完全理解每種方法。請(qǐng)參閱 下載 部分獲得完整例子源代碼。請(qǐng)參閱 參考資料,下載 Struts MVC 和 Spring 框架。

為什么 Spring 這么了不起?

Spring 的創(chuàng)立者 Rod Johnson 以一種批判的眼光看待 Java™ 企業(yè)軟件開發(fā),并且提議很多企業(yè)難題都能夠通過戰(zhàn)略地使用 IOC 模式(也稱作依賴注入)來解決。當(dāng) Rod 和一個(gè)具有奉獻(xiàn)精神的開放源碼開發(fā)者團(tuán)隊(duì)將這個(gè)理論應(yīng)用于實(shí)踐時(shí),結(jié)果就產(chǎn)生了 Spring 框架。簡(jiǎn)言之,Spring 是一個(gè)輕型的容器,利用它可以使用一個(gè)外部 XML 配置文件方便地將對(duì)象連接在一起。每個(gè)對(duì)象都可以通過顯示一個(gè) JavaBean 屬性收到一個(gè)到依賴對(duì)象的引用,留給您的簡(jiǎn)單任務(wù)就只是在一個(gè) XML 配置文件中把它們連接好。

IOC 和 Spring

IOC 是一種使應(yīng)用程序邏輯外在化的設(shè)計(jì)模式,所以它是被注入而不是被寫入客戶機(jī)代碼中。將 IOC 與接口編程應(yīng)用結(jié)合,就像 Spring 框架那樣,產(chǎn)生了一種架構(gòu),這種架構(gòu)能夠減少客戶機(jī)對(duì)特定實(shí)現(xiàn)邏輯的依賴。請(qǐng)參閱 參考資料 了解更多關(guān)于 IOC 和 Spring 的信息。

依賴注入是一個(gè)強(qiáng)大的特性,但是 Spring 框架能夠提供更多特性。Spring 支持可插拔的事務(wù)管理器,可以給您的事務(wù)處理提供更廣泛的選擇范圍。它集成了領(lǐng)先的持久性框架,并且提供一個(gè)一致的異常層次結(jié)構(gòu)。Spring 還提供了一種使用面向方面代碼代替正常的面向?qū)ο蟠a的簡(jiǎn)單機(jī)制。

Spring AOP 允許您使用攔截器 在一個(gè)或多個(gè)執(zhí)行點(diǎn)上攔截應(yīng)用程序邏輯。加強(qiáng)應(yīng)用程序在攔截器中的日志記錄邏輯會(huì)產(chǎn)生一個(gè)更可讀的、實(shí)用的代碼基礎(chǔ),所以攔截器廣泛用于日志記錄。您很快就會(huì)看到,為了處理橫切關(guān)注點(diǎn),Spring AOP 發(fā)布了它自己的攔截器,您也可以編寫您自己的攔截器。






整合 Struts 和 Spring

與 Struts 相似,Spring 可以作為一個(gè) MVC 實(shí)現(xiàn)。這兩種框架都具有自己的優(yōu)點(diǎn)和缺點(diǎn),盡管大部分人同意 Struts 在 MVC 方面仍然是最好的。很多開發(fā)團(tuán)隊(duì)已經(jīng)學(xué)會(huì)在時(shí)間緊迫的時(shí)候利用 Struts 作為構(gòu)造高品質(zhì)軟件的基礎(chǔ)。Struts 具有如此大的推動(dòng)力,以至于開發(fā)團(tuán)隊(duì)寧愿整合 Spring 框架的特性,而不愿意轉(zhuǎn)換成 Spring MVC。沒必要進(jìn)行轉(zhuǎn)換對(duì)您來說是一個(gè)好消息。Spring 架構(gòu)允許您將 Struts 作為 Web 框架連接到基于 Spring 的業(yè)務(wù)和持久層。最后的結(jié)果就是現(xiàn)在一切條件都具備了。

在接下來的小竅門中,您將會(huì)了解到三種將 Struts MVC 整合到 Spring 框架的方法。我將揭示每種方法的缺陷并且對(duì)比它們的優(yōu)點(diǎn)。 一旦您了解到所有三種方法的作用,我將會(huì)向您展示一個(gè)令人興奮的應(yīng)用程序,這個(gè)程序使用的是這三種方法中我最喜歡的一種。





三個(gè)小竅門

接下來的每種整合技術(shù)(或者竅門)都有自己的優(yōu)點(diǎn)和特點(diǎn)。我偏愛其中的一種,但是我知道這三種都能夠加深您對(duì) Struts 和 Spring 的理解。在處理各種不同情況的時(shí)候,這將給您提供一個(gè)廣闊的選擇范圍。方法如下:

  • 使用 Spring 的 ActionSupport 類整合 Structs
  • 使用 Spring 的 DelegatingRequestProcessor 覆蓋 Struts 的 RequestProcessor
  • 將 Struts Action 管理委托給 Spring 框架

裝載應(yīng)用程序環(huán)境

無論您使用哪種技術(shù),都需要使用 Spring 的 ContextLoaderPlugin 為 Struts 的 ActionServlet 裝載 Spring 應(yīng)用程序環(huán)境。就像添加任何其他插件一樣,簡(jiǎn)單地向您的 struts-config.xml 文件添加該插件,如下所示:

<plug-in className=
                        "org.springframework.web.struts.ContextLoaderPlugIn">
                        <set-property property=
                        "contextConfigLocation" value="/WEB-INF/beans.xml"/>
                        </plug-in>
                        

前面已經(jīng)提到過,在 下載 部分,您能夠找到這三個(gè)完全可使用的例子的完整源代碼。每個(gè)例子都為一個(gè)書籍搜索應(yīng)用程序提供一種不同的 Struts 和 Spring 的整合方法。您可以在這里看到例子的要點(diǎn),但是您也可以下載應(yīng)用程序以查看所有的細(xì)節(jié)。





竅門 1. 使用 Spring 的 ActionSupport

手動(dòng)創(chuàng)建一個(gè) Spring 環(huán)境是一種整合 Struts 和 Spring 的最直觀的方式。為了使它變得更簡(jiǎn)單,Spring 提供了一些幫助。為了方便地獲得 Spring 環(huán)境,org.springframework.web.struts.ActionSupport 類提供了一個(gè) getWebApplicationContext() 方法。您所做的只是從 Spring 的 ActionSupport 而不是 Struts Action 類擴(kuò)展您的動(dòng)作,如清單 1 所示:


清單 1. 使用 ActionSupport 整合 Struts
package ca.nexcel.books.actions;
                        import java.io.IOException;
                        import javax.servlet.ServletException;
                        import javax.servlet.http.HttpServletRequest;
                        import javax.servlet.http.HttpServletResponse;
                        import org.apache.struts.action.ActionError;
                        import org.apache.struts.action.ActionErrors;
                        import org.apache.struts.action.ActionForm;
                        import org.apache.struts.action.ActionForward;
                        import org.apache.struts.action.ActionMapping;
                        import org.apache.struts.action.DynaActionForm;
                        import org.springframework.context.ApplicationContext;
                        import org.springframework.web.struts.ActionSupport;
                        import ca.nexcel.books.beans.Book;
                        import ca.nexcel.books.business.BookService;
                        public class SearchSubmit extends ActionSupport {   |(1)
                        public ActionForward execute(
                        ActionMapping mapping,
                        ActionForm form,
                        HttpServletRequest request,
                        HttpServletResponse response)
                        throws IOException, ServletException {
                        DynaActionForm searchForm = (DynaActionForm) form;
                        String isbn = (String) searchForm.get("isbn");
                        //the old fashion way
                        //BookService bookService = new BookServiceImpl();
                        ApplicationContext ctx =
                        getWebApplicationContext();    |(2)
                        BookService bookService =
                        (BookService) ctx.getBean("bookService");   |(3)
                        Book book = bookService.read(isbn.trim());
                        if (null == book) {
                        ActionErrors errors = new ActionErrors();
                        errors.add(ActionErrors.GLOBAL_ERROR,new ActionError
                        ("message.notfound"));
                        saveErrors(request, errors);
                        return mapping.findForward("failure") ;
                        }
                        request.setAttribute("book", book);
                        return mapping.findForward("success");
                        }
                        }
                        

讓我們快速思考一下這里到底發(fā)生了什么。在 (1) 處,我通過從 Spring 的 ActionSupport 類而不是 Struts 的 Action 類進(jìn)行擴(kuò)展,創(chuàng)建了一個(gè)新的 Action。在 (2) 處,我使用 getWebApplicationContext() 方法獲得一個(gè) ApplicationContext。為了獲得業(yè)務(wù)服務(wù),我使用在 (2) 處獲得的環(huán)境在 (3) 處查找一個(gè) Spring bean。

這種技術(shù)很簡(jiǎn)單并且易于理解。不幸的是,它將 Struts 動(dòng)作與 Spring 框架耦合在一起。如果您想替換掉 Spring,那么您必須重寫代碼。并且,由于 Struts 動(dòng)作不在 Spring 的控制之下,所以它不能獲得 Spring AOP 的優(yōu)勢(shì)。當(dāng)使用多重獨(dú)立的 Spring 環(huán)境時(shí),這種技術(shù)可能有用,但是在大多數(shù)情況下,這種方法不如另外兩種方法合適。





竅門 2. 覆蓋 RequestProcessor

將 Spring 從 Struts 動(dòng)作中分離是一個(gè)更巧妙的設(shè)計(jì)選擇。分離的一種方法是使用 org.springframework.web.struts.DelegatingRequestProcessor 類來覆蓋 Struts 的 RequestProcessor 處理程序,如清單 2 所示:


清單 2. 通過 Spring 的 DelegatingRequestProcessor 進(jìn)行整合
<?xml version="1.0" encoding="ISO-8859-1" ?>
                        <!DOCTYPE struts-config PUBLIC
                        "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
                        "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
                        <struts-config>
                        <form-beans>
                        <form-bean name="searchForm"
                        type="org.apache.struts.validator.DynaValidatorForm">
                        <form-property name="isbn"    type="java.lang.String"/>
                        </form-bean>
                        </form-beans>
                        <global-forwards type="org.apache.struts.action.ActionForward">
                        <forward   name="welcome"                path="/welcome.do"/>
                        <forward   name="searchEntry"            path="/searchEntry.do"/>
                        <forward   name="searchSubmit"           path="/searchSubmit.do"/>
                        </global-forwards>
                        <action-mappings>
                        <action    path="/welcome" forward="/WEB-INF/pages/welcome.htm"/>
                        <action    path="/searchEntry" forward="/WEB-INF/pages/search.jsp"/>
                        <action    path="/searchSubmit"
                        type="ca.nexcel.books.actions.SearchSubmit"
                        input="/searchEntry.do"
                        validate="true"
                        name="searchForm">
                        <forward name="success" path="/WEB-INF/pages/detail.jsp"/>
                        <forward name="failure" path="/WEB-INF/pages/search.jsp"/>
                        </action>
                        </action-mappings>
                        <message-resources parameter="ApplicationResources"/>
                        <controller processorClass="org.springframework.web.struts.
                        DelegatingRequestProcessor"/> |(1)
                        <plug-in className="org.apache.struts.validator.ValidatorPlugIn">
                        <set-property property="pathnames"
                        value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml"/>
                        </plug-in>
                        <plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
                        <set-property property="csntextConfigLocation" value="/WEB-INF/beans.xml"/>
                        </plug-in>
                        </struts-config>
                        

我利用了 <controller> 標(biāo)記來用 DelegatingRequestProcessor 覆蓋默認(rèn)的 Struts RequestProcessor。下一步是在我的 Spring 配置文件中注冊(cè)該動(dòng)作,如清單 3 所示:


清單 3. 在 Spring 配置文件中注冊(cè)一個(gè)動(dòng)作
<?xml version="1.0" encoding="UTF-8"?>
                        <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
                        "http://www.springframework.org/dtd/spring-beans.dtd">
                        <beans>
                        <bean id="bookService" class="ca.nexcel.books.business.BookServiceImpl"/>
                        <bean name="/searchSubmit"
                        class="ca.nexcel.books.actions.SearchSubmit"> |(1)
                        <property name="bookService">
                        <ref bean="bookService"/>
                        </property>
                        </bean>
                        </beans>
                        

注意:在 (1) 處,我使用名稱屬性注冊(cè)了一個(gè) bean,以匹配 struts-config 動(dòng)作映射名稱。SearchSubmit 動(dòng)作揭示了一個(gè) JavaBean 屬性,允許 Spring 在運(yùn)行時(shí)填充屬性,如清單 4 所示:


清單 4. 具有 JavaBean 屬性的 Struts 動(dòng)作
package ca.nexcel.books.actions;
                        import java.io.IOException;
                        import javax.servlet.ServletException;
                        import javax.servlet.http.HttpServletRequest;
                        import javax.servlet.http.HttpServletResponse;
                        import org.apache.struts.action.Action;
                        import org.apache.struts.action.ActionError;
                        import org.apache.struts.action.ActionErrors;
                        import org.apache.struts.action.ActionForm;
                        import org.apache.struts.action.ActionForward;
                        import org.apache.struts.action.ActionMapping;
                        import org.apache.struts.action.DynaActionForm;
                        import ca.nexcel.books.beans.Book;
                        import ca.nexcel.books.business.BookService;
                        public class SearchSubmit extends Action {
                        private BookService bookService;
                        public BookService getBookService() {
                        return bookService;
                        }
                        public void setBookService(BookService bookService) { | (1)
                        this.bookService = bookService;
                        }
                        public ActionForward execute(
                        ActionMapping mapping,
                        ActionForm form,
                        HttpServletRequest request,
                        HttpServletResponse response)
                        throws IOException, ServletException {
                        DynaActionForm searchForm = (DynaActionForm) form;
                        String isbn = (String) searchForm.get("isbn");
                        Book book = getBookService().read(isbn.trim());  |(2)
                        if (null == book) {
                        ActionErrors errors = new ActionErrors();
                        errors.add(ActionErrors.GLOBAL_ERROR,new ActionError("message.notfound"));
                        saveErrors(request, errors);
                        return mapping.findForward("failure") ;
                        }
                        request.setAttribute("book", book);
                        return mapping.findForward("success");
                        }
                        }
                        

在清單 4 中,您可以了解到如何創(chuàng)建 Struts 動(dòng)作。在 (1) 處,我創(chuàng)建了一個(gè) JavaBean 屬性。DelegatingRequestProcessor自動(dòng)地配置這種屬性。這種設(shè)計(jì)使 Struts 動(dòng)作并不知道它正被 Spring 管理,并且使您能夠利用 Sping 的動(dòng)作管理框架的所有優(yōu)點(diǎn)。由于您的 Struts 動(dòng)作注意不到 Spring 的存在,所以您不需要重寫您的 Struts 代碼就可以使用其他控制反轉(zhuǎn)容器來替換掉 Spring。

DelegatingRequestProcessor 方法的確比第一種方法好,但是仍然存在一些問題。如果您使用一個(gè)不同的 RequestProcessor,則需要手動(dòng)整合 Spring 的 DelegatingRequestProcessor。添加的代碼會(huì)造成維護(hù)的麻煩并且將來會(huì)降低您的應(yīng)用程序的靈活性。此外,還有過一些使用一系列命令來代替 Struts RequestProcessor 的傳聞。 這種改變將會(huì)對(duì)這種解決方法的使用壽命造成負(fù)面的影響。





竅門 3. 將動(dòng)作管理委托給 Spring

一個(gè)更好的解決方法是將 Strut 動(dòng)作管理委托給 Spring。您可以通過在 struts-config 動(dòng)作映射中注冊(cè)一個(gè)代理來實(shí)現(xiàn)。代理負(fù)責(zé)在 Spring 環(huán)境中查找 Struts 動(dòng)作。由于動(dòng)作在 Spring 的控制之下,所以它可以填充動(dòng)作的 JavaBean 屬性,并為應(yīng)用諸如 Spring 的 AOP 攔截器之類的特性帶來了可能。

清單 5 中的 Action 類與清單 4 中的相同。但是 struts-config 有一些不同:


清單 5. Spring 整合的委托方法
<?xml version="1.0" encoding="ISO-8859-1" ?>
                        <!DOCTYPE struts-config PUBLIC
                        "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
                        "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
                        <struts-config>
                        <form-beans>
                        <form-bean name="searchForm"
                        type="org.apache.struts.validator.DynaValidatorForm">
                        <form-property name="isbn"    type="java.lang.String"/>
                        </form-bean>
                        </form-beans>
                        <global-forwards type="org.apache.struts.action.ActionForward">
                        <forward   name="welcome"                path="/welcome.do"/>
                        <forward   name="searchEntry"            path="/searchEntry.do"/>
                        <forward   name="searchSubmit"           path="/searchSubmit.do"/>
                        </global-forwards>
                        <action-mappings>
                        <action    path="/welcome" forward="/WEB-INF/pages/welcome.htm"/>
                        <action    path="/searchEntry" forward="/WEB-INF/pages/search.jsp"/>
                        <action    path="/searchSubmit"
                        type="org.springframework.web.struts.DelegatingActionProxy" |(1)
                        input="/searchEntry.do"
                        validate="true"
                        name="searchForm">
                        <forward name="success" path="/WEB-INF/pages/detail.jsp"/>
                        <forward name="failure" path="/WEB-INF/pages/search.jsp"/>
                        </action>
                        </action-mappings>
                        <message-resources parameter="ApplicationResources"/>
                        <plug-in className="org.apache.struts.validator.ValidatorPlugIn">
                        <set-property
                        property="pathnames"
                        value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml"/>
                        </plug-in>
                        <plug-in
                        className="org.springframework.web.struts.ContextLoaderPlugIn">
                        <set-property property="contextConfigLocation" value="/WEB-INF/beans.xml"/>
                        </plug-in>
                        </struts-config>
                        

清單 5 是一個(gè)典型的 struts-config.xml 文件,只有一個(gè)小小的差別。它注冊(cè) Spring 代理類的名稱,而不是聲明動(dòng)作的類名,如(1)處所示。DelegatingActionProxy 類使用動(dòng)作映射名稱查找 Spring 環(huán)境中的動(dòng)作。這就是我們使用 ContextLoaderPlugIn 聲明的環(huán)境。

將一個(gè) Struts 動(dòng)作注冊(cè)為一個(gè) Spring bean 是非常直觀的,如清單 6 所示。我利用動(dòng)作映射使用 <bean> 標(biāo)記的名稱屬性(在這個(gè)例子中是 "/searchSubmit")簡(jiǎn)單地創(chuàng)建了一個(gè) bean。這個(gè)動(dòng)作的 JavaBean 屬性像任何 Spring bean 一樣被填充:


清單 6. 在 Spring 環(huán)境中注冊(cè)一個(gè) Struts 動(dòng)作
<?xml version="1.0" encoding="UTF-8"?>
                        <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
                        "http://www.springframework.org/dtd/spring-beans.dtd">
                        <beans>
                        <bean id="bookService" class="ca.nexcel.books.business.BookServiceImpl"/>
                        <bean name="/searchSubmit"
                        class="ca.nexcel.books.actions.SearchSubmit">
                        <property name="bookService">
                        <ref bean="bookService"/>
                        </property>
                        </bean>
                        </beans>
                        





動(dòng)作委托的優(yōu)點(diǎn)

動(dòng)作委托解決方法是這三種方法中最好的。Struts 動(dòng)作不了解 Spring,不對(duì)代碼作任何改變就可用于非 Spring 應(yīng)用程序中。RequestProcessor 的改變不會(huì)影響它,并且它可以利用 Spring AOP 特性的優(yōu)點(diǎn)。

動(dòng)作委托的優(yōu)點(diǎn)不止如此。一旦讓 Spring 控制您的 Struts 動(dòng)作,您就可以使用 Spring 給動(dòng)作補(bǔ)充更強(qiáng)的活力。例如,沒有 Spring 的話,所有的 Struts 動(dòng)作都必須是線程安全的。如果您設(shè)置 <bean> 標(biāo)記的 singleton 屬性為“false”,那么不管用何種方法,您的應(yīng)用程序都將在每一個(gè)請(qǐng)求上有一個(gè)新生成的動(dòng)作對(duì)象。您可能不需要這種特性,但是把它放在您的工具箱中也很好。您也可以利用 Spring 的生命周期方法。例如,當(dāng)實(shí)例化 Struts 動(dòng)作時(shí),<bean> 標(biāo)記的 init-method 屬性被用于運(yùn)行一個(gè)方法。類似地,在從容器中刪除 bean 之前,destroy-method 屬性執(zhí)行一個(gè)方法。這些方法是管理昂貴對(duì)象的好辦法,它們以一種與 Servlet 生命周期相同的方式進(jìn)行管理。





攔截 Struts

前面提到過,通過將 Struts 動(dòng)作委托給 Spring 框架而整合 Struts 和 Spring 的一個(gè)主要的優(yōu)點(diǎn)是:您可以將 Spring 的 AOP 攔截器應(yīng)用于您的 Struts 動(dòng)作。通過將 Spring 攔截器應(yīng)用于 Struts 動(dòng)作,您可以用最小的代價(jià)處理橫切關(guān)注點(diǎn)。

雖然 Spring 提供很多內(nèi)置攔截器,但是我將向您展示如何創(chuàng)建自己的攔截器并把它應(yīng)用于一個(gè) Struts 動(dòng)作。為了使用攔截器,您需要做三件事:

  1. 創(chuàng)建攔截器。
  2. 注冊(cè)攔截器。
  3. 聲明在何處攔截代碼。

這看起來非常簡(jiǎn)單的幾句話卻非常強(qiáng)大。例如,在清單 7 中,我為 Struts 動(dòng)作創(chuàng)建了一個(gè)日志記錄攔截器。 這個(gè)攔截器在每個(gè)方法調(diào)用之前打印一句話:


清單 7. 一個(gè)簡(jiǎn)單的日志記錄攔截器
package ca.nexcel.books.interceptors;
                        import org.springframework.aop.MethodBeforeAdvice;
                        import java.lang.reflect.Method;
                        public class LoggingInterceptor implements MethodBeforeAdvice {
                        public void before(Method method, Object[] objects, Object o) throws Throwable {
                        System.out.println("logging before!");
                        }
                        }
                        

這個(gè)攔截器非常簡(jiǎn)單。before() 方法在攔截點(diǎn)中每個(gè)方法之前運(yùn)行。在本例中,它打印出一句話,其實(shí)它可以做您想做的任何事。下一步就是在 Spring 配置文件中注冊(cè)這個(gè)攔截器,如清單 8 所示:


清單 8. 在 Spring 配置文件中注冊(cè)攔截器
<?xml version="1.0" encoding="UTF-8"?>
                        <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
                        "http://www.springframework.org/dtd/spring-beans.dtd">
                        <beans>
                        <bean id="bookService" class="ca.nexcel.books.business.BookServiceImpl"/>
                        <bean name="/searchSubmit"
                        class="ca.nexcel.books.actions.SearchSubmit">
                        <property name="bookService">
                        <ref bean="bookService"/>
                        </property>
                        </bean>
                        <!--  Interceptors -->
                        <bean name="logger"
                        class="ca.nexcel.books.interceptors.LoggingInterceptor"/> |(1)
                        <!-- AutoProxies -->
                        <bean name="loggingAutoProxy"
                        class="org.springframework.aop.framework.autoproxy.
                        BeanNameAutoProxyCreator"> |(2)
                        <property name="beanNames">
                        <value>/searchSubmit</valuesgt; |(3)
                        </property>
                        <property name="interceptorNames">
                        <list>
                        <value>logger</value> |(4)
                        </list>
                        </property>
                        </bean>
                        </beans>
                        

您可能已經(jīng)注意到了,清單 8 擴(kuò)展了 清單 6 中所示的應(yīng)用程序以包含一個(gè)攔截器。具體細(xì)節(jié)如下:

  • 在 (1) 處,我注冊(cè)了這個(gè)攔截器。
  • 在 (2) 處,我創(chuàng)建了一個(gè) bean 名稱自動(dòng)代理,它描述如何應(yīng)用攔截器。還有其他的方法定義攔截點(diǎn),但是這種方法常見而簡(jiǎn)便。
  • 在 (3) 處,我將 Struts 動(dòng)作注冊(cè)為將被攔截的 bean。如果您想要攔截其他的 Struts 動(dòng)作,則只需要在 "beanNames" 下面創(chuàng)建附加的 <value> 標(biāo)記。
  • 在 (4) 處,當(dāng)攔截發(fā)生時(shí),我執(zhí)行了在 (1) 處創(chuàng)建的攔截器 bean 的名稱。這里列出的所有攔截器都應(yīng)用于“beanNames”。

就是這樣。就像這個(gè)例子所展示的,將您的 Struts 動(dòng)作置于 Spring 框架的控制之下,為處理您的 Struts 應(yīng)用程序提供了一系列全新的選擇。在本例中,使用動(dòng)作委托可以輕松地利用 Spring 攔截器提高 Struts 應(yīng)用程序中的日志記錄能力。





結(jié)束語

在本文中,您已經(jīng)學(xué)習(xí)了將 Struts 動(dòng)作整合到 Spring 框架中的三種竅門。使用 Spring 的 ActionSupport 來整合 Struts(第一種竅門中就是這樣做的)簡(jiǎn)單而快捷,但是會(huì)將 Struts 動(dòng)作與 Spring 框架耦合在一起。如果您需要將應(yīng)用程序移植到一個(gè)不同的框架,則需要重寫代碼。第二種解決方法通過委托 RequestProcessor 巧妙地解開代碼的耦合,但是它的可擴(kuò)展性不強(qiáng),并且當(dāng) Struts 的 RequestProcessor 變成一系列命令時(shí),這種方法就持續(xù)不了很長(zhǎng)時(shí)間。第三種方法是這三種方法中最好的:將 Struts 動(dòng)作委托給 Spring 框架可以使代碼解耦,從而使您可以在您的 Struts 應(yīng)用程序中利用 Spring 的特性(比如日志記錄攔截器)。

 




]]>
Spring 的優(yōu)秀工具類盤點(diǎn)2(轉(zhuǎn))http://www.aygfsteel.com/lanxin1020/archive/2009/04/05/263962.htmllanxin1020lanxin1020Sun, 05 Apr 2009 00:45:00 GMThttp://www.aygfsteel.com/lanxin1020/archive/2009/04/05/263962.htmlhttp://www.aygfsteel.com/lanxin1020/comments/263962.htmlhttp://www.aygfsteel.com/lanxin1020/archive/2009/04/05/263962.html#Feedback0http://www.aygfsteel.com/lanxin1020/comments/commentRss/263962.htmlhttp://www.aygfsteel.com/lanxin1020/services/trackbacks/263962.html特殊字符轉(zhuǎn)義

由于 Web 應(yīng)用程序需要聯(lián)合使用到多種語言,每種語言都包含一些特殊的字符,對(duì)于動(dòng)態(tài)語言或標(biāo)簽式的語言而言,如果需要?jiǎng)討B(tài)構(gòu)造語言的內(nèi)容時(shí),一個(gè)我們經(jīng)常會(huì)碰到的問題就是特殊字符轉(zhuǎn)義的問題。下面是 Web 開發(fā)者最常面對(duì)需要轉(zhuǎn)義的特殊字符類型:

  • HTML 特殊字符;
  • JavaScript 特殊字符;
  • SQL 特殊字符;

如果不對(duì)這些特殊字符進(jìn)行轉(zhuǎn)義處理,則不但可能破壞文檔結(jié)構(gòu),還可以引發(fā)潛在的安全問題。Spring 為 HTML 和 JavaScript 特殊字符提供了轉(zhuǎn)義操作工具類,它們分別是 HtmlUtils 和 JavaScriptUtils。

HTML 特殊字符轉(zhuǎn)義

HTML 中 <,>,& 等字符有特殊含義,它們是 HTML 語言的保留字,因此不能直接使用。使用這些個(gè)字符時(shí),應(yīng)使用它們的轉(zhuǎn)義序列:

  • &:&amp;
  • " :&quot;
  • < :&lt;
  • > :&gt;

由于 HTML 網(wǎng)頁本身就是一個(gè)文本型結(jié)構(gòu)化文檔,如果直接將這些包含了 HTML 特殊字符的內(nèi)容輸出到網(wǎng)頁中,極有可能破壞整個(gè) HTML 文檔的結(jié)構(gòu)。所以,一般情況下需要對(duì)動(dòng)態(tài)數(shù)據(jù)進(jìn)行轉(zhuǎn)義處理,使用轉(zhuǎn)義序列表示 HTML 特殊字符。下面的 JSP 網(wǎng)頁將一些變量動(dòng)態(tài)輸出到 HTML 網(wǎng)頁中:


清單 1. 未進(jìn)行 HTML 特殊字符轉(zhuǎn)義處理網(wǎng)頁
            <%@ page language="java" contentType="text/html; charset=utf-8"%>
            <%!
            String userName = "</td><tr></table>";
            String address = " \" type=\"button";
            %>
            <table border="1">
            <tr>
            <td>姓名:</td><td><%=userName%></td> ①
            </tr>
            <tr>
            <td>年齡:</td><td>28</td>
            </tr>
            </table>
            <input value="<%=address%>"  type="text" /> ②
            

在 ① 和 ② 處,我們未經(jīng)任何轉(zhuǎn)義處理就直接將變量輸出到 HTML 網(wǎng)頁中,由于這些變量可能包含一些特殊的 HTML 的字符,它們將可能破壞整個(gè) HTML 文檔的結(jié)構(gòu)。我們可以從以上 JSP 頁面的一個(gè)具體輸出中了解這一問題:

<table border="1">
            <tr>
            <td>姓名:</td><td></td><tr></table></td>
            ① 破壞了 <table> 的結(jié)構(gòu)
            </tr>
            <tr>
            <td>年齡:</td><td>28</td>
            </tr>
            </table>
            <input value=" " type="button"  type="text" />
            ② 將本來是輸入框組件偷梁換柱為按鈕組件
            

融合動(dòng)態(tài)數(shù)據(jù)后的 HTML 網(wǎng)頁已經(jīng)面目全非,首先 ① 處的 <table> 結(jié)構(gòu)被包含 HTML 特殊字符的 userName 變量截?cái)嗔?,造成其后?<table> 代碼變成無效的內(nèi)容;其次,② 處 <input> 被動(dòng)態(tài)數(shù)據(jù)改換為按鈕類型的組件(type="button")。為了避免這一問題,我們需要事先對(duì)可能破壞 HTML 文檔結(jié)構(gòu)的動(dòng)態(tài)數(shù)據(jù)進(jìn)行轉(zhuǎn)義處理。Spring 為我們提供了一個(gè)簡(jiǎn)單適用的 HTML 特殊字符轉(zhuǎn)義工具類,它就是 HtmlUtils。下面,我們通過一個(gè)簡(jiǎn)單的例子了解 HtmlUtils 的具體用法:


清單 2. HtmpEscapeExample
            package com.baobaotao.escape;
            import org.springframework.web.util.HtmlUtils;
            public class HtmpEscapeExample {
            public static void main(String[] args) {
            String specialStr = "<div id=\"testDiv\">test1;test2</div>";
            String str1 = HtmlUtils.htmlEscape(specialStr); ①轉(zhuǎn)換為HTML轉(zhuǎn)義字符表示
            System.out.println(str1);
            String str2 = HtmlUtils.htmlEscapeDecimal(specialStr); ②轉(zhuǎn)換為數(shù)據(jù)轉(zhuǎn)義表示
            System.out.println(str2);
            String str3 = HtmlUtils.htmlEscapeHex(specialStr); ③轉(zhuǎn)換為十六進(jìn)制數(shù)據(jù)轉(zhuǎn)義表示
            System.out.println(str3);
            ④下面對(duì)轉(zhuǎn)義后字符串進(jìn)行反向操作
            System.out.println(HtmlUtils.htmlUnescape(str1));
            System.out.println(HtmlUtils.htmlUnescape(str2));
            System.out.println(HtmlUtils.htmlUnescape(str3));
            }
            }
            

HTML 不但可以使用通用的轉(zhuǎn)義序列表示 HTML 特殊字符,還可以使用以 # 為前綴的數(shù)字序列表示 HTML 特殊字符,它們?cè)谧罱K的顯示效果上是一樣的。HtmlUtils 提供了三個(gè)轉(zhuǎn)義方法:

方法 說明
static String htmlEscape(String input) 將 HTML 特殊字符轉(zhuǎn)義為 HTML 通用轉(zhuǎn)義序列;
static String htmlEscapeDecimal(String input) 將 HTML 特殊字符轉(zhuǎn)義為帶 # 的十進(jìn)制數(shù)據(jù)轉(zhuǎn)義序列;
static String htmlEscapeHex(String input) 將 HTML 特殊字符轉(zhuǎn)義為帶 # 的十六進(jìn)制數(shù)據(jù)轉(zhuǎn)義序列;

此外,HtmlUtils 還提供了一個(gè)能夠?qū)⒔?jīng)過轉(zhuǎn)義內(nèi)容還原的方法:htmlUnescape(String input),它可以還原以上三種轉(zhuǎn)義序列的內(nèi)容。運(yùn)行以上代碼,您將可以看到以下的輸出:

str1:&lt;div id=&quot;testDiv&quot;&gt;test1;test2&lt;/div&gt;
            str2:<div id="testDiv">test1;test2</div>
            str3:<div id="testDiv">test1;test2</div>
            <div id="testDiv">test1;test2</div>
            <div id="testDiv">test1;test2</div>
            <div id="testDiv">test1;test2</div>
            

您只要使用 HtmlUtils 對(duì)代碼 清單 1 的 userName 和 address 進(jìn)行轉(zhuǎn)義處理,最終輸出的 HTML 頁面就不會(huì)遭受破壞了。

JavaScript 特殊字符轉(zhuǎn)義

JavaScript 中也有一些需要特殊處理的字符,如果直接將它們嵌入 JavaScript 代碼中,JavaScript 程序結(jié)構(gòu)將會(huì)遭受破壞,甚至被嵌入一些惡意的程序。下面列出了需要轉(zhuǎn)義的特殊 JavaScript 字符:

  • ' :\'
  • " :\"
  • \ :\\
  • 走紙換頁: \f
  • 換行:\n
  • 換欄符:\t
  • 回車:\r
  • 回退符:\b
?

我們通過一個(gè)具體例子演示動(dòng)態(tài)變量是如何對(duì) JavaScript 程序進(jìn)行破壞的。假設(shè)我們有一個(gè) JavaScript 數(shù)組變量,其元素值通過一個(gè) Java List 對(duì)象提供,下面是完成這一操作的 JSP 代碼片斷:


清單 3. jsTest.jsp:未對(duì) JavaScript 特殊字符進(jìn)行處理
            <%@ page language="java" contentType="text/html; charset=utf-8"%>
            <jsp:directive.page import="java.util.*"/>
            <%
            List textList = new ArrayList();
            textList.add("\";alert();j=\"");
            %>
            <script>
            var txtList = new Array();
            <% for ( int i = 0 ; i < textList.size() ; i++) { %>
            txtList[<%=i%>] = "<%=textList.get(i)%>";
            ① 未對(duì)可能包含特殊 JavaScript 字符的變量進(jìn)行處理
            <% } %>
            </script>
            

當(dāng)客戶端調(diào)用這個(gè) JSP 頁面后,將得到以下的 HTML 輸出頁面:

<script>
            var txtList = new Array();
            txtList[0] = "";alert();j=""; ① 本來是希望接受一個(gè)字符串,結(jié)果被植入了一段JavaScript代碼
            </script>
            

由于包含 JavaScript 特殊字符的 Java 變量直接合并到 JavaScript 代碼中,我們本來期望 ① 處所示部分是一個(gè)普通的字符串,但結(jié)果變成了一段 JavaScript 代碼,網(wǎng)頁將彈出一個(gè) alert 窗口。想像一下如果粗體部分的字符串是“";while(true)alert();j="”時(shí)會(huì)產(chǎn)生什么后果呢?

因此,如果網(wǎng)頁中的 JavaScript 代碼需要通過拼接 Java 變量動(dòng)態(tài)產(chǎn)生時(shí),一般需要對(duì)變量的內(nèi)容進(jìn)行轉(zhuǎn)義處理,可以通過 Spring 的 JavaScriptUtils 完成這件工作。下面,我們使用 JavaScriptUtils 對(duì)以上代碼進(jìn)行改造:

<%@ page language="java" contentType="text/html; charset=utf-8"%>
            <jsp:directive.page import="java.util.*"/>
            <jsp:directive.page import="org.springframework.web.util.JavaScriptUtils"/>
            <%
            List textList = new ArrayList();
            textList.add("\";alert();j=\"");
            %>
            <script>
            var txtList = new Array();
            <% for ( int i = 0 ; i < textList.size() ; i++) { %>
            ① 在輸出動(dòng)態(tài)內(nèi)容前事先進(jìn)行轉(zhuǎn)義處理
            txtList[<%=i%>] = "<%=JavaScriptUtils.javaScriptEscape(""+textList.get(i))%>";
            <% } %>
            </script>
            

通過轉(zhuǎn)義處理后,這個(gè) JSP 頁面輸出的結(jié)果網(wǎng)頁的 JavaScript 代碼就不會(huì)產(chǎn)生問題了:

<script>
            var txtList = new Array();
            txtList[0] = "\";alert();j=\"";
            ① 粗體部分僅是一個(gè)普通的字符串,而非一段 JavaScript 的語句了
            </script>
            

SQL特殊字符轉(zhuǎn)義

應(yīng)該說,您即使沒有處理 HTML 或 JavaScript 的特殊字符,也不會(huì)帶來災(zāi)難性的后果,但是如果不在動(dòng)態(tài)構(gòu)造 SQL 語句時(shí)對(duì)變量中特殊字符進(jìn)行處理,將可能導(dǎo)致程序漏洞、數(shù)據(jù)盜取、數(shù)據(jù)破壞等嚴(yán)重的安全問題。網(wǎng)絡(luò)中有大量講解 SQL 注入的文章,感興趣的讀者可以搜索相關(guān)的資料深入研究。

雖然 SQL 注入的后果很嚴(yán)重,但是只要對(duì)動(dòng)態(tài)構(gòu)造的 SQL 語句的變量進(jìn)行特殊字符轉(zhuǎn)義處理,就可以避免這一問題的發(fā)生了。來看一個(gè)存在安全漏洞的經(jīng)典例子:

SELECT COUNT(userId)
            FROM t_user
            WHERE userName='"+userName+"' AND password ='"+password+"';
            

以上 SQL 語句根據(jù)返回的結(jié)果數(shù)判斷用戶提供的登錄信息是否正確,如果 userName 變量不經(jīng)過特殊字符轉(zhuǎn)義處理就直接合并到 SQL 語句中,黑客就可以通過將 userName 設(shè)置為 “1' or '1'='1”繞過用戶名/密碼的檢查直接進(jìn)入系統(tǒng)了。

所以除非必要,一般建議通過 PreparedStatement 參數(shù)綁定的方式構(gòu)造動(dòng)態(tài) SQL 語句,因?yàn)檫@種方式可以避免 SQL 注入的潛在安全問題。但是往往很難在應(yīng)用中完全避免通過拼接字符串構(gòu)造動(dòng)態(tài) SQL 語句的方式。為了防止他人使用特殊 SQL 字符破壞 SQL 的語句結(jié)構(gòu)或植入惡意操作,必須在變量拼接到 SQL 語句之前對(duì)其中的特殊字符進(jìn)行轉(zhuǎn)義處理。Spring 并沒有提供相應(yīng)的工具類,您可以通過 jakarta commons lang 通用類包中(spring/lib/jakarta-commons/commons-lang.jar)的 StringEscapeUtils 完成這一工作:


清單 4. SqlEscapeExample
            package com.baobaotao.escape;
            import org.apache.commons.lang.StringEscapeUtils;
            public class SqlEscapeExample {
            public static void main(String[] args) {
            String userName = "1' or '1'='1";
            String password = "123456";
            userName = StringEscapeUtils.escapeSql(userName);
            password = StringEscapeUtils.escapeSql(password);
            String sql = "SELECT COUNT(userId) FROM t_user WHERE userName='"
            + userName + "' AND password ='" + password + "'";
            System.out.println(sql);
            }
            }
            

事實(shí)上,StringEscapeUtils 不但提供了 SQL 特殊字符轉(zhuǎn)義處理的功能,還提供了 HTML、XML、JavaScript、Java 特殊字符的轉(zhuǎn)義和還原的方法。如果您不介意引入 jakarta commons lang 類包,我們更推薦您使用 StringEscapeUtils 工具類完成特殊字符轉(zhuǎn)義處理的工作。





回頁首


方法入?yún)z測(cè)工具類

Web 應(yīng)用在接受表單提交的數(shù)據(jù)后都需要對(duì)其進(jìn)行合法性檢查,如果表單數(shù)據(jù)不合法,請(qǐng)求將被駁回。類似的,當(dāng)我們?cè)诰帉戭惖姆椒〞r(shí),也常常需要對(duì)方法入?yún)⑦M(jìn)行合法性檢查,如果入?yún)⒉环弦螅椒▽⑼ㄟ^拋出異常的方式拒絕后續(xù)處理。舉一個(gè)例子:有一個(gè)根據(jù)文件名獲取輸入流的方法:InputStream getData(String file),為了使方法能夠成功執(zhí)行,必須保證 file 入?yún)⒉荒転?null 或空白字符,否則根本無須進(jìn)行后繼的處理。這時(shí)方法的編寫者通常會(huì)在方法體的最前面編寫一段對(duì)入?yún)⑦M(jìn)行檢測(cè)的代碼,如下所示:

public InputStream getData(String file) {
            if (file == null || file.length() == 0|| file.replaceAll("\\s", "").length() == 0) {
            throw new IllegalArgumentException("file入?yún)⒉皇怯行У奈募刂?);
            }
            …
            }
            

類似以上檢測(cè)方法入?yún)⒌拇a是非常常見,但是在每個(gè)方法中都使用手工編寫檢測(cè)邏輯的方式并不是一個(gè)好主意。閱讀 Spring 源碼,您會(huì)發(fā)現(xiàn) Spring 采用一個(gè) org.springframework.util.Assert 通用類完成這一任務(wù)。

Assert 翻譯為中文為“斷言”,使用過 JUnit 的讀者都熟知這個(gè)概念,它斷定某一個(gè)實(shí)際的運(yùn)行值和預(yù)期想一樣,否則就拋出異常。Spring 對(duì)方法入?yún)⒌臋z測(cè)借用了這個(gè)概念,其提供的 Assert 類擁有眾多按規(guī)則對(duì)方法入?yún)⑦M(jìn)行斷言的方法,可以滿足大部分方法入?yún)z測(cè)的要求。這些斷言方法在入?yún)⒉粷M足要求時(shí)就會(huì)拋出 IllegalArgumentException。下面,我們來認(rèn)識(shí)一下 Assert 類中的常用斷言方法:

斷言方法 說明
notNull(Object object) 當(dāng) object 不為 null 時(shí)拋出異常,notNull(Object object, String message) 方法允許您通過 message 定制異常信息。和 notNull() 方法斷言規(guī)則相反的方法是 isNull(Object object)/isNull(Object object, String message),它要求入?yún)⒁欢ㄊ?null;
isTrue(boolean expression) / isTrue(boolean expression, String message) 當(dāng) expression 不為 true 拋出異常;
notEmpty(Collection collection) / notEmpty(Collection collection, String message) 當(dāng)集合未包含元素時(shí)拋出異常。notEmpty(Map map) / notEmpty(Map map, String message) 和 notEmpty(Object[] array, String message) / notEmpty(Object[] array, String message) 分別對(duì) Map 和 Object[] 類型的入?yún)⑦M(jìn)行判斷;
hasLength(String text) / hasLength(String text, String message) 當(dāng) text 為 null 或長(zhǎng)度為 0 時(shí)拋出異常;
hasText(String text) / hasText(String text, String message) text 不能為 null 且必須至少包含一個(gè)非空格的字符,否則拋出異常;
isInstanceOf(Class clazz, Object obj) / isInstanceOf(Class type, Object obj, String message) 如果 obj 不能被正確造型為 clazz 指定的類將拋出異常;
isAssignable(Class superType, Class subType) / isAssignable(Class superType, Class subType, String message) subType 必須可以按類型匹配于 superType,否則將拋出異常;

使用 Assert 斷言類可以簡(jiǎn)化方法入?yún)z測(cè)的代碼,如 InputStream getData(String file) 在應(yīng)用 Assert 斷言類后,其代碼可以簡(jiǎn)化為以下的形式:

public InputStream getData(String file){
            Assert.hasText(file,"file入?yún)⒉皇怯行У奈募刂?);
            ① 使用 Spring 斷言類進(jìn)行方法入?yún)z測(cè)
            …
            }
            

可見使用 Spring 的 Assert 替代自編碼實(shí)現(xiàn)的入?yún)z測(cè)邏輯后,方法的簡(jiǎn)潔性得到了不少的提高。Assert 不依賴于 Spring 容器,您可以大膽地在自己的應(yīng)用中使用這個(gè)工具類。





回頁首


小結(jié)

本文介紹了一些常用的 Spring 工具類,其中大部分 Spring 工具類不但可以在基于 Spring 的應(yīng)用中使用,還可以在其它的應(yīng)用中使用。

對(duì)于 Web 應(yīng)用來說,由于有很多關(guān)聯(lián)的腳本代碼,如果這些代碼通過拼接字符串的方式動(dòng)態(tài)產(chǎn)生,就需要對(duì)動(dòng)態(tài)內(nèi)容中特殊的字符進(jìn)行轉(zhuǎn)義處理,否則就有可能產(chǎn)生意想不到的后果。Spring 為此提供了 HtmlUtils 和 JavaScriptUtils 工具類,只要將動(dòng)態(tài)內(nèi)容在拼接之前使用工具類進(jìn)行轉(zhuǎn)義處理,就可以避免類似問題的發(fā)生了。如果您不介意引入一個(gè)第三方類包,那么 jakarta commons lang 通用類包中的 StringEscapeUtils 工具類可能更加適合,因?yàn)樗峁┝烁尤娴霓D(zhuǎn)義功能。

最后我們還介紹了 Spring 的 Assert 工具類,Assert 工具類是通用性很強(qiáng)的工具類,它使用面向?qū)ο蟮姆绞浇鉀Q方法入?yún)z測(cè)的問題,您可以在自己的應(yīng)用中使用 Assert 對(duì)方法入?yún)⑦M(jìn)行檢查。




]]>
Spring 的優(yōu)秀工具類盤點(diǎn)1(轉(zhuǎn))http://www.aygfsteel.com/lanxin1020/archive/2009/04/05/263961.htmllanxin1020lanxin1020Sun, 05 Apr 2009 00:44:00 GMThttp://www.aygfsteel.com/lanxin1020/archive/2009/04/05/263961.htmlhttp://www.aygfsteel.com/lanxin1020/comments/263961.htmlhttp://www.aygfsteel.com/lanxin1020/archive/2009/04/05/263961.html#Feedback0http://www.aygfsteel.com/lanxin1020/comments/commentRss/263961.htmlhttp://www.aygfsteel.com/lanxin1020/services/trackbacks/263961.html

Spring 不但提供了一個(gè)功能全面的應(yīng)用開發(fā)框架,本身還擁有眾多可以在程序編寫時(shí)直接使用的工具類,您不但可以在 Spring 應(yīng)用中使用這些工具類,也可以在其它的應(yīng)用中使用,這些工具類中的大部分是可以在脫離 Spring 框架時(shí)使用的。了解 Spring 中有哪些好用的工具類并在程序編寫時(shí)適當(dāng)使用,將有助于提高開發(fā)效率、增強(qiáng)代碼質(zhì)量。

在這個(gè)分為兩部分的文章中,我們將從眾多的 Spring 工具類中遴選出那些好用的工具類介紹給大家。第 1 部分將介紹與文件資源操作和 Web 相關(guān)的工具類。在 第 2 部分 中將介紹特殊字符轉(zhuǎn)義和方法入?yún)z測(cè)工具類。

文件資源操作

文件資源的操作是應(yīng)用程序中常見的功能,如當(dāng)上傳一個(gè)文件后將其保存在特定目錄下,從指定地址加載一個(gè)配置文件等等。我們一般使用 JDK 的 I/O 處理類完成這些操作,但對(duì)于一般的應(yīng)用程序來說,JDK 的這些操作類所提供的方法過于底層,直接使用它們進(jìn)行文件操作不但程序編寫復(fù)雜而且容易產(chǎn)生錯(cuò)誤。相比于 JDK 的 File,Spring 的 Resource 接口(資源概念的描述接口)抽象層面更高且涵蓋面更廣,Spring 提供了許多方便易用的資源操作工具類,它們大大降低資源操作的復(fù)雜度,同時(shí)具有更強(qiáng)的普適性。這些工具類不依賴于 Spring 容器,這意味著您可以在程序中象一般普通類一樣使用它們。

加載文件資源

Spring 定義了一個(gè) org.springframework.core.io.Resource 接口,Resource 接口是為了統(tǒng)一各種類型不同的資源而定義的,Spring 提供了若干 Resource 接口的實(shí)現(xiàn)類,這些實(shí)現(xiàn)類可以輕松地加載不同類型的底層資源,并提供了獲取文件名、URL 地址以及資源內(nèi)容的操作方法。

訪問文件資源

假設(shè)有一個(gè)文件地位于 Web 應(yīng)用的類路徑下,您可以通過以下方式對(duì)這個(gè)文件資源進(jìn)行訪問:

  • 通過 FileSystemResource 以文件系統(tǒng)絕對(duì)路徑的方式進(jìn)行訪問;
  • 通過 ClassPathResource 以類路徑的方式進(jìn)行訪問;
  • 通過 ServletContextResource 以相對(duì)于Web應(yīng)用根目錄的方式進(jìn)行訪問。

相比于通過 JDK 的 File 類訪問文件資源的方式,Spring 的 Resource 實(shí)現(xiàn)類無疑提供了更加靈活的操作方式,您可以根據(jù)情況選擇適合的 Resource 實(shí)現(xiàn)類訪問資源。下面,我們分別通過 FileSystemResource 和 ClassPathResource 訪問同一個(gè)文件資源:


清單 1. FileSourceExample
package com.baobaotao.io;
            import java.io.IOException;
            import java.io.InputStream;
            import org.springframework.core.io.ClassPathResource;
            import org.springframework.core.io.FileSystemResource;
            import org.springframework.core.io.Resource;
            public class FileSourceExample {
            public static void main(String[] args) {
            try {
            String filePath =
            "D:/masterSpring/chapter23/webapp/WEB-INF/classes/conf/file1.txt";
            // ① 使用系統(tǒng)文件路徑方式加載文件
            Resource res1 = new FileSystemResource(filePath);
            // ② 使用類路徑方式加載文件
            Resource res2 = new ClassPathResource("conf/file1.txt");
            InputStream ins1 = res1.getInputStream();
            InputStream ins2 = res2.getInputStream();
            System.out.println("res1:"+res1.getFilename());
            System.out.println("res2:"+res2.getFilename());
            } catch (IOException e) {
            e.printStackTrace();
            }
            }
            }
            

在獲取資源后,您就可以通過 Resource 接口定義的多個(gè)方法訪問文件的數(shù)據(jù)和其它的信息:如您可以通過 getFileName() 獲取文件名,通過 getFile() 獲取資源對(duì)應(yīng)的 File 對(duì)象,通過 getInputStream() 直接獲取文件的輸入流。此外,您還可以通過 createRelative(String relativePath) 在資源相對(duì)地址上創(chuàng)建新的資源。

在 Web 應(yīng)用中,您還可以通過 ServletContextResource 以相對(duì)于 Web 應(yīng)用根目錄的方式訪問文件資源,如下所示:

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
            <jsp:directive.page import="
            org.springframework.web.context.support.ServletContextResource"/>
            <jsp:directive.page import="org.springframework.core.io.Resource"/>
            <%
            // ① 注意文件資源地址以相對(duì)于 Web 應(yīng)用根路徑的方式表示
            Resource res3 = new ServletContextResource(application,
            "/WEB-INF/classes/conf/file1.txt");
            out.print(res3.getFilename());
            %>
            

對(duì)于位于遠(yuǎn)程服務(wù)器(Web 服務(wù)器或 FTP 服務(wù)器)的文件資源,您則可以方便地通過 UrlResource 進(jìn)行訪問。

為了方便訪問不同類型的資源,您必須使用相應(yīng)的 Resource 實(shí)現(xiàn)類,是否可以在不顯式使用 Resource 實(shí)現(xiàn)類的情況下,僅根據(jù)帶特殊前綴的資源地址直接加載文件資源呢?Spring 提供了一個(gè) ResourceUtils 工具類,它支持“classpath:”和“file:”的地址前綴,它能夠從指定的地址加載文件資源,請(qǐng)看下面的例子:


清單 2. ResourceUtilsExample
package com.baobaotao.io;
            import java.io.File;
            import org.springframework.util.ResourceUtils;
            public class ResourceUtilsExample {
            public static void main(String[] args) throws Throwable{
            File clsFile = ResourceUtils.getFile("classpath:conf/file1.txt");
            System.out.println(clsFile.isFile());
            String httpFilePath = "file:D:/masterSpring/chapter23/src/conf/file1.txt";
            File httpFile = ResourceUtils.getFile(httpFilePath);
            System.out.println(httpFile.isFile());
            }
            }
            

ResourceUtils 的 getFile(String resourceLocation) 方法支持帶特殊前綴的資源地址,這樣,我們就可以在不和 Resource 實(shí)現(xiàn)類打交道的情況下使用 Spring 文件資源加載的功能了。

本地化文件資源

本地化文件資源是一組通過本地化標(biāo)識(shí)名進(jìn)行特殊命名的文件,Spring 提供的 LocalizedResourceHelper 允許通過文件資源基名和本地化實(shí)體獲取匹配的本地化文件資源并以 Resource 對(duì)象返回。假設(shè)在類路徑的 i18n 目錄下,擁有一組基名為 message 的本地化文件資源,我們通過以下實(shí)例演示獲取對(duì)應(yīng)中國大陸和美國的本地化文件資源:


清單 3. LocaleResourceTest
package com.baobaotao.io;
            import java.util.Locale;
            import org.springframework.core.io.Resource;
            import org.springframework.core.io.support.LocalizedResourceHelper;
            public class LocaleResourceTest {
            public static void main(String[] args) {
            LocalizedResourceHelper lrHalper = new LocalizedResourceHelper();
            // ① 獲取對(duì)應(yīng)美國的本地化文件資源
            Resource msg_us = lrHalper.findLocalizedResource("i18n/message", ".properties",
            Locale.US);
            // ② 獲取對(duì)應(yīng)中國大陸的本地化文件資源
            Resource msg_cn = lrHalper.findLocalizedResource("i18n/message", ".properties",
            Locale.CHINA);
            System.out.println("fileName(us):"+msg_us.getFilename());
            System.out.println("fileName(cn):"+msg_cn.getFilename());
            }
            }
            

雖然 JDK 的 java.util.ResourceBundle 類也可以通過相似的方式獲取本地化文件資源,但是其返回的是 ResourceBundle 類型的對(duì)象。如果您決定統(tǒng)一使用 Spring 的 Resource 接表征文件資源,那么 LocalizedResourceHelper 就是獲取文件資源的非常適合的幫助類了。

文件操作

在使用各種 Resource 接口的實(shí)現(xiàn)類加載文件資源后,經(jīng)常需要對(duì)文件資源進(jìn)行讀取、拷貝、轉(zhuǎn)存等不同類型的操作。您可以通過 Resource 接口所提供了方法完成這些功能,不過在大多數(shù)情況下,通過 Spring 為 Resource 所配備的工具類完成文件資源的操作將更加方便。

文件內(nèi)容拷貝

第一個(gè)我們要認(rèn)識(shí)的是 FileCopyUtils,它提供了許多一步式的靜態(tài)操作方法,能夠?qū)⑽募?nèi)容拷貝到一個(gè)目標(biāo) byte[]、String 甚至一個(gè)輸出流或輸出文件中。下面的實(shí)例展示了 FileCopyUtils 具體使用方法:


清單 4. FileCopyUtilsExample
package com.baobaotao.io;
            import java.io.ByteArrayOutputStream;
            import java.io.File;
            import java.io.FileReader;
            import java.io.OutputStream;
            import org.springframework.core.io.ClassPathResource;
            import org.springframework.core.io.Resource;
            import org.springframework.util.FileCopyUtils;
            public class FileCopyUtilsExample {
            public static void main(String[] args) throws Throwable {
            Resource res = new ClassPathResource("conf/file1.txt");
            // ① 將文件內(nèi)容拷貝到一個(gè) byte[] 中
            byte[] fileData = FileCopyUtils.copyToByteArray(res.getFile());
            // ② 將文件內(nèi)容拷貝到一個(gè) String 中
            String fileStr = FileCopyUtils.copyToString(new FileReader(res.getFile()));
            // ③ 將文件內(nèi)容拷貝到另一個(gè)目標(biāo)文件
            FileCopyUtils.copy(res.getFile(),
            new File(res.getFile().getParent()+ "/file2.txt"));
            // ④ 將文件內(nèi)容拷貝到一個(gè)輸出流中
            OutputStream os = new ByteArrayOutputStream();
            FileCopyUtils.copy(res.getInputStream(), os);
            }
            }
            

往往我們都通過直接操作 InputStream 讀取文件的內(nèi)容,但是流操作的代碼是比較底層的,代碼的面向?qū)ο笮圆⒉粡?qiáng)。通過 FileCopyUtils 讀取和拷貝文件內(nèi)容易于操作且相當(dāng)直觀。如在 ① 處,我們通過 FileCopyUtils 的 copyToByteArray(File in) 方法就可以直接將文件內(nèi)容讀到一個(gè) byte[] 中;另一個(gè)可用的方法是 copyToByteArray(InputStream in),它將輸入流讀取到一個(gè) byte[] 中。

如果是文本文件,您可能希望將文件內(nèi)容讀取到 String 中,此時(shí)您可以使用 copyToString(Reader in) 方法,如 ② 所示。使用 FileReader 對(duì) File 進(jìn)行封裝,或使用 InputStreamReader 對(duì) InputStream 進(jìn)行封裝就可以了。

FileCopyUtils 還提供了多個(gè)將文件內(nèi)容拷貝到各種目標(biāo)對(duì)象中的方法,這些方法包括:

方法 說明
static void copy(byte[] in, File out) 將 byte[] 拷貝到一個(gè)文件中
static void copy(byte[] in, OutputStream out) 將 byte[] 拷貝到一個(gè)輸出流中
static int copy(File in, File out) 將文件拷貝到另一個(gè)文件中
static int copy(InputStream in, OutputStream out) 將輸入流拷貝到輸出流中
static int copy(Reader in, Writer out) 將 Reader 讀取的內(nèi)容拷貝到 Writer 指向目標(biāo)輸出中
static void copy(String in, Writer out) 將字符串拷貝到一個(gè) Writer 指向的目標(biāo)中

在實(shí)例中,我們雖然使用 Resource 加載文件資源,但 FileCopyUtils 本身和 Resource 沒有任何關(guān)系,您完全可以在基于 JDK I/O API 的程序中使用這個(gè)工具類。

屬性文件操作

我們知道可以通過 java.util.Properties的load(InputStream inStream) 方法從一個(gè)輸入流中加載屬性資源。Spring 提供的 PropertiesLoaderUtils 允許您直接通過基于類路徑的文件地址加載屬性資源,請(qǐng)看下面的例子:

package com.baobaotao.io;
            import java.util.Properties;
            import org.springframework.core.io.support.PropertiesLoaderUtils;
            public class PropertiesLoaderUtilsExample {
            public static void main(String[] args) throws Throwable {
            // ① jdbc.properties 是位于類路徑下的文件
            Properties props = PropertiesLoaderUtils.loadAllProperties("jdbc.properties");
            System.out.println(props.getProperty("jdbc.driverClassName"));
            }
            }
            

一般情況下,應(yīng)用程序的屬性文件都放置在類路徑下,所以 PropertiesLoaderUtils 比之于 Properties#load(InputStream inStream) 方法顯然具有更強(qiáng)的實(shí)用性。此外,PropertiesLoaderUtils 還可以直接從 Resource 對(duì)象中加載屬性資源:

方法 說明
static Properties loadProperties(Resource resource) 從 Resource 中加載屬性
static void fillProperties(Properties props, Resource resource) 將 Resource 中的屬性數(shù)據(jù)添加到一個(gè)已經(jīng)存在的 Properties 對(duì)象中

特殊編碼的資源

當(dāng)您使用 Resource 實(shí)現(xiàn)類加載文件資源時(shí),它默認(rèn)采用操作系統(tǒng)的編碼格式。如果文件資源采用了特殊的編碼格式(如 UTF-8),則在讀取資源內(nèi)容時(shí)必須事先通過 EncodedResource 指定編碼格式,否則將會(huì)產(chǎn)生中文亂碼的問題。


清單 5. EncodedResourceExample
package com.baobaotao.io;
            import org.springframework.core.io.ClassPathResource;
            import org.springframework.core.io.Resource;
            import org.springframework.core.io.support.EncodedResource;
            import org.springframework.util.FileCopyUtils;
            public class EncodedResourceExample {
            public static void main(String[] args) throws Throwable  {
            Resource res = new ClassPathResource("conf/file1.txt");
            // ① 指定文件資源對(duì)應(yīng)的編碼格式(UTF-8)
            EncodedResource encRes = new EncodedResource(res,"UTF-8");
            // ② 這樣才能正確讀取文件的內(nèi)容,而不會(huì)出現(xiàn)亂碼
            String content  = FileCopyUtils.copyToString(encRes.getReader());
            System.out.println(content);
            }
            }
            

EncodedResource 擁有一個(gè) getResource() 方法獲取 Resource,但該方法返回的是通過構(gòu)造函數(shù)傳入的原 Resource 對(duì)象,所以必須通過 EncodedResource#getReader() 獲取應(yīng)用編碼后的 Reader 對(duì)象,然后再通過該 Reader 讀取文件的內(nèi)容。





回頁首


Web 相關(guān)工具類

您幾乎總是使用 Spring 框架開發(fā) Web 的應(yīng)用,Spring 為 Web 應(yīng)用提供了很多有用的工具類,這些工具類可以給您的程序開發(fā)帶來很多便利。在這節(jié)里,我們將逐一介紹這些工具類的使用方法。

操作 Servlet API 的工具類

當(dāng)您在控制器、JSP 頁面中想直接訪問 Spring 容器時(shí),您必須事先獲取 WebApplicationContext 對(duì)象。Spring 容器在啟動(dòng)時(shí)將 WebApplicationContext 保存在 ServletContext的屬性列表中,通過 WebApplicationContextUtils 工具類可以方便地獲取 WebApplicationContext 對(duì)象。

WebApplicationContextUtils

當(dāng) Web 應(yīng)用集成 Spring 容器后,代表 Spring 容器的 WebApplicationContext 對(duì)象將以 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 為鍵存放在 ServletContext 屬性列表中。您當(dāng)然可以直接通過以下語句獲取 WebApplicationContext:

WebApplicationContext wac = (WebApplicationContext)servletContext.
            getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
            

但通過位于 org.springframework.web.context.support 包中的 WebApplicationContextUtils 工具類獲取 WebApplicationContext 更方便:

WebApplicationContext wac =WebApplicationContextUtils.
            getWebApplicationContext(servletContext);
            

當(dāng) ServletContext 屬性列表中不存在 WebApplicationContext 時(shí),getWebApplicationContext() 方法不會(huì)拋出異常,它簡(jiǎn)單地返回 null。如果后續(xù)代碼直接訪問返回的結(jié)果將引發(fā)一個(gè) NullPointerException 異常,而 WebApplicationContextUtils 另一個(gè) getRequiredWebApplicationContext(ServletContext sc) 方法要求 ServletContext 屬性列表中一定要包含一個(gè)有效的 WebApplicationContext 對(duì)象,否則馬上拋出一個(gè) IllegalStateException 異常。我們推薦使用后者,因?yàn)樗芴崆鞍l(fā)現(xiàn)錯(cuò)誤的時(shí)間,強(qiáng)制開發(fā)者搭建好必備的基礎(chǔ)設(shè)施。

WebUtils

位于 org.springframework.web.util 包中的 WebUtils 是一個(gè)非常好用的工具類,它對(duì)很多 Servlet API 提供了易用的代理方法,降低了訪問 Servlet API 的復(fù)雜度,可以將其看成是常用 Servlet API 方法的門面類。

下面這些方法為訪問 HttpServletRequest 和 HttpSession 中的對(duì)象和屬性帶來了方便:

方法 說明
Cookie getCookie(HttpServletRequest request, String name) 獲取 HttpServletRequest 中特定名字的 Cookie 對(duì)象。如果您需要?jiǎng)?chuàng)建 Cookie, Spring 也提供了一個(gè)方便的 CookieGenerator 工具類;
Object getSessionAttribute(HttpServletRequest request, String name) 獲取 HttpSession 特定屬性名的對(duì)象,否則您必須通過request.getHttpSession.getAttribute(name) 完成相同的操作;
Object getRequiredSessionAttribute(HttpServletRequest request, String name) 和上一個(gè)方法類似,只不過強(qiáng)制要求 HttpSession 中擁有指定的屬性,否則拋出異常;
String getSessionId(HttpServletRequest request) 獲取 Session ID 的值;
void exposeRequestAttributes(ServletRequest request, Map attributes) 將 Map 元素添加到 ServletRequest 的屬性列表中,當(dāng)請(qǐng)求被導(dǎo)向(forward)到下一個(gè)處理程序時(shí),這些請(qǐng)求屬性就可以被訪問到了;

此外,WebUtils還提供了一些和ServletContext相關(guān)的方便方法:

方法 說明
String getRealPath(ServletContext servletContext, String path) 獲取相對(duì)路徑對(duì)應(yīng)文件系統(tǒng)的真實(shí)文件路徑;
File getTempDir(ServletContext servletContext) 獲取 ServletContex 對(duì)應(yīng)的臨時(shí)文件地址,它以 File 對(duì)象的形式返回。

下面的片斷演示了使用 WebUtils 從 HttpSession 中獲取屬性對(duì)象的操作:

protected Object formBackingObject(HttpServletRequest request) throws Exception {
            UserSession userSession = (UserSession) WebUtils.getSessionAttribute(request,
            "userSession");
            if (userSession != null) {
            return new AccountForm(this.petStore.getAccount(
            userSession.getAccount().getUsername()));
            } else {
            return new AccountForm();
            }
            }
            

Spring 所提供的過濾器和監(jiān)聽器

Spring 為 Web 應(yīng)用提供了幾個(gè)過濾器和監(jiān)聽器,在適合的時(shí)間使用它們,可以解決一些常見的 Web 應(yīng)用問題。

延遲加載過濾器

Hibernate 允許對(duì)關(guān)聯(lián)對(duì)象、屬性進(jìn)行延遲加載,但是必須保證延遲加載的操作限于同一個(gè) Hibernate Session 范圍之內(nèi)進(jìn)行。如果 Service 層返回一個(gè)啟用了延遲加載功能的領(lǐng)域?qū)ο蠼o Web 層,當(dāng) Web 層訪問到那些需要延遲加載的數(shù)據(jù)時(shí),由于加載領(lǐng)域?qū)ο蟮?Hibernate Session 已經(jīng)關(guān)閉,這些導(dǎo)致延遲加載數(shù)據(jù)的訪問異常。

Spring 為此專門提供了一個(gè) OpenSessionInViewFilter 過濾器,它的主要功能是使每個(gè)請(qǐng)求過程綁定一個(gè) Hibernate Session,即使最初的事務(wù)已經(jīng)完成了,也可以在 Web 層進(jìn)行延遲加載的操作。

OpenSessionInViewFilter 過濾器將 Hibernate Session 綁定到請(qǐng)求線程中,它將自動(dòng)被 Spring 的事務(wù)管理器探測(cè)到。所以 OpenSessionInViewFilter 適用于 Service 層使用HibernateTransactionManager 或 JtaTransactionManager 進(jìn)行事務(wù)管理的環(huán)境,也可以用于非事務(wù)只讀的數(shù)據(jù)操作中。

要啟用這個(gè)過濾器,必須在 web.xml 中對(duì)此進(jìn)行配置:

…
            <filter>
            <filter-name>hibernateFilter</filter-name>
            <filter-class>
            org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
            </filter-class>
            </filter>
            <filter-mapping>
            <filter-name>hibernateFilter</filter-name>
            <url-pattern>*.html</url-pattern>
            </filter-mapping>
            …
            

上面的配置,我們假設(shè)使用 .html 的后綴作為 Web 框架的 URL 匹配模式,如果您使用 Struts 等 Web 框架,可以將其改為對(duì)應(yīng)的“*.do”模型。

中文亂碼過濾器

在您通過表單向服務(wù)器提交數(shù)據(jù)時(shí),一個(gè)經(jīng)典的問題就是中文亂碼問題。雖然我們所有的 JSP 文件和頁面編碼格式都采用 UTF-8,但這個(gè)問題還是會(huì)出現(xiàn)。解決的辦法很簡(jiǎn)單,我們只需要在 web.xml 中配置一個(gè) Spring 的編碼轉(zhuǎn)換過濾器就可以了:

<web-app>
            <!---listener的配置-->
            <filter>
            <filter-name>encodingFilter</filter-name>
            <filter-class>
            org.springframework.web.filter.CharacterEncodingFilter ① Spring 編輯過濾器
            </filter-class>
            <init-param> ② 編碼方式
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
            </init-param>
            <init-param> ③ 強(qiáng)制進(jìn)行編碼轉(zhuǎn)換
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
            </init-param>
            </filter>
            <filter-mapping> ② 過濾器的匹配 URL
            <filter-name>encodingFilter</filter-name>
            <url-pattern>*.html</url-pattern>
            </filter-mapping>
            <!---servlet的配置-->
            </web-app>
            

這樣所有以 .html 為后綴的 URL 請(qǐng)求的數(shù)據(jù)都會(huì)被轉(zhuǎn)碼為 UTF-8 編碼格式,表單中文亂碼的問題就可以解決了。

請(qǐng)求跟蹤日志過濾器

除了以上兩個(gè)常用的過濾器外,還有兩個(gè)在程序調(diào)試時(shí)可能會(huì)用到的請(qǐng)求日志跟蹤過濾器,它們會(huì)將請(qǐng)求的一些重要信息記錄到日志中,方便程序的調(diào)試。這兩個(gè)日志過濾器只有在日志級(jí)別為 DEBUG 時(shí)才會(huì)起作用:

方法 說明
org.springframework.web.filter.ServletContextRequestLoggingFilter 該過濾器將請(qǐng)求的 URI 記錄到 Common 日志中(如通過 Log4J 指定的日志文件);
org.springframework.web.filter.ServletContextRequestLoggingFilter 該過濾器將請(qǐng)求的 URI 記錄到 ServletContext 日志中。

以下是日志過濾器記錄的請(qǐng)求跟蹤日志的片斷:

(JspServlet.java:224) -     JspEngine --> /htmlTest.jsp
            (JspServlet.java:225) - 	     ServletPath: /htmlTest.jsp
            (JspServlet.java:226) - 	        PathInfo: null
            (JspServlet.java:227) - 	        RealPath: D:\masterSpring\chapter23\webapp\htmlTest.jsp
            (JspServlet.java:228) - 	      RequestURI: /baobaotao/htmlTest.jsp
            …
            

通過這個(gè)請(qǐng)求跟蹤日志,程度調(diào)試者可以詳細(xì)地查看到有哪些請(qǐng)求被調(diào)用,請(qǐng)求的參數(shù)是什么,請(qǐng)求是否正確返回等信息。雖然這兩個(gè)請(qǐng)求跟蹤日志過濾器一般在程序調(diào)試時(shí)使用,但是即使程序部署不將其從 web.xml 中移除也不會(huì)有大礙,因?yàn)橹灰獙⑷罩炯?jí)別設(shè)置為 DEBUG 以上級(jí)別,它們就不會(huì)輸出請(qǐng)求跟蹤日志信息了。

轉(zhuǎn)存 Web 應(yīng)用根目錄監(jiān)聽器和 Log4J 監(jiān)聽器

Spring 在 org.springframework.web.util 包中提供了幾個(gè)特殊用途的 Servlet 監(jiān)聽器,正確地使用它們可以完成一些特定需求的功能。比如某些第三方工具支持通過 ${key} 的方式引用系統(tǒng)參數(shù)(即可以通過 System.getProperty() 獲取的屬性),WebAppRootListener 可以將 Web 應(yīng)用根目錄添加到系統(tǒng)參數(shù)中,對(duì)應(yīng)的屬性名可以通過名為“webAppRootKey”的 Servlet 上下文參數(shù)指定,默認(rèn)為“webapp.root”。下面是該監(jiān)聽器的具體的配置:


清單 6. WebAppRootListener 監(jiān)聽器配置
…
            <context-param>
            <param-name>webAppRootKey</param-name>
            <param-value>baobaotao.root</param-value> ① Web 應(yīng)用根目錄以該屬性名添加到系統(tǒng)參數(shù)中
            </context-param>
            …
            ② 負(fù)責(zé)將 Web 應(yīng)用根目錄以 webAppRootKey 上下文參數(shù)指定的屬性名添加到系統(tǒng)參數(shù)中
            <listener>
            <listener-class>
            org.springframework.web.util.WebAppRootListener
            </listener-class>
            </listener>
            …
            

這樣,您就可以在程序中通過 System.getProperty("baobaotao.root") 獲取 Web 應(yīng)用的根目錄了。不過更常見的使用場(chǎng)景是在第三方工具的配置文件中通過${baobaotao.root} 引用 Web 應(yīng)用的根目錄。比如以下的 log4j.properties 配置文件就通過 ${baobaotao.root} 設(shè)置了日志文件的地址:

log4j.rootLogger=INFO,R
            log4j.appender.R=org.apache.log4j.RollingFileAppender
            log4j.appender.R.File=${baobaotao.root}/WEB-INF/logs/log4j.log ① 指定日志文件的地址
            log4j.appender.R.MaxFileSize=100KB
            log4j.appender.R.MaxBackupIndex=1
            log4j.appender.R.layout.ConversionPattern=%d %5p [%t] (%F:%L) - %m%n
            

另一個(gè)專門用于 Log4J 的監(jiān)聽器是 Log4jConfigListener。一般情況下,您必須將 Log4J 日志配置文件以 log4j.properties 為文件名并保存在類路徑下。Log4jConfigListener 允許您通過 log4jConfigLocation Servlet 上下文參數(shù)顯式指定 Log4J 配置文件的地址,如下所示:

① 指定 Log4J 配置文件的地址
            <context-param>
            <param-name>log4jConfigLocation</param-name>
            <param-value>/WEB-INF/log4j.properties</param-value>
            </context-param>
            …
            ② 使用該監(jiān)聽器初始化 Log4J 日志引擎
            <listener>
            <listener-class>
            org.springframework.web.util.Log4jConfigListener
            </listener-class>
            </listener>
            …
            

提示

一些Web應(yīng)用服務(wù)器(如 Tomcat)不會(huì)為不同的Web應(yīng)用使用獨(dú)立的系統(tǒng)參數(shù),也就是說,應(yīng)用服務(wù)器上所有的 Web 應(yīng)用都共享同一個(gè)系統(tǒng)參數(shù)對(duì)象。這時(shí),您必須通過webAppRootKey 上下文參數(shù)為不同Web應(yīng)用指定不同的屬性名:如第一個(gè) Web 應(yīng)用使用 webapp1.root 而第二個(gè) Web 應(yīng)用使用 webapp2.root 等,這樣才不會(huì)發(fā)生后者覆蓋前者的問題。此外,WebAppRootListener 和 Log4jConfigListener 都只能應(yīng)用在 Web 應(yīng)用部署后 WAR 文件會(huì)解包的 Web 應(yīng)用服務(wù)器上。一些 Web 應(yīng)用服務(wù)器不會(huì)將Web 應(yīng)用的 WAR 文件解包,整個(gè) Web 應(yīng)用以一個(gè) WAR 包的方式存在(如 Weblogic),此時(shí)因?yàn)闊o法指定對(duì)應(yīng)文件系統(tǒng)的 Web 應(yīng)用根目錄,使用這兩個(gè)監(jiān)聽器將會(huì)發(fā)生問題。

Log4jConfigListener 監(jiān)聽器包括了 WebAppRootListener 的功能,也就是說,Log4jConfigListener 會(huì)自動(dòng)完成將 Web 應(yīng)用根目錄以 webAppRootKey 上下文參數(shù)指定的屬性名添加到系統(tǒng)參數(shù)中,所以當(dāng)您使用 Log4jConfigListener 后,就沒有必須再使用 WebAppRootListener了。

Introspector 緩存清除監(jiān)聽器

Spring 還提供了一個(gè)名為 org.springframework.web.util.IntrospectorCleanupListener 的監(jiān)聽器。它主要負(fù)責(zé)處理由 JavaBean Introspector 功能而引起的緩存泄露。IntrospectorCleanupListener 監(jiān)聽器在 Web 應(yīng)用關(guān)閉的時(shí)會(huì)負(fù)責(zé)清除 JavaBean Introspector 的緩存,在 web.xml 中注冊(cè)這個(gè)監(jiān)聽器可以保證在 Web 應(yīng)用關(guān)閉的時(shí)候釋放與其相關(guān)的 ClassLoader 的緩存和類引用。如果您使用了 JavaBean Introspector 分析應(yīng)用中的類,Introspector 緩存會(huì)保留這些類的引用,結(jié)果在應(yīng)用關(guān)閉的時(shí)候,這些類以及Web 應(yīng)用相關(guān)的 ClassLoader 不能被垃圾回收。不幸的是,清除 Introspector 的唯一方式是刷新整個(gè)緩存,這是因?yàn)闆]法準(zhǔn)確判斷哪些是屬于本 Web 應(yīng)用的引用對(duì)象,哪些是屬于其它 Web 應(yīng)用的引用對(duì)象。所以刪除被緩存的 Introspection 會(huì)導(dǎo)致將整個(gè) JVM 所有應(yīng)用的 Introspection 都刪掉。需要注意的是,Spring 托管的 Bean 不需要使用這個(gè)監(jiān)聽器,因?yàn)?Spring 的 Introspection 所使用的緩存在分析完一個(gè)類之后會(huì)馬上從 javaBean Introspector 緩存中清除掉,并將緩存保存在應(yīng)用程序特定的 ClassLoader 中,所以它們一般不會(huì)導(dǎo)致內(nèi)存資源泄露。但是一些類庫和框架往往會(huì)產(chǎn)生這個(gè)問題。例如 Struts 和 Quartz 的 Introspector 的內(nèi)存泄漏會(huì)導(dǎo)致整個(gè)的 Web 應(yīng)用的 ClassLoader 不能進(jìn)行垃圾回收。在 Web 應(yīng)用關(guān)閉之后,您還會(huì)看到此應(yīng)用的所有靜態(tài)類引用,這個(gè)錯(cuò)誤當(dāng)然不是由這個(gè)類自身引起的。解決這個(gè)問題的方法很簡(jiǎn)單,您僅需在 web.xml 中配置 IntrospectorCleanupListener 監(jiān)聽器就可以了:

<listener>
            <listener-class>
            org.springframework.web.util.IntrospectorCleanupListener
            </listener-class>
            </listener>
            





回頁首


小結(jié)

本文介紹了一些常用的 Spring 工具類,其中大部分 Spring 工具類不但可以在基于 Spring 的應(yīng)用中使用,還可以在其它的應(yīng)用中使用。使用 JDK 的文件操作類在訪問類路徑相關(guān)、Web 上下文相關(guān)的文件資源時(shí),往往顯得拖泥帶水、拐彎抹角,Spring 的 Resource 實(shí)現(xiàn)類使這些工作變得輕松了許多。

在 Web 應(yīng)用中,有時(shí)你希望直接訪問 Spring 容器,獲取容器中的 Bean,這時(shí)使用 WebApplicationContextUtils 工具類從 ServletContext 中獲取 WebApplicationContext 是非常方便的。WebUtils 為訪問 Servlet API 提供了一套便捷的代理方法,您可以通過 WebUtils 更好的訪問 HttpSession 或 ServletContext 的信息。

Spring 提供了幾個(gè) Servlet 過濾器和監(jiān)聽器,其中 ServletContextRequestLoggingFilter 和 ServletContextRequestLoggingFilter 可以記錄請(qǐng)求訪問的跟蹤日志,你可以在程序調(diào)試時(shí)使用它們獲取請(qǐng)求調(diào)用的詳細(xì)信息。WebAppRootListener 可以將 Web 應(yīng)用的根目錄以特定屬性名添加到系統(tǒng)參數(shù)中,以便第三方工具類通過 ${key} 的方式進(jìn)行訪問。Log4jConfigListener 允許你指定 Log4J 日志配置文件的地址,且可以在配置文件中通過 ${key} 的方式引用 Web 應(yīng)用根目錄,如果你需要在 Web 應(yīng)用相關(guān)的目錄創(chuàng)建日志文件,使用 Log4jConfigListener 可以很容易地達(dá)到這一目標(biāo)。

Web 應(yīng)用的內(nèi)存泄漏是最讓開發(fā)者頭疼的問題,雖然不正確的程序編寫可能是這一問題的根源,也有可能是一些第三方框架的 JavaBean Introspector 緩存得不到清除而導(dǎo)致的,Spring 專門為解決這一問題配備了 IntrospectorCleanupListener 監(jiān)聽器,它只要簡(jiǎn)單在 web.xml 中聲明該監(jiān)聽器就可以了。



]]>
配置Spring數(shù)據(jù)源 (轉(zhuǎn))http://www.aygfsteel.com/lanxin1020/archive/2009/03/25/261822.htmllanxin1020lanxin1020Wed, 25 Mar 2009 03:14:00 GMThttp://www.aygfsteel.com/lanxin1020/archive/2009/03/25/261822.htmlhttp://www.aygfsteel.com/lanxin1020/comments/261822.htmlhttp://www.aygfsteel.com/lanxin1020/archive/2009/03/25/261822.html#Feedback0http://www.aygfsteel.com/lanxin1020/comments/commentRss/261822.htmlhttp://www.aygfsteel.com/lanxin1020/services/trackbacks/261822.html配置Spring數(shù)據(jù)源
配置一個(gè)數(shù)據(jù)源 
    Spring在第三方依賴包中包含了兩個(gè)數(shù)據(jù)源的實(shí)現(xiàn)類包,其一是Apache的DBCP,其二是 C3P0。可以在Spring配置文件中利用這兩者中任何一個(gè)配置數(shù)據(jù)源。
DBCP數(shù)據(jù)源 
    DBCP類包位于 /lib/jakarta-commons/commons-dbcp.jar,DBCP 是一個(gè)依賴 Jakarta commons-pool對(duì)象池機(jī)制的數(shù)據(jù)庫連接池,所以在類路徑下還必須包括/lib/jakarta- commons /commons-pool.jar。下面是使用DBCP配置MySql數(shù)據(jù)源的配置片斷:

xml 代碼
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"        
        destroy-method="close">        
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />       
    <property name="url" value="jdbc:mysql://localhost:3309/sampledb" />       
    <property name="username" value="root" />       
    <property name="password" value="1234" />       
bean>   
BasicDataSource提供了close()方法關(guān)閉數(shù)據(jù)源,所以必須設(shè)定destroy-method=”close”屬性, 以便Spring容器關(guān)閉時(shí),數(shù)據(jù)源能夠正常關(guān)閉。除以上必須的數(shù)據(jù)源屬性外,還有一些常用的屬性: 
    defaultAutoCommit:設(shè)置從數(shù)據(jù)源中返回的連接是否采用自動(dòng)提交機(jī)制,默認(rèn)值為 true; 
    defaultReadOnly:設(shè)置數(shù)據(jù)源是否僅能執(zhí)行只讀操作, 默認(rèn)值為 false; 
    maxActive:最大連接數(shù)據(jù)庫連接數(shù),設(shè)置為0時(shí),表示沒有限制; 
    maxIdle:最大等待連接中的數(shù)量,設(shè)置為0時(shí),表示沒有限制; 
    maxWait:最大等待秒數(shù),單位為毫秒, 超過時(shí)間會(huì)報(bào)出錯(cuò)誤信息; 
    validationQuery:用于驗(yàn)證連接是否成功的查詢SQL語句,SQL語句必須至少要返回一行數(shù)據(jù), 如你可以簡(jiǎn)單地設(shè)置為:“select count(*) from user”; 
    removeAbandoned:是否自我中斷,默認(rèn)是 false ; 
    removeAbandonedTimeout:幾秒后數(shù)據(jù)連接會(huì)自動(dòng)斷開,在removeAbandoned為true,提供該值; 
    logAbandoned:是否記錄中斷事件, 默認(rèn)為 false; 

C3P0數(shù)據(jù)源 
    C3P0 是一個(gè)開放源代碼的JDBC數(shù)據(jù)源實(shí)現(xiàn)項(xiàng)目,它在lib目錄中與Hibernate一起發(fā)布,實(shí)現(xiàn)了JDBC3和JDBC2擴(kuò)展規(guī)范說明的 Connection 和Statement 池。C3P0類包位于/lib/c3p0/c3p0-0.9.0.4.jar。下面是使用C3P0配置一個(gè) oracle數(shù)據(jù)源:

xml 代碼
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"        
        destroy-method="close">       
    <property name="driverClass" value=" oracle.jdbc.driver.OracleDriver "/>       
    <property name="jdbcUrl" value=" jdbc:oracle:thin:@localhost:1521:ora9i "/>       
    <property name="user" value="admin"/>       
    <property name="password" value="1234"/>       
bean>   

ComboPooledDataSource和BasicDataSource一樣提供了一個(gè)用于關(guān)閉數(shù)據(jù)源的close()方法,這樣我們就可以保證Spring容器關(guān)閉時(shí)數(shù)據(jù)源能夠成功釋放。 
    C3P0擁有比DBCP更豐富的配置屬性,通過這些屬性,可以對(duì)數(shù)據(jù)源進(jìn)行各種有效的控制: 
    acquireIncrement:當(dāng)連接池中的連接用完時(shí),C3P0一次性創(chuàng)建新連接的數(shù)目; 
    acquireRetryAttempts:定義在從數(shù)據(jù)庫獲取新連接失敗后重復(fù)嘗試獲取的次數(shù),默認(rèn)為30; 
    acquireRetryDelay:兩次連接中間隔時(shí)間,單位毫秒,默認(rèn)為1000; 
    autoCommitOnClose:連接關(guān)閉時(shí)默認(rèn)將所有未提交的操作回滾。默認(rèn)為false; 
    automaticTestTable: C3P0 將建一張名為Test的空表,并使用其自帶的查詢語句進(jìn)行測(cè)試。如果定義了這個(gè)參數(shù),那么屬性preferredTestQuery將被忽略。你 不能在這張Test表上進(jìn)行任何操作,它將中為C3P0測(cè)試所用,默認(rèn)為null; 
    breakAfterAcquireFailure:獲取連接失敗將會(huì)引起所有等待獲取連接的線程拋出異常。但是數(shù)據(jù)源仍有效保留,并在下次調(diào)   用getConnection()的時(shí)候繼續(xù)嘗試獲取連接。如果設(shè)為true,那么在嘗試獲取連接失敗后該數(shù)據(jù)源將申明已斷開并永久關(guān)閉。默認(rèn)為 false; 
    checkoutTimeout:當(dāng)連接池用完時(shí)客戶端調(diào)用getConnection()后等待獲取新連接的時(shí)間,超時(shí)后將拋出SQLException,如設(shè)為0則無限期等待。單位毫秒,默認(rèn)為0; 
    connectionTesterClassName: 通過實(shí)現(xiàn)ConnectionTester或QueryConnectionTester的類來測(cè)試連接,類名需設(shè)置為全限定名。默認(rèn)為 com.mchange.v2.C3P0.impl.DefaultConnectionTester; 
    idleConnectionTestPeriod:隔多少秒檢查所有連接池中的空閑連接,默認(rèn)為0表示不檢查; 
    initialPoolSize:初始化時(shí)創(chuàng)建的連接數(shù),應(yīng)在minPoolSize與maxPoolSize之間取值。默認(rèn)為3; 
    maxIdleTime:最大空閑時(shí)間,超過空閑時(shí)間的連接將被丟棄。為0或負(fù)數(shù)則永不丟棄。默認(rèn)為0; 
    maxPoolSize:連接池中保留的最大連接數(shù)。默認(rèn)為15; 
    maxStatements:JDBC 的標(biāo)準(zhǔn)參數(shù),用以控制數(shù)據(jù)源內(nèi)加載的PreparedStatement數(shù)量。但由于預(yù)緩存的Statement屬 于單個(gè)Connection而不是整個(gè)連接池。所以設(shè)置這個(gè)參數(shù)需要考慮到多方面的因素,如果maxStatements與 maxStatementsPerConnection均為0,則緩存被關(guān)閉。默認(rèn)為0; 
    maxStatementsPerConnection:連接池內(nèi)單個(gè)連接所擁有的最大緩存Statement數(shù)。默認(rèn)為0; 
    numHelperThreads:C3P0是異步操作的,緩慢的JDBC操作通過幫助進(jìn)程完成。擴(kuò)展這些操作可以有效的提升性能,通過多線程實(shí)現(xiàn)多個(gè)操作同時(shí)被執(zhí)行。默認(rèn)為3; 
    preferredTestQuery:定義所有連接測(cè)試都執(zhí)行的測(cè)試語句。在使用連接測(cè)試的情況下這個(gè)參數(shù)能顯著提高測(cè)試速度。測(cè)試的表必須在初始數(shù)據(jù)源的時(shí)候就存在。默認(rèn)為null; 
    propertyCycle: 用戶修改系統(tǒng)配置參數(shù)執(zhí)行前最多等待的秒數(shù)。默認(rèn)為300; 
    testConnectionOnCheckout:因性能消耗大請(qǐng)只在需要的時(shí)候使用它。如果設(shè)為true那么在每個(gè)connection提交的時(shí)候都 將校驗(yàn)其有效性。建議使用 idleConnectionTestPeriod或automaticTestTable 
等方法來提升連接測(cè)試的性能。默認(rèn)為false; 
    testConnectionOnCheckin:如果設(shè)為true那么在取得連接的同時(shí)將校驗(yàn)連接的有效性。默認(rèn)為false。 

讀配置文件的方式引用屬性: 

<bean id="propertyConfigurer"      
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">       
    <property name="location" value="/WEB-INF/jdbc.properties"/>       
bean>       
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"        
        destroy-method="close">       
    <property name="driverClassName" value="${jdbc.driverClassName}" />       
    <property name="url" value="${jdbc.url}" />       
    <property name="username" value="${jdbc.username}" />       
    <property name="password" value="${jdbc.password}" />       
bean>    

    在jdbc.properties屬性文件中定義屬性值: 
    jdbc.driverClassName= com.mysql.jdbc.Driver 
    jdbc.url= jdbc:mysql://localhost:3309/sampledb 
    jdbc.username=root 
    jdbc.password=1234 
    提示 經(jīng)常有開發(fā)者在${xxx}的前后不小心鍵入一些空格,這些空格字符將和變量合并后作為屬性的值。如: 的屬性配置項(xiàng),在前后都有空格,被解析后,username的值為“ 1234 ”,這將造成最終的錯(cuò)誤,因此需要特別小心。

 獲取JNDI數(shù)據(jù)源 
    如果應(yīng)用配置在高性能的應(yīng)用服務(wù)器(如WebLogic或Websphere等)上,我們可能更希望使用應(yīng)用服務(wù)器本身提供的數(shù)據(jù)源。應(yīng)用服務(wù)器的數(shù)據(jù)源 使用JNDI開放調(diào)用者使用,Spring為此專門提供引用JNDI資源的JndiObjectFactoryBean類。下面是一個(gè)簡(jiǎn)單的配置:

xml 代碼
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">       
    <property name="jndiName" value="java:comp/env/jdbc/bbt"/>       
bean>   

通過jndiName指定引用的JNDI數(shù)據(jù)源名稱。 
    Spring 2.0為獲取J2EE資源提供了一個(gè)jee命名空間,通過jee命名空間,可以有效地簡(jiǎn)化J2EE資源的引用。下面是使用jee命名空間引用JNDI數(shù)據(jù)源的配置: 

xml 代碼
<beans xmlns=http://www.springframework.org/schema/beans     
xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance     
xmlns:jee=http://www.springframework.org/schema/jee     
xsi:schemaLocation="http://www.springframework.org/schema/beans       
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd       
http://www.springframework.org/schema/jee      
http://www.springframework.org/schema/jee /spring-jee-2.0.xsd">       
<jee:jndi-lookup id="dataSource" jndi-name=" java:comp/env/jdbc/bbt"/>       
beans>   

Spring的數(shù)據(jù)源實(shí)現(xiàn)類 
    Spring 本身也提供了一個(gè)簡(jiǎn)單的數(shù)據(jù)源實(shí)現(xiàn)類DriverManagerDataSource ,它位于 org.springframework.jdbc.datasource包中。這個(gè)類實(shí)現(xiàn)了javax.sql.DataSource接口,但 它并沒有提供池化連接的機(jī)制,每次調(diào)用getConnection()獲取新連接時(shí),只是簡(jiǎn)單地創(chuàng)建一個(gè)新的連接。因此,這個(gè)數(shù)據(jù)源類比較適合在單元測(cè)試 或簡(jiǎn)單的獨(dú)立應(yīng)用中使用,因?yàn)樗恍枰~外的依賴類。 
     下面,我們來看一下DriverManagerDataSource的簡(jiǎn)單使用:當(dāng)然,我們也可以通過配置的方式直接使用DriverManagerDataSource。

java 代碼
DriverManagerDataSource ds = new DriverManagerDataSource ();       
ds.setDriverClassName("com.mysql.jdbc.Driver");       
ds.setUrl("jdbc:mysql://localhost:3309/sampledb");       
ds.setUsername("root");       
ds.setPassword("1234");       
Connection actualCon = ds.getConnection();   

 

小結(jié) 

    不管采用何種持久化技術(shù),都需要定義數(shù)據(jù)源。Spring附帶了兩個(gè)數(shù)據(jù)源的實(shí)現(xiàn)類包,你可以自行選擇進(jìn)行定義。在實(shí)際部署時(shí),我們可能會(huì)直接采用應(yīng)用服 務(wù)器本身提供的數(shù)據(jù)源,這時(shí),則可以通過JndiObjectFactoryBean或jee命名空間引用JNDI中的數(shù)據(jù)源。 

DBCP與C3PO配置的區(qū)別:

C3PO :

xml 代碼

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">   
    <property name="driverClass">   
        <value>oracle.jdbc.driver.Oracle</Drivervalue>   
   </ property>   
    <property name="jdbcUrl">              
        <value>jdbc:oracle:thin:@10.10.10.6:1521:DataBaseNamevalue>   
    </ property>   
    <property name="user">   
        <value>testAdmin</value>   
   </ property>   
    <property name="password">   
        <value>123456</value>   
    </property>   
</bean>   

 DBCP:

xml 代碼
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">   
    <property name="driverClassName">   
        <value>oracle.jdbc.driver.OracleDriver</value>   
    </property>   
    <property name="url">              
        <value>jdbc:oracle:thin:@10.10.10.6:1521:DataBaseNamevalue>   
    </property>   
    <property name="username">   
        <value>testAdmin</value>   
    </property>   
    <property name="password">   
        <value>123456</value>   
    </property>   
</bean>



]]>
主站蜘蛛池模板: 柞水县| 长乐市| 凭祥市| 灌南县| 砀山县| 南宫市| 黄龙县| 乐陵市| 乐亭县| 莒南县| 北京市| 柳河县| 海阳市| 师宗县| 绥棱县| 南汇区| 临海市| 光山县| 长葛市| 漳浦县| 凤台县| 芦溪县| 宜兴市| 遵化市| 布尔津县| 麦盖提县| 镇原县| 天门市| 朔州市| 阿尔山市| 洞口县| 清徐县| 崇信县| 芦溪县| 嘉黎县| 延寿县| 永顺县| 石景山区| 孝昌县| 宁晋县| 伊金霍洛旗|