亚洲国产成人a精品不卡在线,亚洲av永久中文无码精品 ,91情国产l精品国产亚洲区 http://m.tkk7.com/lanxin1020/category/38969.htmlzh-cnSat, 11 Apr 2009 04:33:37 GMTSat, 11 Apr 2009 04:33:37 GMT60Introducing to Spring Framework (轉)http://m.tkk7.com/lanxin1020/archive/2009/04/07/264363.htmllanxin1020lanxin1020Tue, 07 Apr 2009 15:30:00 GMThttp://m.tkk7.com/lanxin1020/archive/2009/04/07/264363.htmlhttp://m.tkk7.com/lanxin1020/comments/264363.htmlhttp://m.tkk7.com/lanxin1020/archive/2009/04/07/264363.html#Feedback0http://m.tkk7.com/lanxin1020/comments/commentRss/264363.htmlhttp://m.tkk7.com/lanxin1020/services/trackbacks/264363.htmlIntroducing to Spring Framework

作者:Rod Johnson
譯者:yanger,taowen
校對:taowen

關于Spring Framework,今年夏天你可能已經聽見很多的議論。在本文中,我將試圖解釋Spring能完成什么,和我怎么會認為它能幫助你開發J2EE應用程序。

又來一個framework?

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

我相信Spring是獨特的,有幾個原因:



它關注的領域是其他許多流行的Framework未曾關注的。Spring要提供的是一種管理你的業務對象的方法。

Spring既是全面的又是模塊化的。Spring有分層的體系結構,這意味著你能選擇僅僅使用它任何一個獨立的部分,而它的架構又是內部一致。因此你能從你的學習中,得到最大的價值。例如,你可能選擇僅僅使用Spring來簡單化JDBC的使用,或用來管理所有的業務對象。

它的設計從一開始就是要幫助你編寫易于測試的代碼。Spring是使用測試驅動開發的工程的理想框架。


Spring不會給你的工程添加對其他的框架依賴。Spring也許稱得上是個一站式解決方案,提供了一個典型應用所需要的大部分基礎架構。它還涉及到了其他framework沒有考慮到的內容。

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

2003年1月,Spring已經落戶于SourceForge上了。現在有10個開發人員,其中6個是高度投入的積極分子。

Spring架構上的好處

在我們進入細節之前,讓我們來看看Spring能夠給工程帶來的種種好處:



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

Spring能消除在許多工程中常見的對Singleton的過多使用。根據我的經驗,這是一個很大的問題,它降低了系統的可測試性和面向對象的程度。

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

通過把對接口編程而不是對類編程的代價幾乎減少到沒有,Spring能夠促進養成好的編程習慣。

Spring被設計為讓使用它創建的應用盡可能少的依賴于他的APIs。在Spring應用中的大多數業務對象沒有依賴于Spring。

使用Spring構建的應用程序易于單元測試。

Spring能使EJB的使用成為一個實現選擇,而不是應用架構的必然選擇。你能選擇用POJOs或local EJBs來實現業務接口,卻不會影響調用代碼。

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

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


Spring確實使你能通過最簡單可行的解決辦法來解決你的問題。而這是有有很大價值的。

Spring做了些什么?

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

任務描述

首先,讓我們明確Spring范圍。盡管Spring覆蓋了許多方面,但我們對它應該涉什么,什么不應該涉及有清楚的認識。

Spring的主要目的是使J2EE易用和促進好編程習慣。

Spring不重新輪子。因此,你發現在Spring中沒有logging,沒有連接池,沒有分布式事務調度。所有這些東西均有開源項目提供(例如我們用于處理所有日志輸出的Commons Logging以及Commons DBCP),或由你的應用程序服務器提供了。出于同樣的的原因,我們沒有提供 O/R mapping層。對于這個問題已經有了像Hibernate和JDO這樣的優秀解決方案。

Spring的目標就是讓已有的技術更加易用。例如,盡管我們沒有底層事務協調處理,但我們提供了一個抽象層覆蓋了JTA或任何其他的事務策略。

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

Spring也得益于內在的一致性。所有的開發者都在唱同樣的的贊歌,基礎想法依然與Expert One-on-One J2EE設計與開發中提出的差不多。 并且我們已經能夠在多個領域中使用一些中心的概念,例如Inversion of Control。

Spring在應用服務器之間是可移植的。當然保證可移植性總是一種挑戰,但是我們避免使用任何平臺特有或非標準的東西,并且支持在WebLogic,Tomcat,Resin,JBoss,WebSphere和其他的應用服務器上的用戶。

Inversion of Control 容器

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

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

Bean factories 支持兩種模式的對象:



Singleton:在此模式中,有一個具有特定名稱的共享對象實例,它在查找時被獲取。這是默認的,而且是最為經常使用的。它對于無狀態對象是一種理想的模式。

Prototype:在此模式中,每次獲取將創建一個獨立的對象。例如,這可以被用于讓用戶擁有他們自己的對象。



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



XmlBeanFactory: 可解析簡單直觀的定義類和命名對象屬性的XML結構。 我們提供了一個DTD來使編寫更容易。

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


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

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

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

通過BeanFactory概念,Spring成為一個Inversion of Control的容器。(我不怎么喜歡container這個詞,因為它使人聯想到重量級容器,如EJB容器。Spring的BeanFactory是一個可通過一行代碼創建的容器,并且不需要特殊的部署步驟。)

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

IoC有幾個重要的好處,例如:



因為組件不需要在運行時間尋找合作者,所以他們可以更簡單的編寫和維護。在Spring版的IoC里,組件通過暴露JavaBean的setter方法表達他們依賴的其他組件。這相當于EJB通過JNDI來查找,EJB查找需要開發人員編寫代碼。

同樣原因,應用代碼更容易測試。JavaBean屬性是簡單的,屬于Java核心的,并且是容易測試的:僅編寫一個自包含的Junit測試方法用來創建對象和設置相關屬性即可。

一個好的IoC實現保留了強類型。如果你需要使用一個通用的factory來尋找合作者,你必須通過類型轉換將返回結果轉變為想要的類型。這不是一個大不了的問題,但是不雅觀。使用IoC,你在你的代碼中表達了強類型依賴,框架將負責類型轉換。這意味著在框架配置應用時,類型不匹配將導致錯誤;在你的代碼中,你無需擔心類型轉換異常。

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


最后應該強調的是,IoC 不同于傳統的容器的體系結構,如EJB,應用代碼最小程度地依靠于容器。這意味著你的業務對象可以潛在的被運行在不同的IoC 框架上——或者在任何框架之外——不需要任何代碼的改動。

以我和其他Spring用戶的經驗來說,再怎么強調IoC給應用程序代碼帶來的好處也不為過。

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

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

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

對應用開發人員,最重要的是,一個或多個BeanFactory提供了一個定義明確的業務對象層。這類似于local session bean層,但比它更簡單。與EJBs不同,在這個層中的對象可能是相關的,并且他們的關系被擁有它們的factory管理。有一個定義明確的業務對象層對于成功的體系結構是非常重要的。

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



信息查找,支持著國際化

事件機制,允許發布應用對象以及可選的注冊以接收到事件

可移植的文件和資源訪問


XmlBeanFactory 例子

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

讓我們來看一個簡單的例子,它配置了三個應用程序對象,之間的關系在J2EE應用中常常能夠看到:



J2EE DataSource

使用DataSource的DAO

在處理過程中使用DAO的業務對象


在下面的例子中,我們使用一個來自Jakarta Commons DBCP項目的BasicDataSource。這個class(和其他許多已有的 class一樣)可以簡單地被應用在Spring bean factory中,只要它提供了JavaBean格式的配置。需要在shutdown時被調用的Close方法可通過Spring的"destroy-method"屬性被注冊,以避免BasicDataSource需要實現任何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使用標準的 JavaBean屬性編輯器機制來把String轉換為其他的類型。

現在,我們定義DAO,它有一個對DataSource的bean引用。Bean間關系通過<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>

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

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

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

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


使用這個特性,Spring會找出exampleBusinessObject的dataSource屬性應該被設置為在當前BeanFactory中找到的DataSource實現。在當前的BeanFactory中,如果所需要類型的bean不存在或多于一個,將產生一個錯誤。我們依然要設置 exampleParam屬性,因為它不是一個引用。

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

把管理從Java代碼中移出來比硬編碼有很大的好處,因為這樣可以只改變XML文件而無需改變一行Java代碼。例如,我們可以簡單地改變 myDataSource的bean定義引用不同的bean class以使用別的連接池,或者一個用于測試的數據源。 XML節變成另一種,我們可以用 Spring的JNDI location FactoryBean從應用服務器獲取一個數據源。

現在讓我們來看看例子中業務對象的java 代碼。注意下面列出的代碼中沒有對Spring的依賴。不像EJB容器,Spring BeanFactory不具有侵入性:在應用對象里面你通常不需要對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,它們對應于bean定義文檔中的XML引用。這些將在對象被使用之前由Spring調用。

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

注意在業務接口中沒有聲明將會一起使用的JavaBean屬性。 他們是一個實現細節。我們可以“插入”帶有不同bean屬性的不同的實現類而不影響連接著的對象或者調用的代碼。

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

Bean factories 和application contexts 通常和J2EE server定義的一個范圍相關聯,例如:



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

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

EJB:Spring 為EJB提供方便的超類,它們簡化了EJB的創建并且提供了一個從EJB Jar 文件中的XML文檔載入的BeanFactory。


這些J2EE規范提供的hook通常避免了使用Singleton來創造一個bean factory。

