以前的一系列文章主要介紹了有關Struts2的一些基本用法和部分的簡單原理,可是始終沒有介紹有關攔截器的相關內容,從本篇開始咱們將從另外一個角度去深刻理解框架的使用,核心仍是攔截器,但本篇首先來介紹下有關框架中類型轉換的相關內容。主要包含如下幾小節:html
1、類型轉換的使用場景
何謂類型轉換?類型轉換就是指咱們在客戶端使用GET/POST或者action標籤的轉發到某個具體的Action實例中的時候,咱們傳入的參數能夠自動轉換爲Action實例的實例屬性的值的一個過程。下面看一個熟悉的例子,回顧下:web
//login登陸頁面 <body> <form method="post" action="login"> 姓名:<input type="text" name="username" /><br /> 密碼:<input type="password" name="password" /><br /> <input type="submit" value="提交"/> </form> </body>
//login public class LoginAction extends ActionSupport { private String username; private int password; public void setUsername(String name){ this.username = name; } public String getUsername(){ return this.username; } public void setPassword(int pa){this.password = pa;} public int getPassword(){ return this.password; } public String execute() throws Exception{ return SUCCESS; } }
//index頁面 <body> <h1>this is the index page</h1> <p><s:property value="username"/></p> <p><s:property value="password"/></p> <br/> <s:debug/> </body>
咱們首先在login頁面輸入兩個表單的數值,而後轉發到LoginAction ,執行execute方法,拉取index頁面的內容,輸出結果以下:數組
咱們雖然沒有顯式的爲LoginAction 的兩個實例屬性賦值,可是在index頁面中咱們依然能夠獲取到該屬性的值,他們的值對應於login表單頁面提交過來的值,也就是說從表單頁面提交到LoginAction 的時候會自動根據名稱傳值。這就是類型轉換,從表單頁面的String類型轉換爲LoginAction 中對應的屬性的類型,可是這種自動轉換並非老是生效的,具體咱們接着看。框架
2、Struts2內默認轉換器
表單中全部輸入的值都將做爲String類型提交到相應的Action,至於如何將這些String類型轉換爲Action中的屬性的類型是須要作一些判斷的,Struts2中默認有一個類型轉換器,能夠幫助咱們完成大部分的自動轉換操做。其支持的從String類型轉換的目標類型以下:jsp
對於咱們在Action中聲明的屬性的類型,若是是以上的這些類型的話,那麼從客戶端提交的過來的字符串就能夠默認使用該機制自動轉換成對應的類型,完成自動賦值。若是不是上述的類型,那麼就須要自定義類型轉換器來實現顯式的轉換類型,該內容後文介紹。此處只須要知道Action中的屬性的類型爲上述的幾種,則從表單頁面傳入的數值會自動根據屬性名自動完成賦值。post
3、基於OGNL的類型轉換
對於非基本類型,咱們使用默認的轉換機制是不能解決問題的,例如修改上述的LoginAction:this
//其中walker是一個符合Javabean規範的類,其具備兩個屬性name和age public class LoginAction extends ActionSupport { private Walker walker; public void setWalker(Walker w){ this.walker =w; } public Walker getWalker(){ return this.walker; } public String execute() throws Exception{ return SUCCESS; } }
若是Action實例的一個屬性是咱們自定義的類型,那麼login表單頁面原有的代碼確定是不能生效的,由於你指定的username和password在Action實例中是沒有的。那麼咱們怎麼將一個字符串賦值給Action實例屬性呢?ognl語法是能夠作到的,例如:debug
//login頁面,使用表單標籤 <body> <s:form method="post" action="/login"> <s:textfield name="walker.username" label="用戶名"/> <s:textfield name="walker.age" label="年齡"/> <s:submit value="提交"/> </s:form> </body>
//index頁面 <body> <h1>this is the index page</h1> <p><s:property value="walker.getUsername()"/></p> <p><s:property value="walker.getAge()"/></p> <br/> <s:debug/> </body>
咱們在login頁面使用ognl語法,walker.username指定了爲Action實例屬性walker的username屬性傳值,walker.age指定了爲Action實例屬性的walker的age屬性傳值。其實咱們到這裏能夠看出來,使用ognl語法能夠實現非基本類型的轉換,實際上仍是將問題轉化到咱們討論的第一種狀況,也就是把這麼一個問題:如何將一個String類型轉換爲非基本類型,轉化爲了:如何把一個String類型轉化爲非基本類型的屬性的類型。而這種問題Struts已經幫咱們解決了。下面是上述程序的運行截圖:code
有關該分類還須要說明一點的是:對於list和map集合,這裏的操做是有些變化的。咱們詳細看看:orm
//修改屬性爲一個list集合 public class LoginAction extends ActionSupport { private List<Walker> list; public void setList(List<Walker> w){ this.list =w; } public List<Walker> getList(){ return this.list; } public String execute() throws Exception{ return SUCCESS; } }
//修改了的login表單頁面 <body> <s:form method="post" action="/login"> <s:textfield name="list[0].username" label="用戶名"/> <s:textfield name="list[0].age" label="年齡"/> <s:textfield name="list[1].username" label="用戶名"/> <s:textfield name="list[1].age" label="年齡"/> <s:submit value="提交"/> </s:form> </body>
LoginAction 中的改動沒什麼須要說的,至於login頁面中使用了list[0].username表示爲Action屬性list的第一個元素的username傳值,相同的,list[0].age表示爲Action屬性的list的第一個元素的age屬性傳值。index頁面遍歷list的代碼沒有貼出,由於比較簡單。本質上也是和上述介紹的同樣,最後都是使用了Struts的默認轉換器。下面是輸出結果:
上述介紹的是list集合做爲Action屬性的狀況,對於map集合做爲Action實例屬性的狀況實際上是相似的,只是在傳值和遍歷的方面有細微差異。
//修改後的LoginAction 頁面 public class LoginAction extends ActionSupport { private Map<String,Walker> map; public void setMap(Map<String,Walker> w){ this.map =w; } public Map<String,Walker> getMap(){ return this.map; } public String execute() throws Exception{ return SUCCESS; } }
//login頁面的表單傳值 <body> <s:form method="post" action="/login"> <s:textfield name="map['1'].username" label="用戶名"/> <s:textfield name="map['1'].age" label="年齡"/> <s:textfield name="map['2'].username" label="用戶名"/> <s:textfield name="map['2'].age" label="年齡"/> <s:submit value="提交"/> </s:form> </body>
map['1'].username表示爲Action實例的map屬性添加一條信息:key爲1,key爲1的value值爲walker的username屬性的值爲該文本框的值。age屬性相似。
4、自定義類型轉換
上一小節,咱們使用ognl語法能夠完成對非基本類型的轉換,可是本質上仍是調用了Struts的默認轉換器。雖然利用ognl語法,咱們能夠完成大部分的類型轉換,可是在某些極端狀況下,這種方式是不能解決問題的,此時咱們能夠考慮自定義一個類型轉換器來解析類型轉換。想要自定義一個類型轉換器就必須繼承TypeConverter這個接口並實現其中的惟一方法:
public abstract Object convertValue(Map<String, Object> paramMap, Object paramObject1, Member paramMember, String paramString, Object paramObject2, Class paramClass);
該方法至關複雜,光參數就有七個。好在框架爲咱們提供了一個默認實現類:DefaultTypeConverter。該抽象類實現了TypeConverter接口並默認實現了一些方法,咱們在自定義本身的類型轉換器的時候只須要重寫該類的某個方法便可,大大下降了咱們的開發成本。固然咱們能夠進去簡單看看DefaultTypeConverter抽象類的內部結構:
public Object convertValue(Map<String, Object> context, Object target, Member member, String propertyName, Object value, Class toType) { return convertValue(context, value, toType); } public Object convertValue(Map<String, Object> context, Object value, Class toType) { return convertValue(value, toType); } public Object convertValue(Object value, Class toType) { ...... }
代碼比較多,只貼出一些關鍵性的代碼。該抽象類爲咱們提供了三個convertValue方法重載,他們之間的關係就是:參數多的重載調用參數少的。最後convertValue(Object value, Class toType)方法提供了默認實現,若是目標類型toType是咱們上述介紹的幾種基本類型,那麼直接將value轉換成該類型返回,若是value是個數組類型而且目標類型toType也是個數組類型,那麼會獲取value中的每一個元素遞歸的調用該方法,爲當前元素實現類型轉換,最後返回toType類型。這裏可能文字說明不能很明朗的會意你,你須要輔助着源代碼。
對於默認的實現,咱們仍是不能完成某些自定義類型的轉換,畢竟它只是一個默認實現。由於當系統沒法使用默認類型轉換器實現類型的轉換的時候就會去查找是否有自定義的類型轉換器,有則會自動調用convertValue最多參數的重載。因此咱們能夠重寫convertValue的任意一個重載來完成自定義類型轉換器。下面咱們看一段代碼:
public class WalkerConvert extends DefaultTypeConverter { public Object convertValue(Object value, Class toType){ if(toType == Walker.class){ String[] params = (String[])value; Walker w = new Walker(); String[] user = params[0].split(","); w.setUsername(user[0]); w.setAge(Integer.valueOf(user[1])); return w; } return null; } }
這裏咱們定義了一個WalkerConvert 類繼承自DefaultTypeConverter 並重寫了該convertValue方法。該方法具備兩個參數,第一個參數表示原類型,第二個參數表示目標類型。這裏須要對第一個參數value作一點說明,該參數的值其實是一個String數組,通常狀況下咱們的參數被存放在索引位置爲0的元素中,其他元素內容只有在表單是下拉框的時候將全部下拉框中的選項傳過來(若是不使用下拉框通常只用到該數組的第一個元素)。上述代碼中,咱們將傳入的字符串按照逗號分隔,前半部分是username的值,後半部分是age的值,咱們看下結果圖:
當咱們從表單中提交咱們填入的字符串,到了Action中以後,因爲默認轉換器不能完成自動轉換,因而框架查找是否具備自定義的轉換器,找到以後調用convertValue返回的結果就是屬性walker的值,最後咱們在index頁面輸出該walker屬性的兩個子屬性。該方法中的操做咱們已經介紹過了,此處再也不贅述。固然此處還有一些疑問,例如:定義的該WalkerConvert 該放在什麼位置,以及它是如何被web應用加載的?等。這些問題咱們將在下一小節詳細說明。
5、註冊類型轉換器
帶着上一小節的疑問,咱們看如何讓web容器知道咱們的自定義轉換器,並在沒法使用默認轉換器實現轉換的時候查找到咱們本身定義的轉換器。註冊一個類型轉換器主要有如下三種方式:
局部註冊一個類型轉換器實際上只能對某個Action的屬性生效。首先咱們須要建立一個局部類型轉換文件,該文件的命名規則以下:
ActionName-conversion.properties
例如咱們在LoginAction中有一個屬性爲Walker類型,咱們須要註冊一個該Action的轉換器,則命名以下:
LoginAction-conversion.properties
這是該文件的文件名,對於文件內容,好比咱們須要爲Walker類型註冊轉換器,則能夠在上述文件中添加以下一行代碼:
// 屬性名=轉換器類的位置 walker=MyPackage.WalkerConvert
最後須要補充一點的是,建立的該文件應該和對應的Action位於同一個包下,這是方便框架搜索。
若是想要註冊一個全局範圍的類型轉換器,那麼對於該應用的任意一個Action中,只要存在指定的屬性,都會調用該轉換器實現轉換,這是與局部轉換器不一樣之處。註冊全局類型轉換器須要提供一個文件,該文件名稱以下:
xwork-convertion.properties
爲某個屬性註冊類型轉換器的代碼是同樣的,只是該文件能夠在全局使用。以上便簡單介紹了註冊類型轉換器的兩種方式,至於使用註解註冊也是很簡單的。此時,咱們知道一旦表單頁面傳入的字符串不能被默認轉換器自動轉換成相應的類型,那麼會查找相應的自定義轉換器,返回該屬性的值。
6、類型轉換的錯誤處理
最後有關類型轉換這塊還有一個錯誤處理的內容沒有介紹,其實框架爲咱們在攔截器棧中註冊了一個攔截器:convertionError。該攔截器專門用於攔截類型轉換異常,一旦該攔截器攔截到異常產生則會封裝全部的異常信息到ActionContext中,而後跳轉到input處理結果頁面,因此通常咱們在爲Action添加處理結果的時候會爲其添加一個input頁面。下面看一個錯誤處理的示例:
//input.jsp <html> <head> <title></title> </head> <body> <h1>this is the input page</h1> </body> </html>
咱們只須要爲LoginAction添加一個input的處理結果便可,當發生類型轉換失敗的時候就會封裝錯誤信息並跳轉到input頁面。如下是程序運行的部分截圖:
咱們將第二個參數傳入一個字符串類型,則必然發生類型轉換錯誤,此時咱們看到結果轉向了input頁面。
至此,咱們簡單介紹了struts2中有關類型轉換的相關內容,有些地方理解不到,總結的很差,望不吝賜教。