可愛的javaee:非框架架構漫談(控制器篇)

javaonejcy發表於2009-10-28
概述

你可以說可愛的php,可愛的ror,可愛的python,甚至可愛的.net,但是javaee?他太複雜了。相比其他兩種技術,javaee的技術體系更全面、更規整也更復雜,他的複雜性也讓很多廠商望而止步,寧可選擇簡單甚至簡陋的php,這充分說明快速開發是這個時代最迫切的需求。

javaee的servlet、javabean、jdbc規範給了我們元件和容器的唯一標準,而更高階的支援,jsf、jdo規範卻沒能給予我們唯一的框架級標準,他們被認可的程度遠低於相同領域的開源框架。儘管開源社群給了我們最豐富的選擇,但是相比.net、php、ror的全棧式服務,javaee開發者必須DIY。DIY不但需要時間而且需要冒險,這種發燒友做的事情是企業所不願意做的。一段時間以來,公司javaee方向的招聘幾乎清一色的要求struts、spring、hibernate這幾種主流框架的能力就是一種證明。

javaee的開發往往避免不了配置之旅,儘管很多框架都有自動生成工具,但是,面對一箇中型專案,仍然容易造成混亂。配置使你無論在開發、測試、整合還是維護的時都要配置程式碼兩頭看。配置給了框架一個注入服務的切入點,但是對人並無優雅可言。ror給了我們啟發,儘管企業開發是複雜的,但是大多數的需求都是通用的,事實證明,ror把這部分通用性用約定的方式抽象得很好。其實javaee並不缺乏約定,因為他本身就是建立於一系列規範的基礎之上,而規範就是約定。所以,javaee實際上有機會成為相對簡潔的開發技術,只不過由於種種原因,這種規範並未出現。

在眾多的javaee開發框架中,struts+spring+hibernate有著黃金組合的美譽,用的人多,會的人多,就算是沒出校門的學生,也知道學會ssh的重要性。但是學會和學懂是兩碼事,對於一箇中型專案,ssh就成了一柄雙刃劍,需要由高水平的設計師引路,才能披荊斬棘。spring+hibernate給了設計者廣闊的空間,而設計需要因專案的前進而演進,如果你的專案進度緊張,人手不足,設計質量就難以保障,給系統帶來隱患。

“任何優秀的語言,都可以幫助開發者寫出優秀的程式碼,但不能阻止開發者寫出糟糕的程式碼”。在這一點上,無論是javaee,.net,ror,php都不會例外。而開發框架就像是“一間有很多屋樑的房子”,“框架的強大之處不是他能讓你做什麼,而是他不能讓你做什麼”,其實如同語言一樣,框架雖然可以給予開發一定程度的規範指導,但是這種指導仍然是有限的,這真應了那句老話:事在人為。

本文試圖探討如何簡化javaee開發中不必要的複雜,並給出的是一個不使用任何框架的架構模型,讓我們看看僅僅透過用編碼約定,結構設計和使用方式的組合能不能滿足專案開發的主要需求—短期培訓,降低隱患和快速開發。

問題的源頭

應用軟體開發是複雜的,但其基本模型至為簡單,請求-處理-響應。對應於軟體的層次結構就是:請求-Cortrol(C);處理-Model(M);響應-View(V)。在早期的javaee應用中,servlet負責C,javabean和jdbc在M,jsp是V。這些就是javaee的基礎設施,他們職責劃分的方式被稱為JSP Model2,已經可以滿足web開發的基本需要,javaee的開發始終都圍繞著這幾項主要技術,框架也不例外。以下的內容,將從這些技術的應用與不足說起,然後介紹主流框架的解決方案,之後再介紹我們不用框架的處理方式。

(C)選擇控制器

基礎規範的不足

任何web應用,處理請求之後返回響應是必須的環節,如果編碼規範,傳統的響應就是轉向到某個頁面,servlet處理轉向有兩種方式,其中request轉向隱藏著重複提交的問題,response重定向帶來引數傳遞的編碼解碼的問題,同時眾多的轉向地址直接寫在servlet中也十分不雅,另外,jsp和javabean有一種出色的關聯技術,就是在jsp裡可以把來自請求表單的資料自動拼裝到javabean中。糟糕的是,這麼有用的技術卻無法在servlet中使用,所以Model2缺乏對錶單資料的自動轉換處理。servlet有這些不足很好理解,因為servlet畢竟是較早出現的技術,他的職責只是將(http)請求轉化為物件導向的檢視和輸出響應而已,由於他是低階元件,所以部分功能的缺失是正常的。不過這就讓servlet成為了Model2最薄弱的一環。
開發框架的解決方案

由於以上需求是共性的,所以編寫一個通用框架就成為了很多人努力的事情,struts很快推出並且很快流行。我們先來看一看struts的特性:

前端控制器:struts使用一個servlet作為前端控制器,所有請求先經過這裡,再分派給配置指定的action(這裡是指行為,而不是具體的Action),意圖是以一箇中間層將檢視層和控制層解耦,這種思路帶來了三種可能的好處:1 檢視和控制分離,所以可以選擇不同的檢視技術,比如檢視模板既可以用jsp,也可以用Volecity、FreeMarker;2 可以對所有請求預處理,和後處理(webwork);3 可以將響應的轉向地址管理起來。前端控制器也存在一種直接的不足:配置繁瑣。

ActionForm:struts主要是一個控制層框架,所以他並不意圖深入到模型層,ActionForm是一種無奈的選擇,儘管提供了表單資料到javabean的轉換,但是遺憾的是這個javabean並不能直接使用,還要手工的轉換為模型javabean,使得ActionForm的位置有些尷尬。