然而,如果我們愿意的話可以用代碼創建一個BeanFactory,雖然是沒有什么意義的。例如,我們在以下三行代碼中可以創建bean factory并且得到一個業務對象的引用:

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



這段代碼將能工作在一個應用服務器之外:甚至不依賴J2EE,因為Spring 的IoC容器是純java的。

JDBC 抽象和數據存儲異常層次

數據訪問是Spring 的另一個閃光點。

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



需要冗長的錯誤處理代碼來確保ResultSets,Statements以及(最重要的)Connections在使用后關閉。這意味著對JDBC的正確使用可以快速地導致大量的代碼量。它還是一個常見的錯誤來源。Connection leak可以在有負載的情況下快速宕掉應用程序。

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

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



提供API,把冗長乏味和容易出錯的異常處理從程序代碼移到框架之中。框架處理所有的異常處理;程序代碼能夠集中精力于編寫恰當的SQL和提取結果上。

為你本要處理SQLException程序代碼提供有意義的異常層次。當Spring第一次從數據源取得一個連接時,它檢查元數據以確定數據庫。它使用這些信息把SQLException映射為自己從org.springframework.dao.DataAccessException派生下來的類層次中正確的異常。因而你的代碼可以與有意義的異常打交道,并且不需要為私有的SQLState或者錯誤碼擔心。Spring的數據訪問異常不是JDBC特有的,因而你的DAO并不一定會因為它們可能拋出的異常而綁死在JDBC上。

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

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

例如,Spring的JdbcTemplate對象能夠用于執行SQL查詢并且在如下的列表中保存結果:

代碼:
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));
      }
   });


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

JdbcTemplate提供許多支持不同情景包括prepared statements和批量更新的方法。Spring的JDBC抽象有比起標準JDBC來說性能損失非常小,甚至在當應用中需要的結果集數量很大的時候。

在org.springframework.jdbc.object包中是對JDBC的更高層次的抽象。這是建立在核心的JDBC回調功能基礎紙上的,但是提供了一個能夠對RDBMS操作——無論是查詢,更新或者是存儲過程——使用Java對象來建模的API。這個API部分是受到JDO查詢API的影響,我發現它直觀而且非常有用。

一個用于返回User對象的查詢對象可能是這樣的:

代碼:

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);
   }
}


這個類可以在下面用上:
代碼:

User user = userQuery.findUser(25);


這樣的對象經常可以用作DAO的inner class。它們是線程安全的,除非子類作了一些超出常規的事情。

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

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

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

注意如果我們確實需要從unchecked數據訪問異常中恢復,我們仍然可以這么做。我們可以編寫代碼僅僅處理可恢復的情況。例如,如果我們認為只有optimistic locking violation是可恢復的,我們可以在Spring的DAO中如下這么寫:

代碼:

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


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

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


第一個例子的潛在缺陷是——編譯器不能強制處理可能的可恢復的異常——這對于第二個也是如此。因為我們被強制捕捉base exception (DataAccessException),編譯器不會強制對子類(OptimisticLockingFailureException)的檢查。因而編譯器可能強制我們編寫處理不可恢復問題的代碼,但是對于強制我們處理可恢復的問題并未有任何幫助。

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

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




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

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

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

無論使用的是什么持久化技術,你都會發現容易實現DAO模式,讓業務代碼無需依賴于任何特定的數據訪問API。


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

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

當然你經常需要使用O/R mapping,而不是使用關系數據訪問。你總體的應用程序框架也必須支持它。因而提供了對Hibernate 2.x和 JDO的集成支持。它的數據訪問架構使得它能和任何底層的數據訪問技術集成。Spring和Hibernate集成得尤其好。

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




Session 管理 Spring提供有效率的,簡單的以并且是安全的處理Hibernate Session。使用Hibernate的相關代碼為了效率和恰當的事務處理一般需要使用相同的Hibernate “Session”對象。Spring讓它容易透明地創建和綁定Session到當前的線程,要么使用聲明式,AOP的method interceptor方法,要么在Java代碼層面使用顯式的,“template”包裝類。因而 Spring解決了在Hibernate論壇上經常出現的用法問題。

資源管理 Spring的應用程序context能夠處理Hiberante SessionFactories的位置和配置,JDBC數據源和其他相關資源。這使得這些值易于管理和改變。

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

如上描述的異常包裝 Spring能夠包裝Hibernate異常,把它們從私有的,checked異常轉換為一套抽象的運行時異常。這使得你能夠僅僅在恰當的層面處理大部分不可恢復的持久化異常,而不影響樣板catch/throw,和異常聲明。你仍然能夠在任何你需要的地方捕捉和處理異常。記住 JDBC異常(包括DB特有的方言)也被轉換到相同的層次中,意味著你能在一致的編程模型中對JDBC執行相同的操作。

為了避免和廠商綁定 Hibernate是強大的,靈活的,開放源代碼并且免費,但是它仍然使用私有的API。給出了一些選擇,使用標準或者抽象API實現主要的程序功能通常是你想要的,當你需要因為功能,性能,或者其他考慮要轉換到使用其他實現時。

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

Spring提供了它自己對事務管理的抽象。Spring提供了這些:



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

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


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

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

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

最近在應用AOP來解決企業關注點方面大家有了很大的興趣,例如事務管理,這些都是EJB所要解決的。

Spring的AOP支持的首要目標是要給POJOs提供J2EE服務。這類似于JBoss 4的目標,Spring AOP由它能夠在應用服務器之間移植的優勢,因而沒有綁死在廠商身上的風險。它既可以在web或者EJB容器中使用,也能夠在WebLogic,Tomcat,JBoss,Resin, Jetty,Orion和許多其他應用服務器和web容器上使用。

Spring AOP支持method interception。所支持關鍵的AOP概念包括:



Interception:自定義行為能夠在對接口和類的調用之前和之后插入。這類似于AspectJ術語中類似的“around advice”。

Introduction:指定advice會導致對象實現額外的接口。這混亂了繼承。

靜態和動態的pointcuts:在interception發生的程序執行處指定points。靜態pointcuts concern函數簽名;動態 pointcuts也可以在point被求值的地方考慮函數的參數。Pointcuts獨立interceptors單獨定義,使得標準 interceptor可以應用于不同應用程序和代碼上下文。


Spring既支持有狀態(一個advised對象一個實例)也支持無狀態的interceptors(所有advice使用一個實例)。

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

Spring使用動態代理實現AOP(其中存在一個接口)或者在運行時使用CGLIB生成字節碼(這使得能夠代理類)。兩種方法都能夠在任何應用服務器中使用。

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

在TheServerSide和其他地方有一個正在進行但是不是那么引人注目的爭論,就是這種interception是不是“true AOP”。我倒不怎么在意它叫什么;僅僅需要知道它是否在實踐中有用就好了。我也樂于稱它為“declarative middleware”(聲明式中間件)。把 Spring AOP認做簡單,輕量級的無狀態beans的替代物,這樣就不需要monolithic EJB容器了,而這些僅僅是讓你能夠構建有你需要的服務的容器。我不推薦advising任何一個POJO,對local SLSBs的類比有助于你理解推薦的粒度。(然而,與EJB不同的是,在恰當但是少見的情況下,你可以自由地把Spring的AOP應用到粒度更好的對象上。)

因為Spring在實例上advises 對象,而不是在class loader層面上,使用有不同advice的同一個類的多個實例是可能的,或者與advised實例一道使用unadvised 實例。

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

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



事務管理能夠應用于任何POJO。我們推薦業務對象實現接口,但是這只是一個好的編程習慣的問題,而不是由框架強制的。

通過使用Spring的事務API能夠在事務性POJO中實現編程回調。我們為此提供靜態的方法,使用ThreadLoacal變量,因而你不需要傳播諸如EJBContext這樣的context對象來確保回滾。

你可以聲明式地定義“回滾規則”。EJB不會在未捕捉程序異常的時候自動回滾(僅僅在unchecked exceptions和其他 Throwables的時候),應用程序開發者經常需要在任何異常發生時回滾。Spring事務管理讓你能夠聲明式地指定什么異常什么子類能夠導致自動回滾。缺省的行為和EJB是一致的,但是你能夠在checked和unchecked異常時自動回滾。這個在最少化自編程回調代碼方面有很大好處,而回調依賴于Spring的事務API(因為EJB的編程回調時在EJBContext中完成的)。

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


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



自定義的security interception,當安全檢查的復雜度超出了J2EE安全架構的能力的時候

在開發中使用的調試和profiling aspects

發送email通知管理員用戶不尋常的舉動的Interceptors


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

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

下面的XML片斷展示了如何定義一個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 ()方法返回時依賴的是代理接口。(多個代理方法是被支持的。)ProxyFactoryBean的“interceptorNames”屬性需要一個字符串列表。(因為如果代理是一個“prototype”而不是singleton,有狀態interceptors可能需要創建新的實例,所以必須使用 Bean的名字而不是bean的引用。)列表中的名字可以是interceptor或者pointcuts(interceptors和有關它們合適被使用的信息)。列表中的“target”值自動創建一個“invoker interceptor”封裝target對象。實現代理接口的是在 factory中的bean的名字。這個例子中的myTest可以和其他bean factory中的bean一樣使用。例如,其他對象可以使用< ref>元素引用它而且這些引用是由Spring IoC設置的。

還可以不用BeanFactory,編程構建AOP代理,雖然這個很少用得上:

