早在我剛學Struts2之初的時候,就想寫一篇文章來闡述Struts2如何返回JSON數據的原理和具體應用了,但苦於一直忙於工作難以抽身,漸漸的也淡忘了此事。直到前兩天有同事在工作中遇到這個問題,來找我詢問,我又細細地給他講了一遍之後,才覺得無論如何要抽一個小時的時間來寫這篇文章,從頭到尾將Struts2與JSON的關係說清楚。
其實網絡中,關於這個問題的答案已是海量,我當初也是從這海量的答案中吸收精華,纔將“Struts2返回JSON數據”這個問題搞清楚的。但是這些海量的答案,有一個共同的缺陷,就是作者們只關注問題核心,即“如何在具體的Struts2應用中返回JSON數據到客戶端”如何實現,而對於"爲何要這樣實現"以及實現的本質卻解釋的不甚了了,在筆者看來這只是“授人以魚”而非筆者所推崇的“授人以魚的同時,授人以漁”。在這篇文章中,筆者將總結前輩們的經驗,並結合自己的理解,來從理論到實踐由淺入深的說明“Struts2返回JSON數據”這一問題。
JSON(JavaScript Object Notation)
首先來看一下JSON官方對於“JSON”的解釋:
JSON(JavaScript Object Notation) 是一種輕量級的數據交換格式。易於人閱讀和編寫。同時也易於機器解析和生成。它基於JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999的一個子集。 JSON採用完全獨立於語言的文本格式,但是也使用了類似於C語言家族的習慣(包括C, C++, C#, Java, JavaScript, Perl, Python等)。這些特性使JSON成爲理想的數據交換語言。(更多內容請參見JSON官網http://json.org/json-zh.html)
JSON建構於兩種結構:
“名稱/值”對的集合(A collection of name/value pairs)。不同的語言中,它被理解爲對象(object),紀錄(record),結構(struct),字典(dictionary),哈希表(hash table),有鍵列表(keyed list),或者關聯數組 (associative array)。
值的有序列表(An ordered list of values)。在大部分語言中,它被理解爲數組(array)。
因爲JSON中的值(value)可以是雙引號括起來的字符串(string)、數值(number)、true、false、 null、對象(object)或者數組(array),且這些結構可以嵌套,這種特性給予JSON表達數據以無限的可能:它既可以表達一個簡單的 key/value,也可以表達一個複雜的Map或List,而且它是易於閱讀和理解的。
Struts2中JSON的用武之地
因爲JSON是脫離語言的理想的數據交換格式,所以它被頻繁的應用在客戶端與服務器的通信過程中,這一點是毋庸置疑的。而在客戶端與服務器的通信過程中,JSON數據的傳遞又被分爲服務器向客戶端傳送JSON數據,和客戶端向服務器傳送JSON數據,前者的核心過程中將對象轉換成JSON,而後者的核心是將JSON轉換成對象,這是本質的區別。另外,值得一提的是,JSON數據在傳遞過程中,其實就是傳遞一個普通的符合JSON語法格式的字符串而已,所謂的“JSON對象”是指對這個JSON字符串解析和包裝後的結果,這一點請牢記,因爲下面的內容會依賴這一點。
Struts2返回JSON數據到客戶端
這是最常見的需求,在AJAX大行其道的今天,向服務器請求JSON數據已成爲每一個WEB應用必備的功能。拋開Struts2暫且不提,在常規WEB應用中由服務器返回JSON數據到客戶端有兩種方式:一是在Servlet中輸出JSON串,二是在JSP頁面中輸出JSON串。上文提到,服務器像客戶端返回JSON數據,其實就是返回一個符合JSON語法規範的字符串,所以在上述兩種 方法中存在一個共同點,就是將需要返回的數據包裝稱符合JSON語法規範的字符串後在頁面中顯示。如下所示
使用Servlet返回JSON數據到客戶端:
01 |
package
cn.ysh.studio.struts2.json.demo.servlet; |
03 |
import
java.io.IOException; |
04 |
import
java.io.PrintWriter; |
06 |
import
javax.servlet.ServletException; |
07 |
import
javax.servlet.http.HttpServlet; |
08 |
import
javax.servlet.http.HttpServletRequest; |
09 |
import
javax.servlet.http.HttpServletResponse; |
11 |
import
net.sf.json.JSONObject; |
13 |
import
cn.ysh.studio.struts2.json.demo.bean.User; |
15 |
public
class JSON extends
HttpServlet { |
20 |
private
static final
long serialVersionUID = 1L; |
23 |
* The doGet method of the servlet. <br> |
25 |
* This method is called when a form has its tag value method equals to get. |
27 |
* @param request the request send by the client to the server |
28 |
* @param response the response send by the server to the client |
29 |
* @throws ServletException if an error occurred |
30 |
* @throws IOException if an error occurred |
32 |
public
void doGet(HttpServletRequest request, HttpServletResponse response) |
33 |
throws
ServletException, IOException { |
35 |
response.setContentType( "text/html" ); |
36 |
PrintWriter out = response.getWriter(); |
40 |
user.setName( "JSONServlet" ); |
41 |
user.setPassword( "JSON" ); |
42 |
user.setSay( "Hello , i am a servlet !" ); |
43 |
JSONObject json= new
JSONObject(); |
44 |
json.accumulate( "success" ,
true ); |
45 |
json.accumulate( "user" , user); |
46 |
out.println(json.toString()); |
56 |
* The doPost method of the servlet. <br> |
58 |
* This method is called when a form has its tag value method equals to post. |
60 |
* @param request the request send by the client to the server |
61 |
* @param response the response send by the server to the client |
62 |
* @throws ServletException if an error occurred |
63 |
* @throws IOException if an error occurred |
65 |
public
void doPost(HttpServletRequest request, HttpServletResponse response) |
66 |
throws
ServletException, IOException { |
67 |
doGet(request, response); |
結果在意料之中,如下圖所示:
使用JSP(或html等)返回JSON數據到客戶端:
1 |
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> |
2 |
{"user":{"id":"123","name":"JSONJSP","say":"Hello , i am a JSP !","password":"JSON"},"success":true} |
這個就不用去看結果了吧。
再回到Struts,在Struts的MVC模型中,Action替代Servlet擔當了Model的角色,所以對於Struts而言,返回 JSON數據到客戶端,跟傳統的WEB應用一樣,存在兩種方式,即在Action中輸出JSON數據,和在視圖資源中輸出JSON數據。再往下細分的話,在Action中輸出JSON數據又分爲兩種方式,一是使用傳統方式輸出自己包裝後的JSON數據,二是使用Struts自帶的JSON數據封裝功能來自動包裝並返回JSON數據。
在視圖資源中輸出JSON數據
Action處理完用戶請求後,將數據存放在某一位置,如request中,並返回視圖,然後Struts將跳轉至該視圖資源,在該視圖中,我們需要做的是將數據從存放位置中取出,然後將其轉換爲JSON字符串,輸出在視圖中。這跟傳統WEB應用中在JSP頁面輸出JSON數據的做法如出一轍:
01 |
public
String testByJSP() { |
02 |
User user =
new User(); |
04 |
user.setName( "Struts2" ); |
05 |
user.setPassword( "123" ); |
06 |
user.setSay( "Hello world !" ); |
07 |
JSONObject jsonObject= new
JSONObject(); |
08 |
jsonObject.accumulate( "user" , user); |
10 |
ServletActionContext.getRequest().setAttribute( "data" , jsonObject.toString()); |
因爲是常規的Struts流程配置,所以配置內容就不再展示了。
JSP代碼就非常簡單了,
1 |
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> |
結果如圖所示:
在Action中以傳統方式輸出JSON數據
這一點跟傳統的Servlet的處理方式基本上一模一樣,代碼如下
01 |
public
void doAction() throws
IOException{ |
02 |
HttpServletResponse response=ServletActionContext.getResponse(); |
04 |
response.setContentType( "text/html" ); |
06 |
out = response.getWriter(); |
10 |
user.setName( "JSONActionGeneral" ); |
11 |
user.setPassword( "JSON" ); |
12 |
user.setSay( "Hello , i am a action to print a json!" ); |
13 |
JSONObject json= new
JSONObject(); |
14 |
json.accumulate( "success" ,
true ); |
15 |
json.accumulate( "user" , user); |
16 |
out.println(json.toString()); |
struts.xml中的配置:
1 |
< package
name = "default"
extends = "struts-default"
namespace = "/" > |
2 |
< action
name = "testJSONFromActionByGeneral"
class = "cn.ysh.studio.struts2.json.demo.action.UserAction"
method = "doAction" > |
注意:這個action沒有result,且doAction方法沒有返回值!
就不再貼圖了,因爲結果可想而知!
在Action中以Struts2的方式輸出JSON數據
本着“不重複發明輪子”的原則,我們將轉換JSON數據的工作交給Struts2來做,那麼相對於在Action中以傳統方式輸出JSON不同的是,Action是需要將注意力放在業務處理上,而無需關心處理結果是如何被轉換成JSON被返回客戶端的——這些 工作通過簡單的配置,Struts2會幫我們做的更好。
01 |
public
String testByAction() { |
04 |
User user =
new User(); |
06 |
user.setName( "JSONActionStruts2" ); |
07 |
user.setPassword( "123" ); |
08 |
user.setSay( "Hello world !" ); |
09 |
dataMap.put( "user" , user); |
11 |
dataMap.put( "success" ,
true ); |
struts.xml中action的配置:
1 |
< package
name = "json"
extends = "json-default"
namespace = "/test" > |
2 |
< action
name = "testByAction" |
3 |
class = "cn.ysh.studio.struts2.json.demo.action.UserAction"
method = "testByAction" > |
6 |
< param
name = "root" >dataMap</ param > |
凡是使用Struts2序列化對象到JSON的action,所在的package必須繼承自json-default,注意,這裏唯一的result,沒有指定name屬性。
結果如下圖所示:
上面很詳細的說明了在WEB應用中如何返回JSON數據到客戶端,講了那麼多種方式,涉及的技術核心無非只有兩點:
1、將對象轉換成符合JSON語法格式的字符串;
2、將符合JSON語法格式的字符串返回客戶端;
第二點是整個實現過程的本質,但卻不難做到;第一點其實也不難,他甚至有兩種做法,一是通過字符串拼接方式,而是通過JSONObject以對象方式轉換。看下面的一個例子:
01 |
package
cn.ysh.studio.struts2.json.demo.test; |
03 |
import
cn.ysh.studio.struts2.json.demo.bean.User; |
04 |
import
net.sf.json.JSONObject; |
06 |
public
class JSONTest { |
12 |
public
JSONObject bean2json() { |
13 |
User user =
new User(); |
14 |
user.setId( "JSONTest" ); |
15 |
user.setName( "JSONTest" ); |
16 |
user.setPassword( "JSON" ); |
17 |
user.setSay( "Hello,i am JSONTest.java" ); |
18 |
JSONObject jsonObject =
new JSONObject(); |
19 |
jsonObject.accumulate( "user" , user); |
20 |
System.out.println( "User轉換後的字符串:" +jsonObject.toString()); |
25 |
* 從JSONObject對象中反向解析出User對象 |
28 |
public
void json2bean(JSONObject jsonObject) { |
29 |
User user=(User)JSONObject.toBean((JSONObject)jsonObject.get( "user" ),User. class ); |
30 |
System.out.println( "轉換得到的User對象的Name爲:" +user.getName()); |
33 |
public
static void
main(String[] s) { |
34 |
JSONTest tester= new
JSONTest(); |
35 |
tester.json2bean(tester.bean2json()); |
JSON格式的字符串返回到客戶端後,客戶端會將其解析並封裝成真正的JSON對象,以供JS調用。
總結上述,其實只要明白了服務器返回JSON數據到客戶端的原理,做起來就遊刃有餘了,他甚至有非常多的可選方案,但既然是基於 Struts2的實現,那麼肯定還是要用Struts2的方式來做啦,因爲這樣確實可以省很多事。另外,在文章的最後,說明一下返回JSON數據時在 result中配置的參數的含義及其常見常見配置吧:
04 |
< param
name = "root" >dataMap</ param > |
06 |
< param
name = "excludeNullProperties" >true</ param > |
08 |
< param
name = "includeProperties" > |
12 |
< param
name = "excludeProperties" > |
值得一提的是通過Struts2來返回JSON數據,在IE中會提示下載,這個不用關心,換個瀏覽器就能正常展示JSON數據,而在JS調用中,更是毫無影響。
下面是整個Action的完整代碼:
001 |
package
cn.ysh.studio.struts2.json.demo.action; |
003 |
import
java.io.IOException; |
004 |
import
java.io.PrintWriter; |
005 |
import
java.util.HashMap; |
006 |
import
java.util.Map; |
008 |
import
javax.servlet.http.HttpServletResponse; |
010 |
import
org.apache.struts2.ServletActionContext; |
012 |
import
net.sf.json.JSONObject; |
014 |
import
cn.ysh.studio.struts2.json.demo.bean.User; |
016 |
import
com.opensymphony.xwork2.ActionSupport; |
018 |
public
class UserAction extends
ActionSupport { |
023 |
private
static final
long serialVersionUID = 1L; |
026 |
private
Map<String, Object> dataMap; |
031 |
public
UserAction() { |
033 |
dataMap =
new HashMap<String, Object>(); |
037 |
* 測試通過action以視圖方式返回JSON數據 |
040 |
public
String testByJSP() { |
041 |
User user =
new User(); |
043 |
user.setName( "JSONActionJSP" ); |
044 |
user.setPassword( "123" ); |
045 |
user.setSay( "Hello world !" ); |
046 |
JSONObject jsonObject= new
JSONObject(); |
047 |
jsonObject.accumulate( "user" , user); |
048 |
jsonObject.accumulate( "success" ,
true ); |
050 |
ServletActionContext.getRequest().setAttribute( "data" , jsonObject.toString()); |
055 |
* 測試通過action以Struts2默認方式返回JSON數據 |
058 |
public
String testByAction() { |
061 |
User user =
new User(); |
063 |
user.setName( "JSONActionStruts2" ); |
064 |
user.setPassword( "123" ); |
065 |
user.setSay( "Hello world !" ); |
066 |
dataMap.put( "user" , user); |
068 |
dataMap.put( "success" ,
true ); |
074 |
* 通過action是以傳統方式返回JSON數據 |
075 |
* @throws IOException |
077 |
public
void doAction()
throws IOException{ |
078 |
HttpServletResponse response=ServletActionContext.getResponse(); |
080 |
response.setContentType( "text/html" ); |
082 |
out = response.getWriter(); |
084 |
User user= new
User(); |
086 |
user.setName( "JSONActionGeneral" ); |
087 |
user.setPassword( "JSON" ); |
088 |
user.setSay( "Hello , i am a action to print a json!" ); |
089 |
JSONObject json= new
JSONObject(); |
090 |
json.accumulate( "success" ,
true ); |
091 |
json.accumulate( "user" , user); |
092 |
out.println(json.toString()); |
102 |
* Struts2序列化指定屬性時,必須有該屬性的getter方法,實際上,如果沒有屬性,而只有getter方法也是可以的 |
105 |
public
Map<String, Object> getDataMap() { |
完整的struts.xml配置文件如下:
01 |
<? xml
version = "1.0"
encoding = "UTF-8" ?> |
02 |
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
|
03 |
"http://struts.apache.org/dtds/struts-2.0.dtd"> |
05 |
< package
name = "json"
extends = "json-default"
namespace = "/test" > |
06 |
< action
name = "testByAction" |
07 |
class = "cn.ysh.studio.struts2.json.demo.action.UserAction"
method = "testByAction" > |
11 |
< param
name = "root" >dataMap</ param > |
31 |
< package
name = "default"
extends = "struts-default"
namespace = "/" > |
32 |
< action
name = "testJSONFromActionByGeneral" |
33 |
class = "cn.ysh.studio.struts2.json.demo.action.UserAction"
method = "doAction" > |
35 |
< action
name = "testByJSP" |
36 |
class = "cn.ysh.studio.struts2.json.demo.action.UserAction"
method = "testByJSP" > |
37 |
< result
name = "success" >/actionJSP.jsp</ result > |
原創文章,轉載請註明出處:
http://yshjava.iteye.com/blog/1333104