Struts二、Spring、Hibernate 高效開發的最佳實踐(轉載)

Struts二、Spring、Hibernate 高效開發的最佳實踐

Struts二、Spring、Hibernate(SSH)是最經常使用的 Java EE Web 組件層的開發技術搭配,網絡中和許多 IT 技術書籍中都有它們的開發教程,可是一般的教程都會讓不少程序員陷入痛苦的配置與修改配置的過程。本文利用 SSH 中的技術特性,利用 Java 反射技術,按照規約優於配置的原理,基於 SSH 設定編寫了一個通用開發框架,這使得開發者能夠專一於業務邏輯的開發,而不用隨着業務增長而添加或修改任何配置,而且對於權限控制和日誌記錄也提供了方便的接口。java

  • +內容

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. 框架大體風格
圖 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 文件配置結果
圖 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 開發框架,陳述了其中的設計思想,因爲篇幅限制,文章只介紹了框架中的重要部分和重要思想,但願讀者能夠從中獲益。因爲筆者水平有限,若有錯誤,請聯繫我批評指正。

相關文章
相關標籤/搜索