代碼:

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能力方面的直接競爭者是Jon Tirsen的Nanning Aspects(http://nanning.codehaus.org)。

我覺得AOP作為EJB的替代無提供企業服務這個用法方面的進步是重要的。隨著時間,這將成為Spring很重要的關注點。
MVC web 框架

Spring包括一個強大而且高度可配置的MVC web 框架。

Spring的MVC model類似于Struts。在多線程服務對象這點上,Spring的Controller類似于Struts Action,只有一個實例處理所有客戶的請求。然而,我們相信Spring的MVC比起Struts有很多優點,例如:



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

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

Spring MVC是真正的view無關的。你不會被強制使用JSP,如果你不想那么做的話。你可以使用Velocity,XSLT或其他view技術。如果你想要使用自定義的view機制——例如,你自己的模板語言——你可以簡單實現Spring的View接口并且把它集成進來。

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

Web層變成了業務對象層之上的薄薄一層。這鼓勵了好的習慣。Struts和其他專門的web框架讓你去實現你自己的業務對象;Spring提供了你應用程序所有層的集成。


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

下面的例子展示了一個簡單的Spring Controller如何能夠訪問定義在應用程序context中的業務對象。這個controller在它的handleRequest()方法中執行了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是一個GLUE web services代理,由 Spring FActoryBean返回。然而,Spring把controller從底層web service庫中分離出來。接口可以使用普通的 Java對象,test stub,mock對象或者如下面要討論的EJB代理實現。這個contorller不包括資源查找;除了支持它的web交互的必要代碼之外沒有別的什么了。

Spring還提供了數據綁定,forms,wizards和更復雜的工作流的支持。

對Spring MVC 框架的優秀簡介是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的分層架構使得你能夠使用Spring的其他部分而不用MVC層。我們有使用Spring做中間層管理和數據訪問,但是在web層使用Struts,WebWork或者Tapestry的用戶。
實現EJB

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

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

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

代碼:

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是一個接口,它的實現類——以及任何它需要的配置,注入基本的屬性和更多的合作者——在XML bean factory 定義中隱藏。

我們通過在ejb-jar.xmldeployment descriptor中名為ejb/BeanFactoryPath的環境變量定義告訴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 文件將會從classpath裝載:在本例中,是EJB Jar文件的根目錄。每個EJB都能指定自己的XML文檔,因而這個機制能在每個EJB Jar文件中使用多次。

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

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



使用EJB的典型代碼依賴Service Locator或者Business Delegate singletons,使得測試難于進行。

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

實現Business Delegate模式通常導致顯著的代碼重復,其中我們必須編寫大量僅僅是調用EJB同等方法的方法。


基于這些和其他原因,傳統的EJB訪問,如在Sun Adventure Builder和OTN J2EE Virtual Shopping Mall中展示的那樣,會降低生產率并且帶來顯著的復雜度。

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

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

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

代碼:

private MyComponent myComponent;

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


我們隨后在任何業務方法中使用這個實例變量。

Spring自動完稱剩下的工作,通過像這樣的XML bean定義。LocalStatelessSessionProxyFactoryBean是一個可以用于任何EJB的通用factory bean。它創建的對象能夠自動被Spring轉型為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>


在幕后有許多魔法般的事情發生,Spring AOP framework的殷勤,雖然不強迫你使用AOP的概念享受這些結果。 “myComponent”bean定義為EJB創建一個代理,它實現了業務方法的接口。EJB local home在啟動的時候被緩存,因而只需要一次JNDI查找。每次EJB被調用的時候,代理調用local EJB中的create()方法并且調用EJB中對應的業務方法。

myController bean定義為這個代理設置controller類的myController屬性。

這個EJB訪問機制極大簡化了應用程序的代碼:



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

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


在實際程序中的性能測試和經驗標明這種方法(包括對目標EJB的反射調用)的性能影響是很小的,在典型的應用中檢測不出。記住無論如何我們都不希望使用fine-grained的EJB調用,因為會有有關應用服務器上的EJB的底層架構方面的代價。

我們可以把相同方法應用于遠程EJB,通過類似 org.springframework.ejb.access.SimpleRemoteStatelessSessionProxyFactoryBean factory bean 的方法。然而我們無法隱藏遠程EJB的業務方法接口中的RemoteException。
測試

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

Spring自身有一個極好的單元測試包。我們的1.0 M1的單元測試覆蓋率是75%,而且我們希望在1.0 RC1的時候能夠達到80%的單元測試覆蓋率。我們發現在這個項目中測試優先的開發帶來的好處是實實在在的。例如,它使得作為國際化分布式開發團隊的工作極端有效率,而且用戶評論 CVS snapshots趨向于穩定和使用安全。

因為以下理由,我們相信用Spring構建的應用程序是非常易于測試的:



IoC推動了單元測試

應用程序不包括直接使用注入JNDI的J2EE服務的plumbing code,這些代碼一般讓測試難于進行

Spring bean factories和contexts能夠在容器外設置


在容器外可以設置Spring bean factory的能力提供了對開發過程有趣的可選項。在幾個使用Spring的web應用中,工作是從定義業務接口和在web容器外集成測試開始的。在業務功能已經足夠完整之后,web接口不過是添加在其上的薄薄一層。
誰在使用Spring

雖然相對來說Spring還是一個新的項目,但是我們已經有了一個令人印象深刻并且不斷增長的用戶群。它們已經有許多產品使用著Spring。用戶包括一個主要的全球投資銀行(做大型項目的),一些知名的網絡公司,幾個web開發顧問機構,衛生保健公司,以及學院機構。

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

在今年晚些時候我們主要要做的是讓Spring發布release 1.0。然而,我們還有一些更長遠的目標。

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

1.0之后,一些可能的改進地方包括:



通過對我們的JDBC和事務支持的一個相當抽象來支持JMS

支持bean factories的動態重配置

提供web services的能力

IDE和其他工具支持


作為一個敏捷項目,我們主要是受到用戶需求的驅動。因而我們不會開發沒有一個用戶需要的特性,并且我們會仔細傾聽來自用戶群的聲音。
總結

Spring是一個解決了許多在J2EE開發中常見的問題的強大框架。

Spring提供了管理業務對象的一致方法并且鼓勵了注入對接口編程而不是對類編程的良好習慣。Spring的架構基礎是基于使用JavaBean屬性的 Inversion of Control容器。然而,這僅僅是完整圖景中的一部分:Spring在使用IoC容器作為構建完關注所有架構層的完整解決方案方面是獨一無二的。

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

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

Spring提供了一個用標準Java語言編寫的AOP框架,它給POJOs提供了聲明式的事務管理和其他企業事務——如果你需要——還能實現你自己的aspects。這個框架足夠強大,使得應用程序能夠拋開EJB的復雜性,同時享受著和傳統EJB相關的關鍵服務。

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

參見以下資源獲得關于Spring的更多信息:



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

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

在Sourceforge上的論壇和下載

Spring用戶和Spring開發者的郵件列表


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

Rod Johnson 作為Java開發者和架構師已經有了7年的經驗了并且在J2EE平臺出現之初就在其上進行開發了。他是《Expert One- on-One J2EE Design and Development》(Wrox,2002)的作者并且貢獻了其他好幾本關于J2EE的書。他當前正在為Wiley撰寫另外一本有關J2EE架構的書。Rod在兩個Java標準委員會服務并且經常師大會發言人。現在他在UK做一個咨詢顧問。

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


lanxin1020 2009-04-07 23:06 發表評論
]]>
利用Java的反射與代理實現AOP(轉)http://m.tkk7.com/lanxin1020/archive/2009/04/06/264075.htmllanxin1020lanxin1020Mon, 06 Apr 2009 03:50:00 GMThttp://m.tkk7.com/lanxin1020/archive/2009/04/06/264075.htmlhttp://m.tkk7.com/lanxin1020/comments/264075.htmlhttp://m.tkk7.com/lanxin1020/archive/2009/04/06/264075.html#Feedback0http://m.tkk7.com/lanxin1020/comments/commentRss/264075.htmlhttp://m.tkk7.com/lanxin1020/services/trackbacks/264075.html利用Java的反射與代理實現AOP

 

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

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

