15年前6月的一個悶熱的早晨,我爬到一艘老式的玻璃纖維皮劃艇中。它是如此古老,以至于有些小碎片差點插到了我的手指中,而且劃槳長度大約是傳統激流劃槳的兩倍。我游泳的時候要多過劃船,但是這沒有關系。15年之后,我仍然對它著迷。
大約2年前,我試用了在Hibernate站點上突出提到的Spring項目。我覺得它就像是上面提到的老式皮劃艇:它再適合我不過了。為了進行企業硬編碼,Spring如此深植于我的編程工作中,以至于我使用它作為我第4本Java著作《Spring: A Developer's Notebook》的主題。在本文中,我將說明其中的原因。
1. Spring 提供更好的優勢
在河流中,我學會了更多地使用我的腰和背來劃槳,因為我的手臂肌肉無法堅持整日在河上劃槳。我變得更加高效;我獲得了更好的利用率。借助于Spring,我可以使每行代碼做更多的事情。借助于Spring,您可以發現很多額外的手段,其中最大的一處是在持久性方面。下面給出一個Hibernate數據訪問對象中的方法:
public List getReservations( ) { return getHibernateTemplate( ).find("from Reservation");}}
注意您沒有看到的內容。這里沒有事務處理。Spring允許構建配置代碼來處理事務。不必通過關閉會話來管理資源。不必進行自己的配置。不必在這個層次上管理異常,因為異常是未經檢查的。可以自由地在最合適的位置管理它們。下面給出了另一個Hibernate方法,沒有使用Spring:
public List getBikesOldWay( ) throws Exception { List bikes = null; Session s = null; try { s = mySessionFactory.openSession( ); bikes = s.find("from Bike"); }catch (Exception ex) { //handle exception gracefully }finally { s.close( ); } return bikes;
Spring為您提供了更好的優勢。借助于Spring,可以更快地編寫代碼,而進行更少的維護。
2. Spring支持POJO編程
EJB 2.x失敗之后,我們都在尋求表達企業服務而不使用笨拙的模型侵入每個bean的方法。當然,我們需要事務、安全性、持久性,有時還需要遠程控制。對于EJB,我必須學習一個廣泛使用的API,并通過新的工具和部署流程開展工作。我是容器所提供的服務的奴隸。而借助于Spring,我可以選擇自己的服務和持久性框架。我進行POJO編程,并使用配置文件為它們添加企業服務。
在《Spring: A Developer's Notebook》一書中,我構建了一個RentaBike應用程序。我把我的POJO稱作hibRentaBike,而不是會話bean或實體bean;它用作我的數據訪問對象。我還在別處添加了服務。Spring配置文件稱為context,它是一個XML文件,包含容器中所有的bean以及bean所需的屬性和服務。下面讓我們看一看。
目標:
<bean id="rentaBikeTarget" class="com.springbook.HibRentABike"> <property name="storeName"> <value>Bruce's Bikes</value> </property> <property name="sessionFactory"> <ref local="sessionFactory"/> </property> <property name="transactionManager"> <ref local="transactionManager"/> </property></bean>攔截器:
<bean name="transactionInterceptor" class="org.springframework.transaction.interceptor.
TransactionInterceptor"> <property name="transactionManager"> <ref local="transactionManager"/> </property> <property name="transactionAttributeSource"> <value> com.springbook.RentABike.transferReservation= PROPAGATION_REQUIRED,-ReservationTransferException com.springbook.RentABike.save*=PROPAGATION_REQUIRED com.springbook.RentABike.*=PROPAGATION_REQUIRED,readOnly </value> </property></bean>
代理:
<bean id="rentaBike" class="org.springframework.aop.
framework.ProxyFactoryBean"> <property name="proxyInterfaces"> <value>com.springbook.RentABike</value> </property> <property name="interceptorNames"> <value>transactionInterceptor,rentaBikeTarget</value> </property></bean>
注意,有3種不同的bean:代理、目標和攔截器。代理將調用POJO以及POJO所需的任何服務。攔截器包含用于調用服務的結合代碼(glue code),還指定了如何處理目標中的每個方法。任何需要訪問RentaBike的人都要調用代理,而代理調用事務攔截器,然后事務攔截器就會開始一個事務并調用目標(POJO)。目標完成自己的工作,返回給(負責提交事務的)攔截器,然后再返回給代理和代理的調用者。
圖1. 正在進行的POJO編程
您在POJO之外構建程序并對其進行配置,而余下的工作就交給Spring。我是一名POJO程序員。
3.依賴注入有助于可測試性
通過一種稱為依賴注入(Dependency Injection,DI)的設計模式,Spring極大地提高了可測試性。當客戶依賴于某種依賴性(我們將稱之為一個服務)時,您將創建客戶的一個屬性。Spring將創建客戶和服務,然后把客戶的屬性設置為服務的值。換言之,Spring負責管理上下文中bean的生命周期,并解決依賴性。下面給出一個依賴注入的例子,沒有使用Spring。我們首先來看客戶(應用程序的基本元素):
public class CommandLineView { private RentABike rentaBike; public CommandLineView( ) {rentaBike =
new ArrayListRentABike("Bruce's Bikes"); }public void setRentABike(RentABike rentABike){ this.rentABike = rentABike;} public void printAllBikes( ) { System.out.println(rentaBike.toString( )); Iterator iter = rentaBike.getBikes().iterator( ); while(iter.hasNext( )) { Bike bike = (Bike)iter.next( ); System.out.println(bike.toString( )); } } public static final void main(String[] args) { CommandLineView clv = new CommandLineView( ); clv.printAllBikes( ); }}
接下來是服務,即模型。它是一種帶有數組表的簡單實現。它對模型(RentaBike)具有依賴性。
interface RentABike {List getBikes( );Bike getBike(String serialNo);}public class ArrayListRentABike implements RentABike { private String storeName; final List bikes = new ArrayList(); public ArrayListRentABike(String storeName) { this.storeName = storeName; bikes.add(new Bike("Shimano", "
Roadmaster", 20, "11111", 15, "Fair")); bikes.add(new Bike("Cannondale",
"F2000 XTR", 18, "22222",12,"Excellent")); bikes.add(new Bike("Trek","6000", 19, "33333",
12.4, "Fair")); } public String toString() { return "RentABike: " + storeName; } public List getBikes() { return bikes; } public Bike getBike(String serialNo) { Iterator iter = bikes.iterator(); while(iter.hasNext()) { Bike bike = (Bike)iter.next(); if(serialNo.equals(bike.getSerialNo())) return bike; } return null; }}下面是一個匯編程序。以粗體表示的代碼就是依賴注入。匯編程序實例化了服務和客戶,然后通過設置rentaBike屬性來解決依賴性。
public class RentABikeAssembler { public static final void main(String[] args) { CommandLineView clv = new CommandLineView( ); RentABike rentaBike = new ArrayListRentABike
("Bruce's Bikes"); clv.setRentaBike(rentaBike); clv.printAllBikes( ); }}
當然,Spring最終將擔任匯編程序的角色。如果把服務包裝在一個接口中,就能夠把任何接口實現注入容器中。
依賴注入使您可以編碼生產依賴性和測試依賴性。例如,本例創建了一個存根對象,從而可以更輕松地測試視圖。
您已經看到了RentaBike的Hibernate實現和數組表版本。我不想在完整的Hibernate實現上運行所有的用戶接口測試。相反,我使用數組表簡單地實現了接口。
依賴注入使您可以獲得一個生產版本(使用HibRentaBike)、一個開發版本(使用一個ArrayListRentaBike列表)和一個測試版本(使用一個mock對象)。使用Java編程時,我使用依賴注入把這些mock放到難于到達的地方中。
4. 反向控制簡化了JDBC
JDBC應用程序麻煩、冗長且乏味。一個好的抽象層會有很大幫助。Spring允許您使用查詢定制一個默認的JDBC方法和匿名內部類,以便減少大量的繁重工作。下面給出了一個簡單的JDBC例子:
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)); } });
把template.query方法當作一個默認的JDBC方法。Spring將為結果集中的每一行執行匿名內部類中的processRow方法。您在上下文中配置數據源。您不必擔心打開或關閉語句或連接、配置數據源或管理事務等諸項事宜。您不用指定外部的結果集或者在最底層管理異常,因為Spring把SQLException放到了一個未檢查異常的常見集合中。其他語言,比如Ruby和Smalltalk,通常使用包含代碼塊的反向控制,但是這在Java中并不十分常見。反向控制可以實現驚人的效果。
5. Spring在社區中的繁榮
一些開源項目不需要特別有用。例如,JUnit完成了預定的任務,如果您喜歡編程模型,它基本上擁有您所需要的一切功能。像Spring這樣的輕量級容器需要一個有活力的社區。Spring擁有您所能找到的最積極的社區之一,這對您有很多好處:
- 服務:借助于Spring,您可以找到數以百計的不同服務,從安全性到系統管理,再到工作流。對于持久性,您可以插入JDO、Hibernate、Top Link、JDBC或OJB。
- 支持與培訓:許多獨立顧問都提供Spring服務,您可以在全球范圍內獲得優質的培訓。
- 增強:Spring一年推出好幾個版本。每個版本的質量都很不錯,框架中的測試性良好,擴展組成清晰。Spring已經開始支持Hibernate 3,并會提供一個功能強大的新Web流框架,這些都包括在最新的版本中。
- 商業支持:有很多人像我一樣編寫有關Spring的書籍。迄今為止,可以找到5本專門講述Spring的書籍,還有一些書籍包含了Spring方面的內容。有幾家產品供應商也支持Spring。很多開源框架,比如Geronimo和Hibernate,對Spring提供特殊的支持。
Spring社區使得使用這個框架變得更加容易。我可以雇傭Spring開發人員,對他們進行培訓。我可以閱讀書籍來補充我的知識,并針對需要做的工作獲取組件。我找不到另一個擁有其他類似的輕 量級容器的社區。