SSH(Struts2+Spring+Hibernate)是最爲 Java 業界熟知的 Java EE Web 組件層的開發技術。不少人提起 Java EE,甚至都會將其誤認爲就是 SSH。不管是書籍仍是電子教程,大部分都已經千篇一概,講解各類標籤、配置的用法。許多人包括筆者在內,第一次使用 SSH 的時候,按照教程的介紹進行開發。繁瑣的配置,重複的修改配置,不判定義的參數轉換器,真的讓筆者苦不堪言。本文對 SSH 的開發模式嘗試了從新定義,按照規約優於配置的原則,利用 Java 反射、註解等技術,設計了新的一套 SSH 開發框架,應用該框架,能夠大大提升開發效率,筆者屢次將該開發框架應用在各類小型 SSH Web 應用系統之中,屢試不爽。web
閱讀文章以前,讀者須要對 SSH 的結合開發有一些瞭解,最好是有實踐的經驗,特別是對 Struts2 須要較爲了解,掌握 Struts2 自定義攔截器、自定義驗證器等的開發。另外,讀者還須要掌握一些前提技術,包括 Java 反射、Java 註解、理解事務隔離級別等。數據庫
文章首先進行框架的整體介紹,而後分點介紹各個部分的詳細設計。另外,文章還將列舉該框架中採用的技術特色及其相應目的,但願讀者能夠從中獲益。json
文章之因此說框架是通用的,由於它的思想適應任何的業務需求。按照文章介紹,按照介紹的框架搭建完代碼架構後,能夠屏蔽許多技術細節,讓開發人員專一於業務邏輯的實現,這些繁瑣的技術細節包括技術配置、權限控制、頁面跳轉、錯誤處理等等。框架大體的風格如圖 1 所示,整體來講,該框架遵照了「規約優於配置」的原則。數組
圖 1. 框架大體風格
上圖中,系統只有一個 action 配置。每一個業務操做,再也不對應一個 ActionSupport 子類,而是對應一個 ActionSupport 子類的類方法,利用 Struts2 的動態方法特性,使得業務方法擺脫了繁瑣的配置,方便的增長和刪除。全面的 result 配置,解決了各類頁面跳轉的問題。Action 方法的起名,遵照了權限與業務模型規約,讓模型選擇與權限控制交給框架來實現,同時訪問的操做名就是【方法名】 .action。下面,將一一對框架的各部分進行講解。安全
Struts2 的不動配置
該框架中,咱們建議只定義少許的類繼承於 ActionSupport,這樣可使得 struts.xml 的配置儘可能減小,甚至只進行少許配置,而不用隨着業務的增長而修改配置。清單 1 是筆者爲一個教師考勤系統定義的 Action 配置。服務器
清單 1. Struts2 的配置清單
<action name="*" method="{1}" class="Main"> <interceptor-ref name="fileUpload"> <param name="maximumSize">4073741824</param> </interceptor-ref> <interceptor-ref name="myInterceptorStack"></interceptor-ref> <result name="input">/noDir/error.jsp</result> <result type="json" name="success"></result> <result name="errorJson" type="json"></result> <result type="json" name="error"></result> <result type="stream" name="stream"> <param name="contentType">${contentType}</param> <param name="contentDisposition">fileName="${inputFileName}"</param> <param name="inputName">inputStream</param> </result> <result name="dynamic">/${url}</result> <result name="otherAction" type="redirectAction">/${url}</result> <result name="red" type="redirect">/${url}</result> </action>
清單 1 定義了不少規則。首先,該配置使用了動態方法調用技術,這可使得許多的 Action 方法能夠聲明到一個類裏,不用重複定義 Action 類,同時對業務的增長和刪除能夠簡約到對 Action 類裏方法增長和刪除,增長的 Action 方法不需進行其餘配置,若是業務被刪除,則只須要將方法註釋或者刪除,很是方便。第二,配置的 package 繼承於 json-default,也就是說這個包裏的 action 是支持 Ajax 調用的,默認的,若是返回 ERROR、SUCCESS,則會將 Action 序列化爲 JSON 返回到客戶端。咱們定義了各類的跳轉類型,包括重定向到頁面(具體的頁面由 Action 裏的 url 指定)、重定向到 Action、重定向到錯誤頁面、流類型的返回等等,這爲咱們動態的選擇返回結果數據提供了方便。那麼使用上述的配置,對咱們開發有什麼好處呢?假設有一個新的業務,咱們只須要在 Action 添加新的方法,如清單 2 所示。網絡
清單 2. 添加新業務方法
public String business() throws Exception { …business process… if (has error) { addFieldError(「error message」) return INPUT; } else { //url是action中定義的一個String變量,指定跳轉地址 url = 「business.jsp」; return 「dynamic」; } }
調用這個新的業務方法,只須要調用這個連接:http://{host}:{port}/{webapp}/business.action。正如咱們看到的,定義了新的業務邏輯方法,咱們沒有修改或添加任何配置,由於咱們的配置是完整的,考慮到了各類跳轉、錯誤狀況,同時動態方法調用特性,讓咱們能夠動態的指定業務方法。若是咱們對清單 2 中的跳轉代碼進行抽取,清單 2 會更加簡單。如清單 3 所示。session
清單 3. 抽取基礎方法後的業務方法
public String business() throws Exception { …business process… if (has error) { return redirectToErrorPage("error message") } else { return redirectToPage("business.jsp"); } }
上面的清單中,咱們抽取了 redirectToErrorPage 和 redirectToPage 方法,這樣就可使得其餘的業務方法能夠重用這些跳轉方法,整個業務過程變得清晰易懂。相似的咱們還能夠抽取出 redirectToAction(跳轉到另外一個業務方法)、redirectStream(流類型的跳轉)、redirectToAnotherPage(用於重定向的跳轉)、redirectToJson(Ajax 的跳轉)等等。這樣這些公共方法就可讓其餘的開發人員一塊兒使用。程序員能夠從跳轉、錯誤提示、重複配置 Action 的痛苦中解救出來,專一於編寫業務邏輯。
ModelDriven 的規約
有了上面的配置,咱們還不能作到徹底脫離配置。好比,咱們定義了一個 Action 類繼承於 ActionSupport,咱們知道使用 ModelDriven 能夠將用戶上傳的數據封裝到一個業務 Bean 裏,而不用直接在 Action 裏聲明變量,這很重要。我相信有不少讀者遇到過這個問題。當業務 Bean 不一樣時,也就是須要用戶上傳的數據不一樣時,咱們就要隨之添加新的 Action,這直接致使修改 struts.xml 的配置,而後還要修改 applicationContext.xml 的事務配置、Bean 的配置,哪天咱們不要這個業務了,又要重複的修改刪除配置,筆者開始時爲此事近乎抓狂,爲何咱們不能只定義一個 Action,而 ModelDriven 的模型動態改變呢?
仔細考慮 Struts2 的機制,Struts2 將客戶端的參數裝配到 ModelDriven 的模型裏,是經過「裝配攔截器」裝配的。只要咱們在裝配攔截器執行前,改變 ModelDriven 裏的模型對象就好了。這就須要咱們自定義一個攔截器,struts2 提供了這個機制。在自定義的攔截器裏,咱們根據用戶調用的 Action 方法,新建一個模型,而且將模型設置到 Action 中,這樣模型就能夠是動態的了,記住,這個攔截器須要放在 defaultStack 的前面。
一樣,新的問題是如何根據 Action 方法動態的選擇業務模型呢?難道重複的寫 if 方法嗎?固然不能這樣,動態的模型,就應該來自於動態的方法。所以定義的 action 方法須要有規約,筆者在本身的程序中,是這樣定義規約的。Action 方法是這樣組成:_$_,使用美圓符隔開(美圓符是 Java 方法合法的標識符),前部分是操做名,能夠任意取,後部分是業務模型的類名。同時,全部的業務模型,都放到指定的一個包裏,假設該包名爲 com.dw.business。那麼在自定義的攔截裏,咱們得到用戶調用的 Action 方法名,按美圓符隔開得到後半部分的類名,指定的包名(這裏是 com.dw.business)+ 類名就是業務模型類的全路徑,使用 Java 反射機制動態生成一個空的業務模型對象(因此,業務模型類必須有一個無參的構造函數),設置到 Action 裏,再交給裝配器的時候,裝配器會自動組裝這個模型。該攔截器的核心代碼如清單 4 所示。
清單 4. 攔截器清單
public String intercept(ActionInvocation ai) throws Exception { ai.addPreResultListener(this); Main action = (Main)ai.getAction(); //業務方法名 String name = ai.getInvocationContext().getName(); int lastIndex = name.lastIndexOf("$"); if (lastIndex != -1) { try { String head = "com.dw.business."; String className = name.substring(lastIndex+1, name.length()); //關鍵:動態設置業務模型 action.setModel(Class.forName(head).newInstance()); } catch (Exception e) {} } return ai.invoke(); }
有了上面的配置,基本能夠作到屏蔽大多數技術細節開發了,可是咱們還有一個問題,當在業務方法中,意外拋出了異常,struts2 默認的是返回 INPUT,按照 清單 1 的配置,發生錯誤將跳轉到 noDir/error.jsp 頁面,顯示錯誤信息,這適合在非 Ajax 的處理。可是有時候咱們但願他返回錯誤是 JSON 類型,由於 Action 的一些方法是 Ajax 的調用方式,也就是 Action 方法的執行結果須要返回 errorJson。個人解決方法是利用 Java 註解技術,定義一個新的註解,名爲 IfErrorReturnToJson,它的代碼如清單 5 所示。
清單 5. IfErrorReturnToJson 的代碼
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface IfErrorReturnToJson { }
該註解做用在方法上,也就是 Action 的業務方法,若是咱們但願某業務方法在發送錯誤時,就返回 JSON 類型,那麼咱們只須要在該方法上加上這個註解。如今,咱們如何動態的修改返回值呢。清單 2 的第二行,爲 ActionInvocation 添加了 PreResultListener,這個監聽器是在返回結果前進行一些處理,正符合咱們的需求。咱們爲自定義攔截器類實現了 PreResultListener 接口,實現了接口方法,如清單 6 所示。
清單 6. PreResultListener 的代碼
public void beforeResult(ActionInvocation ai, String result) { if (Main.INPUT.equals(result) || Main.ERROR.equals(result)) { try { String methodName = ai.getInvocationContext().getName(); Method method = Main.class.getMethod(methodName); if (method != null && method.getAnnotation( IfErrorReturnToJson.class) != null) { ai.setResultCode(Main.ERROR_JSON); } } catch (Exception e) { } } }
能夠看到,咱們在返回結果前,若是當前的返回結果是 INPUT 或者 ERROR,咱們會使用反射機制,檢查該方法是否加了 IfErrorReturnToJson 的註解,若是加了註解,則調用 ai.setResultCode(Main.ERROR_JSON); 方法,修改返回值,使得結果爲 JSON 數據類型。
事務隔離級別的規約
在 SSH 開發中,須要重點考慮的是 Spring 的事務配置。根據不一樣的業務,定義的事務隔離級別就不一樣,好比對隔離級別要求高的,就要用到 Spring 的序列化讀的隔離配置;有一些方法只是爲了權限控制,用於頁面跳轉,則不須要用到事務控制;一些操做目的是搜索,那麼該事務就是隻讀的。精細的事務配置,能夠提升業務處理的代碼效率。這時候,業務方法的命名規約就能夠起到隔離級別的控制做用,好比方法名是 *begin*( 方法名中包含 begin),則該方法沒有事務控制;方法名是 *search* 的,則該方法擁有隻讀的事務;方法名是 *seri*,則用序列化讀事務控制,提升併發安全級別。這個事務配置清單如所示。
權限規約
在一些應用中,涉及到簡單的權限管理。讀者可能會想到一些開源的中間件,如 ralasafe 這樣的開源權限組件,雖然很全面,可是學習起來須要必定的時間,熟練掌握,並配合 SSH 開發則更須要細緻的學習。當咱們只是簡單的權限控制時,咱們徹底能夠用到一些規約,來完成用戶的權限控制。在我設計的規約中,須要在數據庫中定義一張 Permission 表,表明權限 , 它與用戶表是多對一的關係,它至少有兩個字段,一個是 permissionName,一個是 permissionPrefix。permissionName 用於描述該權限,而 permissionPrefix 是咱們所關心的。咱們設計 Action 的方法,遵照 permissionPrefix_actionMethodName$BussinessBean 這樣的原則,permissionPrefix 前綴表明數據庫中權限表的 permissionPrefix 值 , 使用下劃線隔開。當用戶登陸後,會將該用戶所擁有的權限列表存放到 session 之中。當用戶試圖訪問一個 Action Method 時,會使用 ModelDriven 的規約 裏同樣的方法,截取方法名得到該方法容許的訪問權限,若是用戶的權限列表中包含了該方法的權限,則容許調用,不然不容許調用。這樣設計,雖然沒法作到數據的訪問權限,卻能夠知足一大部門的功能權限控制。這裏須要記住,權限描述能夠修改,可是權限的 permissionPrefix 不能修改,若是方法名中沒有權限標識,則表明任何用戶均可以訪問。相似的,若是一個方法多個權限均可以訪問,則能夠這樣設計方法 p1_p2_p3_methodName$BussinessBean,使用多個下劃線分割。
合理利用 Struts2 的傳參技術
基於 HTTP 協議的特殊性,上傳到 Web 服務器的數值都是字符串,而咱們的業務模型都是對象類型,有一些仍是複雜的業務對象。Struts2 提供了 Conveter 的機制,容許程序員將 String 轉換成複雜的業務對象。可是筆者剛用這個機制的時候,開始的確興奮,可是隨着業務的修改,業務模型的修改,這些 Conveter 的管理着實讓人頭疼。所以,本人認爲 Conveter 技術應該儘可能少用。我提出了一個新的方法,假設一個業務模型中,裏面包含一個 List<People> pids 屬性對象,People 裏有一個 id 屬性,咱們須要客戶端上傳 People 的 id 列表,放入 List<People> pids 之中。若是使用 Conveter,咱們在客戶端須要定義規則,如客戶端上傳 pids=1,2,3,4,6,8。而後服務器端使用 Conveter 執行分割字符串、新建 People 對象、設置 ID、添加到列表等一系列操做。聽着都頭暈不是嗎 ? 並且這相對並不安全,對程序員解析字符串的功底要求很高。咱們感謝 Struts2 的傳參機制,若是咱們這樣傳參:<input name=」pids[0].id」/><input name=」pids[1].id」/>。這兩個 input 的上傳,Struts2 會根據上傳數據的 name,自動組裝成 List<People> 對象,而且賦值給 pids,pids[0] 表明列表的第 0 個元素,這用到了 OGNL 表達式的傳參規則,它會自動識別 pids 是數組仍是列表,若是 pids 默認是空,它還會自動新建一個空的數組或列表。筆者能夠查看 OGNL 的相關教程。合理利用該技術,能夠大大縮減傳參的難度。
合理使用 Struts2 的標籤
Struts2 提供了衆多的標籤,這些標籤大體包括了 UI 標籤、數據標籤以及邏輯標籤。這裏,咱們須要理解 UI 標籤與服務器端的數據交互格式,好比 checkboxlist 標籤,傳到服務器端就是一個數組,該數組存放的是選定的 checkbox 列表的數值 , 這比使用 iterator 標籤生成 checkbox 列表容易得到數據。Doubleselect 標籤,用於生成 2 級的級聯 select,常用的是部門 - 用戶的級聯。Optiontransferselect 標籤,用於批量的遷移,好比用於批量的爲部門分配用戶。使用 struts2 的標籤能夠大大減小用戶界面的開發,以及使得用戶界面到服務器的數據傳輸方式變得很是簡單。
Action 的模型驗證
Struts2 具備一套完善的驗證機制,在 ActionSupport 類裏,能夠將模型驗證方法寫在 validate* 方法裏。若是重寫了 ActionSupport 的 validate 方法,這個 validate 會在執行全部 Action Method 前調用,執行驗證。而自定義的 validate*(* 是方法名),好比方法名是 add,則這個方法名就是 validateAdd),它只會在執行 add 方法前執行驗證。對於數據的驗證,筆者建議使用 validater 文件驗證,它更加容易配置和修改,利用現有的驗證器,能夠減小硬編碼驗證的痛苦。在上面提到的框架中,因爲使用的是動態方法機制,咱們須要在 Action 類所在的包裏,新建 validator 的 XML 文件,名字是 { 類名 }-{ 方法名 }-validation.xml(若是不是動態的方法,則 { 類名 }-validation.xml 就足夠了)。最後的結果如圖 2 所示。
圖 2. Validator 文件配置結果
具體的配置內容,讀者能夠搜索 Struts2 的 validation 框架教程。
自定義業務模型驗證器
Struts2 提供的驗證器,包括 date、required、requiredstring 等等,這些能夠歸於數據驗證。而對於特定的業務模型驗證,則比較複雜,所以,在該框架中,能夠自定義一個業務驗證器,這是 struts2 支持的,它負責對業務模型進行驗證,好比能夠驗證用戶上傳的用戶 ID 的用戶是否存在,這能夠在很大程度上保證系統的安全性。驗證器的代碼清單 7 以下面所示。
清單 7. 業務驗證器代碼
public class BusinessValidator extends FieldValidatorSupport { private String property = null; public String getProperty() { return property; } public void setProperty(String property) { this.property = property; } @Override public void validate(Object exist) throws ValidationException { String fieldName = getFieldName(); Object fieldValue = getFieldValue(fieldName, exist); if (fieldValue != null && fieldValue instanceof Integer && ((Integer)fieldValue) <= 0) { addFieldError("message", "上傳的ID,該數據是不存在!"); } else if (fieldValue == null) { addFieldError("message", "上傳的ID,該數據是不存在!"); } else { if (exist != null && exist instanceof Main && ((Main)exist).getModel() instanceof IChecker) { Main pa = (Main)exist; IChecker e = (IChecker)pa.getModel(); boolean isRight = e.checkOk(property, pa); if (!isRight) { addFieldError("message", "上傳的ID,該數據是不存在!"); } else { pa.getPubDao().getHibernateTemplate().clear(); } } } } }
清單 7 中,用到一個 IChecker 接口,須要驗證的業務模型須要實現 IChecker 接口,在接口實現方法中實現業務驗證過程,錯誤的話返回 false。
在 src 目錄(或者 WEB-INF 下的 class 目錄),添加 validater.xml 文件,在其餘自帶的驗證器後,添加業務驗證器配置,命名爲 check。
清單 8. 驗證器的配置
<validators> …… <validator name="check" class="com.attendance.action.BusinessValidator"/> </validators>
接下來就可使用這個驗證器,在圖 2 中的 validation 文件裏,添加如清單 9 的配置。
清單 9. 驗證器的使用
<field-validator type="check" short-circuit="true"> <param name="property">department</param> <message> 該部門不存在 !</message> </field-validator>
利用 JEE Eclipse 生成 Hibernate 的 JPA 模型
IBM 提供了 JEE 版的 Eclipse,專門用於開發 Java EE 的應用,它提供了從數據庫中生成符合 JPA 規範的數據模型的能力。Hibernate3 之後,支持了 JPA 規範(不是徹底支持,可是已經較爲全面),利用 JEE Eclipse 的生成能力,就能夠避免繁瑣的 hbm 文件的配置。同時,JPA 的規範裏,還能夠提供 NamedQuery、OrderBy 等註解,對於複雜的數據庫,該規範能夠減小開發時間。
UI 重用
Struts2 的 action 標籤,用於調用某個 action。這個標籤筆者認爲很是有用,尤爲體如今 UI 重用中,好比用戶管理中,在不少個界面裏,都容許用戶信息修改和用戶刪除,那麼咱們就能夠將用戶信息修改和用戶刪除的頁面代碼以及 JavaScript 放在一個 JSP 裏,同時定義一個 Action 方法 ( 名爲 A),爲這個 JSP 執行初始化。接下來在容許進行用戶修改和刪除的界面,都用 s:action 標籤調用這個 A 方法,設置 executeResult 爲 true,將用戶修改和刪除的代碼包含在頁面裏。這樣就實現了 Action 的重用。若是重用的界面不須要服務器的初始化,這直接使用 jsp:include 或 s:include 引用重用的 JSP。
小結
本文經過一系列的講解,講述瞭如何搭建一個通用的 SSH 開發框架,陳述了其中的設計思想,因爲篇幅限制,文章只介紹了框架中的重要部分和重要思想,但願讀者能夠從中獲益。因爲筆者水平有限,若有錯誤,請聯繫我批評指正。