package org.amigo.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 日志攔截器,用來進行日志處理. * @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; /** * 構造函數,設置代理對象. */ public LogInterceptor(Object delegate){ this.delegate = delegate; } /** * 方法的調用. * @param proxy * @param method 對應的方法 * @param args 方法的參信息 * @return 返回操作結果對象 * @throws Throwable */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; try { System.out.println("before process" + method); //調用代理對象delegate的method方法,并將args作為參數信息傳入 result = method.invoke(delegate, args); System.out.println("after process" + method); } catch (Exception e){ System.err.println("發生異常:" + e.toString()); } return result; } /** * 測試方法. * @param args * @throws Exception */ public static void main(String[] args) throws Exception { BusinessObj obj = new BusinessObjImpl(); //創建一個日志攔截器 LogInterceptor interceptor = new LogInterceptor(obj); //通過Proxy類的newProxyInstance(...)方法來獲得動態的代理對象 BusinessObj proxy = (BusinessObj) Proxy.newProxyInstance( BusinessObjImpl.class.getClassLoader(), BusinessObjImpl.class.getInterfaces(), interceptor); //執行動態代理對象的業務邏輯方法 proxy.process(); } }
 
此時還需要對BusinessObj的實現類BusinessObjImpl類進行修改,去掉其日志記錄等內容,修改后的文件如下:

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

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 { //需要代理的目標對象 private Object target; //方法前置顧問 Advisor beforeAdvisor; //方法后置顧問 Advisor afterAdvisor; /** * 設置代理目標對象,并生成動態代理對象. * @param target 代理目標對象 * @return 返回動態代理對象 */ public Object setObject(Object target) { //設置代理目標對象 this.target = target; //根據代理目標對象生成動態代理對象 Object obj = Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); return obj; } /** * 若定義了前置處理,則在方法執行前執行前置處理, * 若定義了后置處理,則在方法調用后調用后置處理. * @param proxy 代理對象 * @param method 調用的業務方法 * @param args 方法的參數 * @return 返回結果信息 * @throws Throwable */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //進行業務方法的前置處理 if (beforeAdvisor != null) { beforeAdvisor.doInAdvisor(proxy, method, args); } //執行業務方法 Object result = method.invoke(target, args); //進行業務方法的后置處理 if (afterAdvisor != null) { afterAdvisor.doInAdvisor(proxy, method, args); } //返回結果對象 return result; } /** * 設置方法的前置顧問. * @param advisor 方法的前置顧問 */ public void setBeforeAdvisor(Advisor advisor) { this.beforeAdvisor = advisor; } /** * 設置方法的后置顧問. * @param advisor 方法的后置顧問 */ public void setAfterAdvisor(Advisor advisor) { this.afterAdvisor = advisor; } }
    在上類中,前置和后置顧問對象都繼承Advisor接口,接下來讓我們來看看顧問接口類的內容,該類定義了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都實現了Advisor接口,分別為方法的前置顧問和后置顧問類。 BeforeMethodAdvisor.java文件(前置顧問類)的內容如下所示: 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 { /** * 在方法執行前所進行的操作. */ public void doInAdvisor(Object proxy, Method method, Object[] args) { System.out.println("before process " + method); } } AfterMethodAdvisor.java文件(后置顧問類)的內容如下所示: 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 { /** * 在方法執行后所進行的操作. */ public void doInAdvisor(Object proxy, Method method, Object[] args) { System.out.println("after process " + method); } }
這兩個類分別在方法執行前和方法執行后做一些額外的操作。
       對于在配置文件中對某個bean配置前置或后置處理器,我們可以在bean中增加兩個屬性aop和aopType,aop的值為對應的前置顧問類或后置顧問類的名稱,aopType用于指明該顧問類為前置還是后置顧問,為before時表示為前置處理器,為after時表示為后置處理器,這時候我們需要修改上一篇文章中的BeanFactory.java這個文件,添加其對aop和aopType的處理,修改后的該文件內容如下:

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; //創建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屬性時,需進行攔截操作 if (aop != null && aopType != null) { //根據aop字符串獲取對應的類 Class advisorCls = Class.forName(aop.getText()); //創建該類的對象 Advisor advisor = (Advisor) advisorCls.newInstance(); //根據aopType的類型來設置前置或后置顧問 if ("before".equals(aopType.getText())) { aopHandler.setBeforeAdvisor(advisor); } else if ("after".equals(aopType.getText())) { aopHandler.setAfterAdvisor(advisor); } } //利用Java反射機制,通過class的名稱獲取Class對象 Class bean = Class.forName(cls.getText()); //獲取對應class的信息 java.beans.BeanInfo info = java.beans.Introspector.getBeanInfo(bean); //獲取其屬性描述 java.beans.PropertyDescriptor pd[] = info.getPropertyDescriptors(); //設置值的方法 Method mSet = null; //創建一個對象 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的反射極致調用對象的某個set方法,并將值設置進去 mSet.invoke(obj, value); } } } //為對象增加前置或后置顧問 obj = (Object) aopHandler.setObject(obj); //將對象放入beanMap中,其中key為id值,value為對象 beanMap.put(id.getText(), obj); } } catch (Exception e) { System.out.println(e.toString()); } } /** * 通過bean的id獲取bean的對象. * @param beanName bean的id * @return 返回對應對象 */ public Object getBean(String beanName) { Object obj = beanMap.get(beanName); return obj; } /** * 測試方法. * @param args */ public static void main(String[] args) { BeanFactory factory = new BeanFactory(); factory.init("config.xml"); BusinessObj obj = (BusinessObj) factory.getBean("businessObj"); obj.process(); } }
    觀察此類我們可以發現,該類添加了對bean元素的aop和aopType屬性的處理。編寫完該文件后,我們還需要修改src目錄下的配置文件:config.xml文件,在該文件中添加名為businessObj的bean,我們為其配置了aop和aopType屬性,增加了方法的前置顧問。增加的部分為:

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


lanxin1020 2009-04-06 11:50 發表評論
]]>
利用java的反射實現IOC(轉)http://m.tkk7.com/lanxin1020/archive/2009/04/06/264072.htmllanxin1020lanxin1020Mon, 06 Apr 2009 03:43:00 GMThttp://m.tkk7.com/lanxin1020/archive/2009/04/06/264072.htmlhttp://m.tkk7.com/lanxin1020/comments/264072.htmlhttp://m.tkk7.com/lanxin1020/archive/2009/04/06/264072.html#Feedback0http://m.tkk7.com/lanxin1020/comments/commentRss/264072.htmlhttp://m.tkk7.com/lanxin1020/services/trackbacks/264072.html閱讀全文

lanxin1020 2009-04-06 11:43 發表評論
]]>
工廠模式 與 ioc(轉)http://m.tkk7.com/lanxin1020/archive/2009/04/06/264043.htmllanxin1020lanxin1020Sun, 05 Apr 2009 16:13:00 GMThttp://m.tkk7.com/lanxin1020/archive/2009/04/06/264043.htmlhttp://m.tkk7.com/lanxin1020/comments/264043.htmlhttp://m.tkk7.com/lanxin1020/archive/2009/04/06/264043.html#Feedback0http://m.tkk7.com/lanxin1020/comments/commentRss/264043.htmlhttp://m.tkk7.com/lanxin1020/services/trackbacks/264043.htmlSpring的模塊化是很強的,各個功能模塊都是獨立的,我們可以選擇的使用。這一章先從Spring的IoC開始。所謂IoC就是一個用XML來定義生成對象的模式,我們看看如果來使用的。

   數據模型

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

源代碼如下:

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("中國人對吃很有一套");   
  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("美國人以車代步,有四肢退化的趨勢");   
  38. }   
  39. }  

 

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

   創建一個工廠類Factory,如下。這個工廠類里定義了兩個字符串常量,所標識不同的人種。getHuman方法根據傳入參數的字串,來判斷要生成什么樣的人種。

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("參數(人種)錯誤");   
  13. }   
  14. }  

 

下面是一個測試的程序,使用工廠方法來得到了不同的“人種對象”,并執行相應的方法。

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的用法如下:

   在項目根目錄下創建一個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. }  

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

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

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

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

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

   因為getBean方法返回的是Object類型,所以前面要加一個類型轉換。

   總結

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

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

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

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

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

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

   另外,bean.xml也可以改成其他名字。這樣我們在系統中就可以分門別類的設置不同的bean.xml。

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

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

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

   (5)關于bean.xml的用法

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

<?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>

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

 Spring能有效地組織J2EE應用各層的對象。不管是控制層的Action對象,還是業務層的Service對象,還是持久層的DAO對象,都可在Spring的管理下有機地協調、運行。Spring將各層的對象以松耦合的方式組織在一起,Action對象無須關心Service對象的具體實現,Service對象無須關心持久層對象的具體實現,各層對象的調用完全面向接口。當系統需要重構時,代碼的改寫量將大大減少。

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

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

  不管是依賴注入,還是控制反轉,都說明Spring采用動態、靈活的方式來管理各種對象。對象與對象之間的具體實現互相透明。在理解依賴注入之前,看如下這個問題在各種社會形態里如何解決:一個人(Java實例,調用者)需要一把斧子(Java實例,被調用者)。

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

  (2)進入工業社會,工廠出現。斧子不再由普通人完成,而在工廠里被生產出來,此時需要斧子的人(調用者)找到工廠,購買斧子,無須關心斧子的制造過程。對應Java程序的簡單工廠的設計模式。

  (3)進入“按需分配”社會,需要斧子的人不需要找到工廠,坐在家里發出一個簡單指令:需要斧子。斧子就自然出現在他面前。對應Spring的依賴注入。

  第一種情況下,Java實例的調用者創建被調用的Java實例,必然要求被調用的Java類出現在調用者的代碼里。無法實現二者之間的松耦合。

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

  第三種情況下,調用者無須自己定位工廠,程序運行到需要被調用者時,系統自動提供被調用者實例。事實上,調用者和被調用者都處于Spring的管理下,二者之間的依賴關系由Spring提供。

  所謂依賴注入,是指程序運行過程中,如果需要調用另一個對象協助時,無須在代碼中創建被調用者,而是依賴于外部的注入。Spring的依賴注入對調用者和被調用者幾乎沒有任何要求,完全支持對POJO之間依賴關系的管理。依賴注入通常有兩種:

  ·設值注入。

  ·構造注入。
.......



lanxin1020 2009-04-06 00:13 發表評論
]]>
AOP 動態代理模式(轉)http://m.tkk7.com/lanxin1020/archive/2009/04/05/264034.htmllanxin1020lanxin1020Sun, 05 Apr 2009 14:40:00 GMThttp://m.tkk7.com/lanxin1020/archive/2009/04/05/264034.htmlhttp://m.tkk7.com/lanxin1020/comments/264034.htmlhttp://m.tkk7.com/lanxin1020/archive/2009/04/05/264034.html#Feedback0http://m.tkk7.com/lanxin1020/comments/commentRss/264034.htmlhttp://m.tkk7.com/lanxin1020/services/trackbacks/264034.html閱讀全文

