摘要 把你的現有Struts應用程序移植到Stripes框架能夠簡化Web開發,並且這一移植過程要比你想象的更為容易。
一、引言
把一個現有Java Web應用程序移植到一種新框架可能不是大多數開發者最感興趣的問題。除了要花費時間學習一種新的Web框架外,例如標簽、國際化系統和校驗等繁重的轉化過程可能會迫使每一位程序員考慮再三。我最近就面臨這樣的一個挑戰-從Struts進行移植。
在決定移植一個應用程序前,應該首先問一下"為何不使用現在的框架?"在我看來,Struts是一種穩定的具有良好文檔的框架,並且有一大批開發者社區成員,但是其配置很麻煩,而且其表單、行為、應用程序流和校驗的分離有時會帶來很多麻煩。這種情形在我的Struts應用程序不斷變大時越發糟糕。最後,純粹從一種維護的角度,我決定把它移植到一種新的框架。
開始,我認為沒有一種框架(Java ServerFaces,Tapestry,WebWorks,Spring MVC)值得從Struts遷移向其遷移。例如JSF這樣的框架看上去極不友好。其它的,例如Tapestry和WebWorks,涉及到整頁整頁的看上去令人麻煩的國際化系統。而從配置角度來看,Spring MVC看上去並不比Struts好多少。我選擇的框架應該僅需適當的學習時間,還要與移植效益相稱;而且,它還一定要使我編碼、排錯與維護更為容易。
二、發現Stripes框架
後來,我偶然發現了Stripes框架。就象Java社區中的許多發燒友一樣,我一直追隨著Ruby on Rails(RoR)現象。依我看來,Stripes是最接近於RoR哲學的Java MVC框架-簡單,漂亮,並且要求最小的配置。除了它的簡潔外,象我這樣一位Struts程序員,Stripes非常適合我的口味。應用程序流和許多命名慣例都與之十分相似。Stripes中的ActionBeans就象Strut的Actions,而ForwardResolutions極象ActionForwards。因此,使用這一框架,我不必拋棄我所有以前的Struts知識。
另外吸引我的是Stripes文檔。象框架本身一樣,文檔也是干淨、清潔而簡練。其標簽庫文檔和API都具有良好的歸檔,而且該框架的每一種特征幾乎都有相應的示例源碼。這些優秀的文檔再加上我的現有Struts知識使我堅信,我可以快速地掌握這種Stripes框架。
值得注意的是,Stripes還包括另外一些使其成為一種良好的AJAX平台的特征,例如它提供了一種流式方案,該方案允許對AJAX實現進行改進的錯誤處理。然而,對於我來說,最終的決定因素還是我能夠清楚地看到它會使我的生活更容易些。我估計,在我的應用程序的行為/配置/校驗部分,我只需使用約一半的代碼就夠了。更少的代碼意味著了更少的錯誤、更快的開發時間和更容易的糾錯。
三、移植過程
我從視圖層開始移植,然後再向行為層移植。事實上,我也沒有很明確的邏輯思路;只是必須從某處開始,而視圖部分看起來更適合於作為一個起始點。
(一) JavaServer Pages
就象Struts一樣,Stripes使用JSP來實現其視圖層。我吃驚地發現,Stripes標簽庫非常類似於Struts的HTML taglib。事實上,我能夠使用這種統一替換方式來升級我的許多標簽。
Stripes依賴於JSTL實現JSP視圖中的邏輯。我在我的應用程序中混合使用了Struts邏輯標簽和JSTL。通過把我的所有邏輯標簽移植到JSTL,我能夠利用JSTL的優越的if/else和case語句的能力處理,它們可能是很原始的或者根本不存在於Struts邏輯taglib中。
(二) 國際化
接下來,我要移植我的Struts的消息資源。在配置端,所有要求的操作就是重命名我的Struts消息資源文件。在我的JSP中,我能夠使用統一替換方式把我的所有Struts message標簽(例如,<bean:message key="buttons.save"/>)替換為JSTL格式標簽(例如,<fmt:message key="buttons.save"/>)。這種JSTL格式標簽還支持可用於Struts中的消息資源綁定。
(三) 表單
我的移植的最有意義的部分是去掉了我的Struts Action表單,這是在Action類中進行的,要求大量的XML標記和冗長的轉換,如下例所示:
<form-bean name="employeeUpdateForm" type="org.apache.struts.validator.DynaValidatorForm">
<form-property name="employeeid" type="java.lang.Long" />
<form-property name="firstname" type="java.lang.String" />
<form-property name="lastname" type="java.lang.String" />
<form-property name="phone" type="java.lang.String" />
<form-property name="email" type="java.lang.String" />
<form-property name="phone" type="java.lang.String" />
<form-property name="socialsecurity" type="java.lang.String" />
<form-property name="birthdate" type="java.lang.String" />
<form-property name="salary " type="java.lang.String" />
</form-bean>
public ActionForward executeAction(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) throws Exception {
Employee employee=new Employee();
DynaValidatorForm eaf = (DynaValidatorForm) form;
employee.setFirstname(eaf.getString("firstname"));
employee.setLastname(eaf.getString("lastname"));
employee.setPhone (eaf.getString("phone"));
employee.setEmail (eaf.getString("email"));
employee.setEmployeeid ((Integer)eaf.get("employeeid"));
employee.setSocialsecurity(Long.parseLong(eaf.getString("socialsecurity")));
employee.setBirthdate(MyUtils.convertStringToDate(eaf.getString("birthdate")));
employee.setSalary(MyUtils.convertStringToBigDecimal(eaf.getString("salary")));
EmployeeDAOService.updateEmployee(employee);
return new ActionForward(mapping.getForward());
}
另一方面,Stripes表單處理允許你把你的域對象用作一個表單使用:
public class UpdateEmployeeActionBean implements ActionBean {
private ActionBeanContext context;
private Employee employee;
public ActionBeanContext getContext() {
return context;
}
public void setContext(ActionBeanContext context) {
this.context = context;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
public Employee getEmployee() {
return this.employee;
}
@DefaultHandler
public Resolution update() {
EmployeeDAOService.updateEmployee(employee);
return new ForwardResolution("/employees/updateEmployee.jsp");
}
}
在大多數情況中,我能夠嵌入我的域對象的一個副本作為我的Stripes ActionBean類的一個屬性並且僅包括涉及把該對象移入/移出我的持久層的代碼部分。我把所有表單處理都交給了Struts Action來實現,包括初始化配置、把表單強制轉換成適當的類以及從域對象中來回轉換數據(約30%的代碼是在大多數Action類中實現的)。在Stripes中並不需要這樣。
簡言之,把域對象作為你的ActionBean類的一個屬性嵌入,為該類提供了getter和setter方法。總之,所有有趣的地方(包括列表)都在HTML視圖的表單中體現出來。
所有我對表單的操作也都可以通過查詢串參數方式來實現。我僅把這些參數作為我的ActionBean的一個屬性,而如果它們是請求的一部分的話,可以把它們自動地復制到相應的域中。
(四) 校驗
與表單或標簽移植相比,把Struts校驗移植到Stripes要求更多的工作。在我的應用程序中,我必須在Stripes ActionBean類內部使用Java 5.0注解重寫在validation.xml文件中的校驗配置。Stripes還為你提供一種良好的基於類型的校驗。當用戶輸入錯誤值時,不需要用戶進行任何配置,Stripes就可以把HTML表單返回給他們(例如,在一個數字或日期域中的字符)。表單能夠被自動返回並帶有一條向用戶友好顯示的消息,最後出錯域被高亮顯示。
(五) 應用程序流程
轉換我的Struts應用程序的控制流可能是唯一遠離Struts思維的一個地方。在Struts中,控制流(URL請求綁定、行為和結果視圖)都以XML標記形式生成並且被集中放到struts-config.xml文件中。在行為層外進行生成使Struts綁定更為靈活。它們沒有被硬編碼到行為層中,而單個行為可以容易地與不同的輸入URL和轉發進行耦合。這種方式的不好的地方在於,Struts配置量可能會急劇增加而成為麻煩。控制流與行為層的分離還會使在整個請求周期中的調試相當困難。
為此,Stripes共提供了三種不同方式以便把請求映射到行為層:
1. 使用注解把一個ActionBean顯式綁定到一個URL;
2. 允許Stripes在啟動期間基於ActionBean類路徑和應用程序URL之間的相似性猜測它的ActionBean的綁定;
3. 類路徑通過使用Stripes useBean標簽,把一個JSP綁定到任何ActionBean,或調用應用程序中一個Java類的任何方法。
盡管與Struts配置相比,前兩種方法似乎有點"硬編碼"特征,但是useBean標簽提供了大量的靈活性。借助於該標簽,JSP可以存取多個ActionBean或類以得到其所需要的內容。
四、結論
當選擇一個新框架時,遷移的容易性(既包括學習新框架方面,也包括移植你的現有代碼方面)是要考慮的要素之一,但是不應該過多地強調。是的,你可能已經在學習一種現有框架上做出很大的投資並且在你的下一個MVC平台上保留這些投資的一部分更好一些。而且,如果你能夠在幾周而不是在幾個月內移植完你的應用程序則最好不過。但是不管問題是多麼容易或是多麼愉快,你還是要首先應該決定是否目標能夠滿足你的真正要求。對於我來說,能夠把幾乎一半的代碼放到我的行為層中而把表單、配置和校驗放到一起是我最關心的問題。Stripe文檔的質量及其它問題則為次要。