國際化支援、標籤庫和全域性異常:國際化和標籤庫都是struts的亮點,不過全域性異常作用有限。

我們的選擇

javaee的控制器必然是一個servlet,我們也不能例外,因為我們必須要執行在servlet容器之中。不過,我們選擇的是servlet的演進版本-jsp。別誤會,我們並不是要退回到JSP Model1。一個典型的示例是,如果我有一個員工資訊錄入的功能點,那麼為了實現這個功能,我可以建立下面兩個檔案:
worker_input.jsp
worker_inputOper.jsp
worker_input.jsp裡不寫控制程式碼,worker_inuptOper.jsp裡也不寫檢視程式碼,這種用法實際是JSP Model1和JSP Model2的綜合體。這樣做最大的好處就是,免去配置的煩惱,但是等等..前端控制器呢?我們的中間層呢?

考慮一下,你有一個企業資訊的表單,表單中有一個企業名稱域,對這個域的要求是不能在已有企業中重名,域旁邊有一個按鈕,業務員可以透過點選這個按鈕獲得錄入企業名稱是否重複的提示。如果是傳統方式,點選按鈕將導致一個頁面提交,如果用struts,將要配置這個action處理之後轉向的URL地址,這就是傳統web應用的響應方式-基於URL地址的頁面導航。

web2.0來了,ajax來了,非同步請求的出現徹底顛覆了傳統的web互動模型。對於ajax應用而言,伺服器端返回響應只需要out.print,請求從哪來,回哪去,轉向(如果需要)和更新檢視的任務都交給了客戶端指令碼,也就是說,基於非同步互動模式的web應用,根本就沒有需要配置的result URL路徑。這樣,頁面導航的問題就自動解決了。而對於預處理,我們可以用filter替代。所以,我們完全可以和前端控制器說:再見。

由於客戶端技術的趨勢,在webappdemo中我們將全面使用ajax。也許你會說,如果客戶端瀏覽器禁用指令碼呢?這個擔心在如今已經沒有必要,你可以訪問開心或者噹噹,看看禁用指令碼他們能不能工作。時代在進步,富客戶RIA是必然的選擇。

使用jsp作為控制器,還使我們得到了另一個關鍵的特性,那就是從form表單資料到javabean的自動建立和輸入,使javabean本身既是模型也是DTO,再也不必象ActionForm那樣還要手工轉換。這裡還有一個隱含的好處,就是強制統一了表單域名和模型屬性名,不然,有可能出現這樣的情況:表單域:child_center;模型屬性:branch。以下是worker_inputOper.jsp的寫法:

// 在此輸入java程式碼
<jsp:useBean id="worker" class="webappdemo.worker.entity.Worker" scope="page"/>
<jsp:setProperty name="worker" property="*"/>
<%
	response.setContentType("text/x-json;charset=UTF-8");
	response.setHeader("Cache-Control", "no-cache");
	
	String method = request.getParameter("method");
	
	if("save".equals(method)){
		
		EntityService es = new EntityService();
		Message m = es.add(worker);
		out.print(new JSONObject().put(m.isSucceed()?"succeed":"error", m.getMessage()));
		
		return;
	}
%>
<p class="indent">

可以看出,只需將實體類名引入標籤,我們就可以獲得自動拼裝的Worker物件。對於複雜物件或複合物件,由於request裡同樣有我們需要的所有請求引數,所以你可以在自動建立的javabean基礎上修改部分屬性,以符合業務需要。

程式碼還展示了基於“method”的用法,這只是一個字串,用來告訴oper jsp要用哪個方法來處理請求,這類似於ror控制器內部定義的方法以及struts的DispatchAction但比他更靈活,變通的解決了jsp的請求不能直接面向方法的不足。

在呼叫服務處理請求之後,worker_inputOper.jsp將處理結果out.print回客戶端,這句程式碼的意思是新建一個JSON物件,將處理結果新增進去,然後輸出這個物件,方便客戶端js指令碼解析。JSON物件可以增加多個處理結果,只要他們的key不同就可以。在實際應用中,往往返回處理訊息,或者html檢視的字串。最後別忘了return;否則程式仍然會向下進行。

如果你的專案需要國際化,我們可以使用fmt標籤,而對於反饋訊息的國際化,我們也許就需要建立一個全域性MessageSource物件了,這個問題在webappdemo中沒有涉及,因為筆者認為這不是普遍需求。
對於異常處理,其實jsp已經提供了簡單的機制,我們可以在web.xml中配置:

// 在此輸入java程式碼
<error-page>
	<error-code>404</error-code>
	<location>/404.jsp</location>
</error-page>
<error-page>
	<error-code>500</error-code>
	<location>/500.jsp</location>
</error-page>
<p class="indent">

這種簡單的處理其實正是我們需要的全部,因為筆者認為,web應用的系統錯誤和java的異常沒有區別,即檢測錯誤和執行時錯誤。在web2.0時代,所有的錯誤都應該被捕獲,並且把內容經處理後在使用者輸入位置反饋給使用者,而不應該重新定向。執行時錯誤屬於系統bug,是需要修復的程式碼級錯誤,這種錯誤是真正的“意外”,所以我們用定製的錯誤頁面反饋給使用者就可以了。

綜上所述,我們用ajax+jsp+控制jsp的方式代替了servlet或者action,擺脫了前端控制器,用模型javabean代替了過程javabean ActionForm,這些使用方式使我們不需要配置即可以開發應用程式,除了ajax是相對新概念外不需要額外學習框架技術也是他的優點。

[該貼被javaonejcy於2009-10-28 17:44修改過]

相關文章