lanxin1020 2009-04-05 22:40 發表評論
]]>
整合 Struts 和 Springhttp://m.tkk7.com/lanxin1020/archive/2009/04/05/263966.htmllanxin1020lanxin1020Sun, 05 Apr 2009 02:17:00 GMThttp://m.tkk7.com/lanxin1020/archive/2009/04/05/263966.htmlhttp://m.tkk7.com/lanxin1020/comments/263966.htmlhttp://m.tkk7.com/lanxin1020/archive/2009/04/05/263966.html#Feedback0http://m.tkk7.com/lanxin1020/comments/commentRss/263966.htmlhttp://m.tkk7.com/lanxin1020/services/trackbacks/263966.html閱讀全文

lanxin1020 2009-04-05 10:17 發表評論
]]>
三種整合 Struts 應用程序與 Spring 的方式(轉)http://m.tkk7.com/lanxin1020/archive/2009/04/05/263963.htmllanxin1020lanxin1020Sun, 05 Apr 2009 01:37:00 GMThttp://m.tkk7.com/lanxin1020/archive/2009/04/05/263963.htmlhttp://m.tkk7.com/lanxin1020/comments/263963.htmlhttp://m.tkk7.com/lanxin1020/archive/2009/04/05/263963.html#Feedback0http://m.tkk7.com/lanxin1020/comments/commentRss/263963.htmlhttp://m.tkk7.com/lanxin1020/services/trackbacks/263963.html

三種整合 Struts 應用程序與 Spring 的方式

 

Struts Recipes 的合著者 George Franciscus 將介紹另一個重大的 Struts 整合竅門 —— 這次是將 Struts 應用程序導入 Spring 框架。請跟隨 George,他將向您展示如何改變 Struts 動作,使得管理 Struts 動作就像管理 Spring beans 那樣。結果是一個增強的 web 框架,這個框架可以方便地利用 Spring AOP 的優勢。

您肯定已經聽說過控制反轉 (IOC) 設計模式,因為很長一段時間以來一直在流傳關于它的信息。如果您在任何功能中使用過 Spring 框架,那么您就知道其原理的作用。在本文中,我利用這一原理把一個 Struts 應用程序注入 Spring 框架,您將親身體會到 IOC 模式的強大。

將一個 Struts 應用程序整合進 Spring 框架具有多方面的優點。首先,Spring 是為解決一些關于 JEE 的真實世界問題而設計的,比如復雜性、低性能和可測試性,等等。第二,Spring 框架包含一個 AOP 實現,允許您將面向方面技術應用于面向對象的代碼。第三,一些人可能會說 Spring 框架只有處理 Struts 比 Struts 處理自己好。但是這是觀點問題,我演示三種將 Struts 應用程序整合到 Spring 框架的方法后,具體由您自己決定使用哪一種。

我所演示的方法都是執行起來相對簡單的,但是它們卻具有明顯不同的優點。我為每一種方法創建了一個獨立而可用的例子,這樣您就可以完全理解每種方法。請參閱 下載 部分獲得完整例子源代碼。請參閱 參考資料,下載 Struts MVC 和 Spring 框架。

為什么 Spring 這么了不起?

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

IOC 和 Spring

IOC 是一種使應用程序邏輯外在化的設計模式,所以它是被注入而不是被寫入客戶機代碼中。將 IOC 與接口編程應用結合,就像 Spring 框架那樣,產生了一種架構,這種架構能夠減少客戶機對特定實現邏輯的依賴。請參閱 參考資料 了解更多關于 IOC 和 Spring 的信息。

依賴注入是一個強大的特性,但是 Spring 框架能夠提供更多特性。Spring 支持可插拔的事務管理器,可以給您的事務處理提供更廣泛的選擇范圍。它集成了領先的持久性框架,并且提供一個一致的異常層次結構。Spring 還提供了一種使用面向方面代碼代替正常的面向對象代碼的簡單機制。

Spring AOP 允許您使用攔截器 在一個或多個執行點上攔截應用程序邏輯。加強應用程序在攔截器中的日志記錄邏輯會產生一個更可讀的、實用的代碼基礎,所以攔截器廣泛用于日志記錄。您很快就會看到,為了處理橫切關注點,Spring AOP 發布了它自己的攔截器,您也可以編寫您自己的攔截器。






整合 Struts 和 Spring

與 Struts 相似,Spring 可以作為一個 MVC 實現。這兩種框架都具有自己的優點和缺點,盡管大部分人同意 Struts 在 MVC 方面仍然是最好的。很多開發團隊已經學會在時間緊迫的時候利用 Struts 作為構造高品質軟件的基礎。Struts 具有如此大的推動力,以至于開發團隊寧愿整合 Spring 框架的特性,而不愿意轉換成 Spring MVC。沒必要進行轉換對您來說是一個好消息。Spring 架構允許您將 Struts 作為 Web 框架連接到基于 Spring 的業務和持久層。最后的結果就是現在一切條件都具備了。

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





三個小竅門

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

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

裝載應用程序環境

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

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

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





竅門 1. 使用 Spring 的 ActionSupport

手動創建一個 Spring 環境是一種整合 Struts 和 Spring 的最直觀的方式。為了使它變得更簡單,Spring 提供了一些幫助。為了方便地獲得 Spring 環境,org.springframework.web.struts.ActionSupport 類提供了一個 getWebApplicationContext() 方法。您所做的只是從 Spring 的 ActionSupport 而不是 Struts Action 類擴展您的動作,如清單 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");
                        }
                        }
                        

讓我們快速思考一下這里到底發生了什么。在 (1) 處,我通過從 Spring 的 ActionSupport 類而不是 Struts 的 Action 類進行擴展,創建了一個新的 Action。在 (2) 處,我使用 getWebApplicationContext() 方法獲得一個 ApplicationContext。為了獲得業務服務,我使用在 (2) 處獲得的環境在 (3) 處查找一個 Spring bean。

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





竅門 2. 覆蓋 RequestProcessor

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


清單 2. 通過 Spring 的 DelegatingRequestProcessor 進行整合
<?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> 標記來用 DelegatingRequestProcessor 覆蓋默認的 Struts RequestProcessor。下一步是在我的 Spring 配置文件中注冊該動作,如清單 3 所示:


清單 3. 在 Spring 配置文件中注冊一個動作
<?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) 處,我使用名稱屬性注冊了一個 bean,以匹配 struts-config 動作映射名稱。SearchSubmit 動作揭示了一個 JavaBean 屬性,允許 Spring 在運行時填充屬性,如清單 4 所示:


清單 4. 具有 JavaBean 屬性的 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.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 中,您可以了解到如何創建 Struts 動作。在 (1) 處,我創建了一個 JavaBean 屬性。DelegatingRequestProcessor自動地配置這種屬性。這種設計使 Struts 動作并不知道它正被 Spring 管理,并且使您能夠利用 Sping 的動作管理框架的所有優點。由于您的 Struts 動作注意不到 Spring 的存在,所以您不需要重寫您的 Struts 代碼就可以使用其他控制反轉容器來替換掉 Spring。

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





竅門 3. 將動作管理委托給 Spring

一個更好的解決方法是將 Strut 動作管理委托給 Spring。您可以通過在 struts-config 動作映射中注冊一個代理來實現。代理負責在 Spring 環境中查找 Struts 動作。由于動作在 Spring 的控制之下,所以它可以填充動作的 JavaBean 屬性,并為應用諸如 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 是一個典型的 struts-config.xml 文件,只有一個小小的差別。它注冊 Spring 代理類的名稱,而不是聲明動作的類名,如(1)處所示。DelegatingActionProxy 類使用動作映射名稱查找 Spring 環境中的動作。這就是我們使用 ContextLoaderPlugIn 聲明的環境。

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


清單 6. 在 Spring 環境中注冊一個 Struts 動作
<?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>
                        





動作委托的優點

動作委托解決方法是這三種方法中最好的。Struts 動作不了解 Spring,不對代碼作任何改變就可用于非 Spring 應用程序中。RequestProcessor 的改變不會影響它,并且它可以利用 Spring AOP 特性的優點。

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





攔截 Struts

前面提到過,通過將 Struts 動作委托給 Spring 框架而整合 Struts 和 Spring 的一個主要的優點是:您可以將 Spring 的 AOP 攔截器應用于您的 Struts 動作。通過將 Spring 攔截器應用于 Struts 動作,您可以用最小的代價處理橫切關注點。

雖然 Spring 提供很多內置攔截器,但是我將向您展示如何創建自己的攔截器并把它應用于一個 Struts 動作。為了使用攔截器,您需要做三件事:

  1. 創建攔截器。
  2. 注冊攔截器。
  3. 聲明在何處攔截代碼。

這看起來非常簡單的幾句話卻非常強大。例如,在清單 7 中,我為 Struts 動作創建了一個日志記錄攔截器。 這個攔截器在每個方法調用之前打印一句話:


清單 7. 一個簡單的日志記錄攔截器
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!");
                        }
                        }
                        

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


清單 8. 在 Spring 配置文件中注冊攔截器
<?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>
                        

您可能已經注意到了,清單 8 擴展了 清單 6 中所示的應用程序以包含一個攔截器。具體細節如下:

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

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





結束語

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

 




lanxin1020 2009-04-05 09:37 發表評論
]]>
Spring 的優秀工具類盤點2(轉)http://m.tkk7.com/lanxin1020/archive/2009/04/05/263962.htmllanxin1020lanxin1020Sun, 05 Apr 2009 00:45:00 GMThttp://m.tkk7.com/lanxin1020/archive/2009/04/05/263962.htmlhttp://m.tkk7.com/lanxin1020/comments/263962.htmlhttp://m.tkk7.com/lanxin1020/archive/2009/04/05/263962.html#Feedback0http://m.tkk7.com/lanxin1020/comments/commentRss/263962.htmlhttp://m.tkk7.com/lanxin1020/services/trackbacks/263962.html特殊字符轉義

由于 Web 應用程序需要聯合使用到多種語言,每種語言都包含一些特殊的字符,對于動態語言或標簽式的語言而言,如果需要動態構造語言的內容時,一個我們經常會碰到的問題就是特殊字符轉義的問題。下面是 Web 開發者最常面對需要轉義的特殊字符類型:

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

如果不對這些特殊字符進行轉義處理,則不但可能破壞文檔結構,還可以引發潛在的安全問題。Spring 為 HTML 和 JavaScript 特殊字符提供了轉義操作工具類,它們分別是 HtmlUtils 和 JavaScriptUtils。

HTML 特殊字符轉義

HTML 中 <,>,& 等字符有特殊含義,它們是 HTML 語言的保留字,因此不能直接使用。使用這些個字符時,應使用它們的轉義序列:

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

由于 HTML 網頁本身就是一個文本型結構化文檔,如果直接將這些包含了 HTML 特殊字符的內容輸出到網頁中,極有可能破壞整個 HTML 文檔的結構。所以,一般情況下需要對動態數據進行轉義處理,使用轉義序列表示 HTML 特殊字符。下面的 JSP 網頁將一些變量動態輸出到 HTML 網頁中:


清單 1. 未進行 HTML 特殊字符轉義處理網頁
            <%@ 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" /> ②
            

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

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

融合動態數據后的 HTML 網頁已經面目全非,首先 ① 處的 <table> 結構被包含 HTML 特殊字符的 userName 變量截斷了,造成其后的 <table> 代碼變成無效的內容;其次,② 處 <input> 被動態數據改換為按鈕類型的組件(type="button")。為了避免這一問題,我們需要事先對可能破壞 HTML 文檔結構的動態數據進行轉義處理。Spring 為我們提供了一個簡單適用的 HTML 特殊字符轉義工具類,它就是 HtmlUtils。下面,我們通過一個簡單的例子了解 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); ①轉換為HTML轉義字符表示
            System.out.println(str1);
            String str2 = HtmlUtils.htmlEscapeDecimal(specialStr); ②轉換為數據轉義表示
            System.out.println(str2);
            String str3 = HtmlUtils.htmlEscapeHex(specialStr); ③轉換為十六進制數據轉義表示
            System.out.println(str3);
            ④下面對轉義后字符串進行反向操作
            System.out.println(HtmlUtils.htmlUnescape(str1));
            System.out.println(HtmlUtils.htmlUnescape(str2));
            System.out.println(HtmlUtils.htmlUnescape(str3));
            }
            }
            

HTML 不但可以使用通用的轉義序列表示 HTML 特殊字符,還可以使用以 # 為前綴的數字序列表示 HTML 特殊字符,它們在最終的顯示效果上是一樣的。HtmlUtils 提供了三個轉義方法:

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

此外,HtmlUtils 還提供了一個能夠將經過轉義內容還原的方法:htmlUnescape(String input),它可以還原以上三種轉義序列的內容。運行以上代碼,您將可以看到以下的輸出:

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 對代碼 清單 1 的 userName 和 address 進行轉義處理,最終輸出的 HTML 頁面就不會遭受破壞了。

JavaScript 特殊字符轉義

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

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

我們通過一個具體例子演示動態變量是如何對 JavaScript 程序進行破壞的。假設我們有一個 JavaScript 數組變量,其元素值通過一個 Java List 對象提供,下面是完成這一操作的 JSP 代碼片斷:


清單 3. jsTest.jsp:未對 JavaScript 特殊字符進行處理
            <%@ 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)%>";
            ① 未對可能包含特殊 JavaScript 字符的變量進行處理
            <% } %>
            </script>
            

當客戶端調用這個 JSP 頁面后,將得到以下的 HTML 輸出頁面:

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

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

因此,如果網頁中的 JavaScript 代碼需要通過拼接 Java 變量動態產生時,一般需要對變量的內容進行轉義處理,可以通過 Spring 的 JavaScriptUtils 完成這件工作。下面,我們使用 JavaScriptUtils 對以上代碼進行改造:

<%@ 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++) { %>
            ① 在輸出動態內容前事先進行轉義處理
            txtList[<%=i%>] = "<%=JavaScriptUtils.javaScriptEscape(""+textList.get(i))%>";
            <% } %>
            </script>
            

通過轉義處理后,這個 JSP 頁面輸出的結果網頁的 JavaScript 代碼就不會產生問題了:

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

SQL特殊字符轉義

應該說,您即使沒有處理 HTML 或 JavaScript 的特殊字符,也不會帶來災難性的后果,但是如果不在動態構造 SQL 語句時對變量中特殊字符進行處理,將可能導致程序漏洞、數據盜取、數據破壞等嚴重的安全問題。網絡中有大量講解 SQL 注入的文章,感興趣的讀者可以搜索相關的資料深入研究。

雖然 SQL 注入的后果很嚴重,但是只要對動態構造的 SQL 語句的變量進行特殊字符轉義處理,就可以避免這一問題的發生了。來看一個存在安全漏洞的經典例子:

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

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

所以除非必要,一般建議通過 PreparedStatement 參數綁定的方式構造動態 SQL 語句,因為這種方式可以避免 SQL 注入的潛在安全問題。但是往往很難在應用中完全避免通過拼接字符串構造動態 SQL 語句的方式。為了防止他人使用特殊 SQL 字符破壞 SQL 的語句結構或植入惡意操作,必須在變量拼接到 SQL 語句之前對其中的特殊字符進行轉義處理。Spring 并沒有提供相應的工具類,您可以通過 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);
            }
            }
            

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





回頁首


方法入參檢測工具類

Web 應用在接受表單提交的數據后都需要對其進行合法性檢查,如果表單數據不合法,請求將被駁回。類似的,當我們在編寫類的方法時,也常常需要對方法入參進行合法性檢查,如果入參不符合要求,方法將通過拋出異常的方式拒絕后續處理。舉一個例子:有一個根據文件名獲取輸入流的方法:InputStream getData(String file),為了使方法能夠成功執行,必須保證 file 入參不能為 null 或空白字符,否則根本無須進行后繼的處理。這時方法的編寫者通常會在方法體的最前面編寫一段對入參進行檢測的代碼,如下所示:

public InputStream getData(String file) {
            if (file == null || file.length() == 0|| file.replaceAll("\\s", "").length() == 0) {
            throw new IllegalArgumentException("file入參不是有效的文件地址");
            }
            …
            }
            

類似以上檢測方法入參的代碼是非常常見,但是在每個方法中都使用手工編寫檢測邏輯的方式并不是一個好主意。閱讀 Spring 源碼,您會發現 Spring 采用一個 org.springframework.util.Assert 通用類完成這一任務。

Assert 翻譯為中文為“斷言”,使用過 JUnit 的讀者都熟知這個概念,它斷定某一個實際的運行值和預期想一樣,否則就拋出異常。Spring 對方法入參的檢測借用了這個概念,其提供的 Assert 類擁有眾多按規則對方法入參進行斷言的方法,可以滿足大部分方法入參檢測的要求。這些斷言方法在入參不滿足要求時就會拋出 IllegalArgumentException。下面,我們來認識一下 Assert 類中的常用斷言方法:

斷言方法 說明
notNull(Object object) 當 object 不為 null 時拋出異常,notNull(Object object, String message) 方法允許您通過 message 定制異常信息。和 notNull() 方法斷言規則相反的方法是 isNull(Object object)/isNull(Object object, String message),它要求入參一定是 null;
isTrue(boolean expression) / isTrue(boolean expression, String message) 當 expression 不為 true 拋出異常;
notEmpty(Collection collection) / notEmpty(Collection collection, String message) 當集合未包含元素時拋出異常。notEmpty(Map map) / notEmpty(Map map, String message) 和 notEmpty(Object[] array, String message) / notEmpty(Object[] array, String message) 分別對 Map 和 Object[] 類型的入參進行判斷;
hasLength(String text) / hasLength(String text, String message) 當 text 為 null 或長度為 0 時拋出異常;
hasText(String text) / hasText(String text, String message) text 不能為 null 且必須至少包含一個非空格的字符,否則拋出異常;
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 斷言類可以簡化方法入參檢測的代碼,如 InputStream getData(String file) 在應用 Assert 斷言類后,其代碼可以簡化為以下的形式:

public InputStream getData(String file){
            Assert.hasText(file,"file入參不是有效的文件地址");
            ① 使用 Spring 斷言類進行方法入參檢測
            …
            }
            

可見使用 Spring 的 Assert 替代自編碼實現的入參檢測邏輯后,方法的簡潔性得到了不少的提高。Assert 不依賴于 Spring 容器,您可以大膽地在自己的應用中使用這個工具類。





回頁首


小結

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

對于 Web 應用來說,由于有很多關聯的腳本代碼,如果這些代碼通過拼接字符串的方式動態產生,就需要對動態內容中特殊的字符進行轉義處理,否則就有可能產生意想不到的后果。Spring 為此提供了 HtmlUtils 和 JavaScriptUtils 工具類,只要將動態內容在拼接之前使用工具類進行轉義處理,就可以避免類似問題的發生了。如果您不介意引入一個第三方類包,那么 jakarta commons lang 通用類包中的 StringEscapeUtils 工具類可能更加適合,因為它提供了更加全面的轉義功能。

最后我們還介紹了 Spring 的 Assert 工具類,Assert 工具類是通用性很強的工具類,它使用面向對象的方式解決方法入參檢測的問題,您可以在自己的應用中使用 Assert 對方法入參進行檢查。




lanxin1020 2009-04-05 08:45 發表評論
]]>
Spring 的優秀工具類盤點1(轉)http://m.tkk7.com/lanxin1020/archive/2009/04/05/263961.htmllanxin1020lanxin1020Sun, 05 Apr 2009 00:44:00 GMThttp://m.tkk7.com/lanxin1020/archive/2009/04/05/263961.htmlhttp://m.tkk7.com/lanxin1020/comments/263961.htmlhttp://m.tkk7.com/lanxin1020/archive/2009/04/05/263961.html#Feedback0http://m.tkk7.com/lanxin1020/comments/commentRss/263961.htmlhttp://m.tkk7.com/lanxin1020/services/trackbacks/263961.html

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

在這個分為兩部分的文章中,我們將從眾多的 Spring 工具類中遴選出那些好用的工具類介紹給大家。第 1 部分將介紹與文件資源操作和 Web 相關的工具類。在 第 2 部分 中將介紹特殊字符轉義和方法入參檢測工具類。

文件資源操作

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

加載文件資源

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

訪問文件資源

假設有一個文件地位于 Web 應用的類路徑下,您可以通過以下方式對這個文件資源進行訪問:

  • 通過 FileSystemResource 以文件系統絕對路徑的方式進行訪問;
  • 通過 ClassPathResource 以類路徑的方式進行訪問;
  • 通過 ServletContextResource 以相對于Web應用根目錄的方式進行訪問。

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


清單 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";
            // ① 使用系統文件路徑方式加載文件
            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 接口定義的多個方法訪問文件的數據和其它的信息:如您可以通過 getFileName() 獲取文件名,通過 getFile() 獲取資源對應的 File 對象,通過 getInputStream() 直接獲取文件的輸入流。此外,您還可以通過 createRelative(String relativePath) 在資源相對地址上創建新的資源。

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

<%@ 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"/>
            <%
            // ① 注意文件資源地址以相對于 Web 應用根路徑的方式表示
            Resource res3 = new ServletContextResource(application,
            "/WEB-INF/classes/conf/file1.txt");
            out.print(res3.getFilename());
            %>
            

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

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


清單 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 實現類打交道的情況下使用 Spring 文件資源加載的功能了。

本地化文件資源

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


清單 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();
            // ① 獲取對應美國的本地化文件資源
            Resource msg_us = lrHalper.findLocalizedResource("i18n/message", ".properties",
            Locale.US);
            // ② 獲取對應中國大陸的本地化文件資源
            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 類型的對象。如果您決定統一使用 Spring 的 Resource 接表征文件資源,那么 LocalizedResourceHelper 就是獲取文件資源的非常適合的幫助類了。

文件操作

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

文件內容拷貝

第一個我們要認識的是 FileCopyUtils,它提供了許多一步式的靜態操作方法,能夠將文件內容拷貝到一個目標 byte[]、String 甚至一個輸出流或輸出文件中。下面的實例展示了 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");
            // ① 將文件內容拷貝到一個 byte[] 中
            byte[] fileData = FileCopyUtils.copyToByteArray(res.getFile());
            // ② 將文件內容拷貝到一個 String 中
            String fileStr = FileCopyUtils.copyToString(new FileReader(res.getFile()));
            // ③ 將文件內容拷貝到另一個目標文件
            FileCopyUtils.copy(res.getFile(),
            new File(res.getFile().getParent()+ "/file2.txt"));
            // ④ 將文件內容拷貝到一個輸出流中
            OutputStream os = new ByteArrayOutputStream();
            FileCopyUtils.copy(res.getInputStream(), os);
            }
            }
            

往往我們都通過直接操作 InputStream 讀取文件的內容,但是流操作的代碼是比較底層的,代碼的面向對象性并不強。通過 FileCopyUtils 讀取和拷貝文件內容易于操作且相當直觀。如在 ① 處,我們通過 FileCopyUtils 的 copyToByteArray(File in) 方法就可以直接將文件內容讀到一個 byte[] 中;另一個可用的方法是 copyToByteArray(InputStream in),它將輸入流讀取到一個 byte[] 中。

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

FileCopyUtils 還提供了多個將文件內容拷貝到各種目標對象中的方法,這些方法包括:

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

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

屬性文件操作

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

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"));
            }
            }
            

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

方法 說明
static Properties loadProperties(Resource resource) 從 Resource 中加載屬性
static void fillProperties(Properties props, Resource resource) 將 Resource 中的屬性數據添加到一個已經存在的 Properties 對象中

特殊編碼的資源

當您使用 Resource 實現類加載文件資源時,它默認采用操作系統的編碼格式。如果文件資源采用了特殊的編碼格式(如 UTF-8),則在讀取資源內容時必須事先通過 EncodedResource 指定編碼格式,否則將會產生中文亂碼的問題。


清單 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");
            // ① 指定文件資源對應的編碼格式(UTF-8)
            EncodedResource encRes = new EncodedResource(res,"UTF-8");
            // ② 這樣才能正確讀取文件的內容,而不會出現亂碼
            String content  = FileCopyUtils.copyToString(encRes.getReader());
            System.out.println(content);
            }
            }
            

EncodedResource 擁有一個 getResource() 方法獲取 Resource,但該方法返回的是通過構造函數傳入的原 Resource 對象,所以必須通過 EncodedResource#getReader() 獲取應用編碼后的 Reader 對象,然后再通過該 Reader 讀取文件的內容。





回頁首


Web 相關工具類

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

操作 Servlet API 的工具類

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

WebApplicationContextUtils

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

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

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

WebApplicationContext wac =WebApplicationContextUtils.
            getWebApplicationContext(servletContext);
            

當 ServletContext 屬性列表中不存在 WebApplicationContext 時,getWebApplicationContext() 方法不會拋出異常,它簡單地返回 null。如果后續代碼直接訪問返回的結果將引發一個 NullPointerException 異常,而 WebApplicationContextUtils 另一個 getRequiredWebApplicationContext(ServletContext sc) 方法要求 ServletContext 屬性列表中一定要包含一個有效的 WebApplicationContext 對象,否則馬上拋出一個 IllegalStateException 異常。我們推薦使用后者,因為它能提前發現錯誤的時間,強制開發者搭建好必備的基礎設施。

WebUtils

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

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

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

此外,WebUtils還提供了一些和ServletContext相關的方便方法:

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

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

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 所提供的過濾器和監聽器

Spring 為 Web 應用提供了幾個過濾器和監聽器,在適合的時間使用它們,可以解決一些常見的 Web 應用問題。

延遲加載過濾器

Hibernate 允許對關聯對象、屬性進行延遲加載,但是必須保證延遲加載的操作限于同一個 Hibernate Session 范圍之內進行。如果 Service 層返回一個啟用了延遲加載功能的領域對象給 Web 層,當 Web 層訪問到那些需要延遲加載的數據時,由于加載領域對象的 Hibernate Session 已經關閉,這些導致延遲加載數據的訪問異常。

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

OpenSessionInViewFilter 過濾器將 Hibernate Session 綁定到請求線程中,它將自動被 Spring 的事務管理器探測到。所以 OpenSessionInViewFilter 適用于 Service 層使用HibernateTransactionManager 或 JtaTransactionManager 進行事務管理的環境,也可以用于非事務只讀的數據操作中。

要啟用這個過濾器,必須在 web.xml 中對此進行配置:

…
            <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>
            …
            

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

中文亂碼過濾器

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

<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> ③ 強制進行編碼轉換
            <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 請求的數據都會被轉碼為 UTF-8 編碼格式,表單中文亂碼的問題就可以解決了。

請求跟蹤日志過濾器

除了以上兩個常用的過濾器外,還有兩個在程序調試時可能會用到的請求日志跟蹤過濾器,它們會將請求的一些重要信息記錄到日志中,方便程序的調試。這兩個日志過濾器只有在日志級別為 DEBUG 時才會起作用:

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

以下是日志過濾器記錄的請求跟蹤日志的片斷:

(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
            …
            

通過這個請求跟蹤日志,程度調試者可以詳細地查看到有哪些請求被調用,請求的參數是什么,請求是否正確返回等信息。雖然這兩個請求跟蹤日志過濾器一般在程序調試時使用,但是即使程序部署不將其從 web.xml 中移除也不會有大礙,因為只要將日志級別設置為 DEBUG 以上級別,它們就不會輸出請求跟蹤日志信息了。

轉存 Web 應用根目錄監聽器和 Log4J 監聽器

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


清單 6. WebAppRootListener 監聽器配置
…
            <context-param>
            <param-name>webAppRootKey</param-name>
            <param-value>baobaotao.root</param-value> ① Web 應用根目錄以該屬性名添加到系統參數中
            </context-param>
            …
            ② 負責將 Web 應用根目錄以 webAppRootKey 上下文參數指定的屬性名添加到系統參數中
            <listener>
            <listener-class>
            org.springframework.web.util.WebAppRootListener
            </listener-class>
            </listener>
            …
            

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

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
            

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

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

提示

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

Log4jConfigListener 監聽器包括了 WebAppRootListener 的功能,也就是說,Log4jConfigListener 會自動完成將 Web 應用根目錄以 webAppRootKey 上下文參數指定的屬性名添加到系統參數中,所以當您使用 Log4jConfigListener 后,就沒有必須再使用 WebAppRootListener了。

Introspector 緩存清除監聽器

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

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





回頁首


小結

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

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

Spring 提供了幾個 Servlet 過濾器和監聽器,其中 ServletContextRequestLoggingFilter 和 ServletContextRequestLoggingFilter 可以記錄請求訪問的跟蹤日志,你可以在程序調試時使用它們獲取請求調用的詳細信息。WebAppRootListener 可以將 Web 應用的根目錄以特定屬性名添加到系統參數中,以便第三方工具類通過 ${key} 的方式進行訪問。Log4jConfigListener 允許你指定 Log4J 日志配置文件的地址,且可以在配置文件中通過 ${key} 的方式引用 Web 應用根目錄,如果你需要在 Web 應用相關的目錄創建日志文件,使用 Log4jConfigListener 可以很容易地達到這一目標。

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



lanxin1020 2009-04-05 08:44 發表評論
]]>
配置Spring數據源 (轉)http://m.tkk7.com/lanxin1020/archive/2009/03/25/261822.htmllanxin1020lanxin1020Wed, 25 Mar 2009 03:14:00 GMThttp://m.tkk7.com/lanxin1020/archive/2009/03/25/261822.htmlhttp://m.tkk7.com/lanxin1020/comments/261822.htmlhttp://m.tkk7.com/lanxin1020/archive/2009/03/25/261822.html#Feedback0http://m.tkk7.com/lanxin1020/comments/commentRss/261822.htmlhttp://m.tkk7.com/lanxin1020/services/trackbacks/261822.html配置Spring數據源
配置一個數據源 
    Spring在第三方依賴包中包含了兩個數據源的實現類包,其一是Apache的DBCP,其二是 C3P0。可以在Spring配置文件中利用這兩者中任何一個配置數據源。
DBCP數據源 
    DBCP類包位于 /lib/jakarta-commons/commons-dbcp.jar,DBCP 是一個依賴 Jakarta commons-pool對象池機制的數據庫連接池,所以在類路徑下還必須包括/lib/jakarta- commons /commons-pool.jar。下面是使用DBCP配置MySql數據源的配置片斷:

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()方法關閉數據源,所以必須設定destroy-method=”close”屬性, 以便Spring容器關閉時,數據源能夠正常關閉。除以上必須的數據源屬性外,還有一些常用的屬性: 
    defaultAutoCommit:設置從數據源中返回的連接是否采用自動提交機制,默認值為 true; 
    defaultReadOnly:設置數據源是否僅能執行只讀操作, 默認值為 false; 
    maxActive:最大連接數據庫連接數,設置為0時,表示沒有限制; 
    maxIdle:最大等待連接中的數量,設置為0時,表示沒有限制; 
    maxWait:最大等待秒數,單位為毫秒, 超過時間會報出錯誤信息; 
    validationQuery:用于驗證連接是否成功的查詢SQL語句,SQL語句必須至少要返回一行數據, 如你可以簡單地設置為:“select count(*) from user”; 
    removeAbandoned:是否自我中斷,默認是 false ; 
    removeAbandonedTimeout:幾秒后數據連接會自動斷開,在removeAbandoned為true,提供該值; 
    logAbandoned:是否記錄中斷事件, 默認為 false; 

C3P0數據源 
    C3P0 是一個開放源代碼的JDBC數據源實現項目,它在lib目錄中與Hibernate一起發布,實現了JDBC3和JDBC2擴展規范說明的 Connection 和Statement 池。C3P0類包位于/lib/c3p0/c3p0-0.9.0.4.jar。下面是使用C3P0配置一個 oracle數據源:

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一樣提供了一個用于關閉數據源的close()方法,這樣我們就可以保證Spring容器關閉時數據源能夠成功釋放。 
    C3P0擁有比DBCP更豐富的配置屬性,通過這些屬性,可以對數據源進行各種有效的控制: 
    acquireIncrement:當連接池中的連接用完時,C3P0一次性創建新連接的數目; 
    acquireRetryAttempts:定義在從數據庫獲取新連接失敗后重復嘗試獲取的次數,默認為30; 
    acquireRetryDelay:兩次連接中間隔時間,單位毫秒,默認為1000; 
    autoCommitOnClose:連接關閉時默認將所有未提交的操作回滾。默認為false; 
    automaticTestTable: C3P0 將建一張名為Test的空表,并使用其自帶的查詢語句進行測試。如果定義了這個參數,那么屬性preferredTestQuery將被忽略。你 不能在這張Test表上進行任何操作,它將中為C3P0測試所用,默認為null; 
    breakAfterAcquireFailure:獲取連接失敗將會引起所有等待獲取連接的線程拋出異常。但是數據源仍有效保留,并在下次調   用getConnection()的時候繼續嘗試獲取連接。如果設為true,那么在嘗試獲取連接失敗后該數據源將申明已斷開并永久關閉。默認為 false; 
    checkoutTimeout:當連接池用完時客戶端調用getConnection()后等待獲取新連接的時間,超時后將拋出SQLException,如設為0則無限期等待。單位毫秒,默認為0; 
    connectionTesterClassName: 通過實現ConnectionTester或QueryConnectionTester的類來測試連接,類名需設置為全限定名。默認為 com.mchange.v2.C3P0.impl.DefaultConnectionTester; 
    idleConnectionTestPeriod:隔多少秒檢查所有連接池中的空閑連接,默認為0表示不檢查; 
    initialPoolSize:初始化時創建的連接數,應在minPoolSize與maxPoolSize之間取值。默認為3; 
    maxIdleTime:最大空閑時間,超過空閑時間的連接將被丟棄。為0或負數則永不丟棄。默認為0; 
    maxPoolSize:連接池中保留的最大連接數。默認為15; 
    maxStatements:JDBC 的標準參數,用以控制數據源內加載的PreparedStatement數量。但由于預緩存的Statement屬 于單個Connection而不是整個連接池。所以設置這個參數需要考慮到多方面的因素,如果maxStatements與 maxStatementsPerConnection均為0,則緩存被關閉。默認為0; 
    maxStatementsPerConnection:連接池內單個連接所擁有的最大緩存Statement數。默認為0; 
    numHelperThreads:C3P0是異步操作的,緩慢的JDBC操作通過幫助進程完成。擴展這些操作可以有效的提升性能,通過多線程實現多個操作同時被執行。默認為3; 
    preferredTestQuery:定義所有連接測試都執行的測試語句。在使用連接測試的情況下這個參數能顯著提高測試速度。測試的表必須在初始數據源的時候就存在。默認為null; 
    propertyCycle: 用戶修改系統配置參數執行前最多等待的秒數。默認為300; 
    testConnectionOnCheckout:因性能消耗大請只在需要的時候使用它。如果設為true那么在每個connection提交的時候都 將校驗其有效性。建議使用 idleConnectionTestPeriod或automaticTestTable 
等方法來提升連接測試的性能。默認為false; 
    testConnectionOnCheckin:如果設為true那么在取得連接的同時將校驗連接的有效性。默認為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 
    提示 經常有開發者在${xxx}的前后不小心鍵入一些空格,這些空格字符將和變量合并后作為屬性的值。如: 的屬性配置項,在前后都有空格,被解析后,username的值為“ 1234 ”,這將造成最終的錯誤,因此需要特別小心。

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

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

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

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的數據源實現類 
    Spring 本身也提供了一個簡單的數據源實現類DriverManagerDataSource ,它位于 org.springframework.jdbc.datasource包中。這個類實現了javax.sql.DataSource接口,但 它并沒有提供池化連接的機制,每次調用getConnection()獲取新連接時,只是簡單地創建一個新的連接。因此,這個數據源類比較適合在單元測試 或簡單的獨立應用中使用,因為它不需要額外的依賴類。 
     下面,我們來看一下DriverManagerDataSource的簡單使用:當然,我們也可以通過配置的方式直接使用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();   

 

小結 

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

DBCP與C3PO配置的區別:

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>



lanxin1020 2009-03-25 11:14 發表評論
]]>
主站蜘蛛池模板: 亚洲色图视频在线观看| 国产精品久久免费视频| 伊人久久免费视频| 国产拍拍拍无码视频免费| 国产啪精品视频网站免费尤物| 一级特级女人18毛片免费视频| 免费夜色污私人影院网站电影| 阿v视频免费在线观看| 色妞www精品视频免费看| 免费国产va在线观看| 成人嫩草影院免费观看| 一级美国片免费看| 中文字幕在线免费视频| 日本免费A级毛一片| 久久aⅴ免费观看| 亚洲精品在线免费看| 亚洲VA成无码人在线观看天堂| 亚洲欧洲精品无码AV| 亚洲产国偷V产偷V自拍色戒| 亚洲精品天天影视综合网| 亚洲美女免费视频| 亚洲最大av资源站无码av网址| 亚洲精华国产精华精华液| 日韩精品免费一线在线观看| 99久久免费国产精精品| 精品无码国产污污污免费网站| 一二三四影视在线看片免费| 在线观看亚洲免费视频| 95老司机免费福利| 拍拍拍又黄又爽无挡视频免费| 国产网站在线免费观看| 国产精品亚洲mnbav网站 | 亚洲A∨无码一区二区三区 | 国产一区二区三区在线免费观看| 亚洲AⅤ无码一区二区三区在线| 亚洲免费网站观看视频| 国产又大又粗又硬又长免费 | 亚洲免费福利视频| 爽爽日本在线视频免费| 亚洲色欲久久久综合网| 亚洲精品456在线播放|