Struts2-Json-Plugin 的使用(翻譯自官方文檔)

 

在 Struts2 中要使用 Ajax 獲得 Json 數據我認爲目前還是 struts2-json-plugin 了。當然你你可以用手工用像 XStreamGoogle GsonJackson 這樣的工具手工把 Java 對象轉換成 Json 字符串再寫往 Response 去,要寫的代碼自然多不了,還得留心字符集與 content type。而 struts2-json-plugin 毫無疑問是與 Struts2 最親近了,只需你配置一些屬性就能得到你想的結果。

本想分幾篇逐步介紹如何使用 struts2-json-plugin 的,然而就在現在發現官方的 struts2-json-plugin 指南已經很詳細了,所以乾脆翻譯一下 http://struts.apache.org/2.2.1.1/docs/json-plugin.html,同時自己加深對它的理解。

JSON 插件提供了一個 "json" 結果類型來把 action 序列化成 JSON. 這一序列化的過程是遞歸的, 意即整個對象圖,從 action 類開始 (未包括基類) 將會被序列化 (可以用 "root" 屬性來指定自己的根對象). 如果使用了 json 攔截器, action 將可通過請求中的 JSON 內容組裝出來, 該攔截器遵循以下幾條規則:

  1. "content-type" 必須爲 "application/json"
  2. JSON 內容必須是格式良好的, 參考 json.org 中的語法.
  3. Action 裏必須有欲獲取值的屬性的相應 public 的 "setter" 方法.
  4. 所支持的類型有: 原始類型 (int,long...String), Date, List, Map, 原始類型數組, 其他的類 (將會支持更多), 和其他類型的數組.
  5. JSON 中的任何將要被填入到 list 或 map 中的對象會是 Map 類型(屬性映射到值), 任何整數都是 Long 類型, 任何小數會是 Double 類型, 任何數組會是 List 類型.

給定下面的 JSON 字符串:

01
02
03
04
05
06
07
08
09
10
{
   "doubleValue": 10.10,
   "nestedBean": {
      "name": "Mr Bean"
   },
   "list": ["A", 10, 20.20, {
      "firstName": "El Zorro"
   }],
   "array": [10, 20]
}
 action 中必須有一個 "setDoubleValue" 方法, 參數爲 "float" 或者 "double"(攔截器將會把值轉換爲相應的類型). 還必須有一個 "setNestedBean" 方法,它的參數類型可以爲任何類類型, 其中含有參數爲 "String" 的 "setName" 方法. 還必須有一個參數爲 "List" 的 "setList" 方法, 這個 List 中將會包含: "A" (String), 10 (Long), 20.20 (Double), Map ("firstName" -> "El Zorro"). "setArray" 方法可以是 "List", 或任何數字類型數組作參數的.
序列化你的對象成 javascript 的 JSON, 參考 json2

安裝

本插件可通過把插件  jar 包到你的應用的 /WEB-INF/lib 目錄來完成安裝. 沒有別的文件需要拷貝或被創建.

使用 maven 的話, 加入下列到你的 pom 中:

1
2
3
4
5
6
7
8
9
<dependencies>
   ...
   <dependency>
       <groupId>org.apache.struts</groupId>
       <artifactId>struts2-json-plugin</artifactId>
       <version>STRUTS_VERSION</version>
   </dependency>
   ...
</dependencies>
 

定製化序列化和反序列化

使用 JSON 註解來達到定製序列化和反序列化過程. 可用的 JSON 註解如下:

名稱 描述 默認值 序列化 反序列化
name 定製字段名 empty yes no
serialize 標識爲可被序列化 true yes no
deserialize 標識爲可被反序列化 true no yes
format 用於格式化或解析 Date 字段的格式 "yyyy-MM-dd'T'HH:mm:ss" yes yes

排除屬性

逗號分隔的正則表達式列表可傳遞給 JSON Result 和 Interceptor(攔截器), 被任何 一個正則表達式匹配的屬性將會在序列化過程時忽略掉:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
<!-- Result fragment -->
<result type="json">
  <param name="excludeProperties">
    login.password,
    studentList.*\.sin
  </param>
</result>
  
<!-- Interceptor fragment -->
<interceptor-ref name="json">
  <param name="enableSMD">true</param>
  <param name="excludeProperties">
    login.password,
    studentList.*\.sin
  </param>
</interceptor-ref>
包含屬性

逗號分隔的正則表達式列表可被傳遞給 JSON Result, 用於限制哪些屬性可用於序列化. 只有當能夠匹配任何一個正則表達式的屬性纔會包含在序列化輸出中.

注:
排除屬性表達式優先於包含屬性的表達式.  那就是說, 如果包含和排除表達式應用於同一個結果, 包含表達式對於被排除表達式匹配到的屬性是不起作用的.
 
1
2
3
4
5
6
7
8
<!-- Result fragment -->
<result type="json">
  <param name="includeProperties">
    ^entries\[\d+\]\.clientNumber,
    ^entries\[\d+\]\.scheduleNumber,
    ^entries\[\d+\]\.createUserId
  </param>
</result>
根對象

使用 "root" 屬性(OGNL 表達式) 指定被用於序列化的根對象.

1
2
3
4
5
<result type="json">
  <param name="root">
    person.job
  </param>
</result>
"root" 屬性(OGNL 表達式) 也可以用於攔截器來指定被組裝的對象, 確保這個對象不會是 null.
1
2
3
<interceptor-ref name="json">
  <param name="root">bean1.bean2</param>
</interceptor-ref>
包裝

可能會有某些原因,你想要用些文本對 JSON 輸出包裝一下, 像用註釋包裹, 加上前綴, 或使用文件上載讓結果顯示在 textarea 之中. 用 wrapPrefix 在開始處加上內容,wrapPostfix 添加內容在尾端. 這兩個參數優先使用,而  "wrapWithComments" 和 "prefix" 自從 0.34 後就不推薦使用. 例子:

進行註釋:

1
2
3
4
<result type="json">
  <param name="wrapPrefix">/*</param>
  <param name="wrapSuffix">*/</param>
</result>

添加前綴:

1
2
3
<result type="json">
  <param name="wrapPrefix">{}&&</param>
</result>
 包裹上傳的文件內容:
1
2
3
4
<result type="json">
  <param name="wrapPrefix"><![CDATA[<html><body><textarea>]]></param>
  <param name="wrapSuffix"><![CDATA[</textarea></body></html>]]></param>
</result>

包裹以註釋

wrapWithComments 自 0.34 不推薦使用, 建議用 wrapPrefix 和 wrapSuffix.
wrapWithComments 可使得安全的 JSON 文本變得不安全. 例如,

["*/ alert('XSS'); /*"]

謝謝 Douglas Crockford 的提示! 應考慮用 prefix.

假如被序列化的 JSON 是 {name: 'El Zorro'}. 那麼此時輸出就會是: {}&& ({name: 'El Zorro'}

假如 "wrapWithComments" (默認爲 false) 屬性被設爲 true, 生成的被包裹上註釋的 JSON 就如下:

01
02
03
04
05
06
07
08
09
10
/* {
   "doubleVal": 10.10,
   "nestedBean": {
      "name": "Mr Bean"
   },
   "list": ["A", 10, 20.20, {
      "firstName": "El Zorro"
   }],
   "array": [10, 20]
} */
 欲取消上面的註釋,可用:
var responseObject = eval("("+data.substring(data.indexOf("\/\*")+2, data.lastIndexOf("\*\/"))+")");

前綴

prefix 從 0.34 後不建議用, 請用 wrapPrefix 和 wrapSuffix.

假如參數 prefix 被設置爲 true, 生成的 JSON 將被附上前綴 "{}&& ". 這有助於防止被劫持. 詳細內容請看 this Dojo Ticket:

1
2
3
<result type="json">
  <param name="prefix">true</param>
</result>
基類

默認時,定義在 "root" 對象的基類中的屬性不會被序列化, 要序列化來自於所有基類(直到 Object) 中的屬性,需在 JSON result 裏設置 "ignoreHierarchy" 爲 false:

1
2
3
<result type="json">
  <param name="ignoreHierarchy">false</param>
</result>

枚舉類型

默認的, Enum 被序列化爲 name=value 對,這裏的 value = name().

1
2
3
4
  public enum AnEnum {
     ValueA,
     ValueB
  }

 JSON: "myEnum":"ValueA"

使用 result 的參數 "enumAsBean" 可使得 Enum 像一個 bean 一樣的被序列化,特定的屬性爲 _name,值爲 name().  所有的枚舉屬性都會被序列化. 

01
02
03
04
05
06
07
08
09
10
  public enum AnEnum {
     ValueA("A"),
     ValueB("B");
  
     private String val;
  
     public AnEnum(val) { this.val = val; } public getVal() {
        return val;
     }
   }
 JSON: myEnum: { "_name": "ValueA", "val": "A" }

在 struts.xml 中啓用該參數:

1
2
3
<result type="json">
  <param name="enumAsBean">true</param>
</result>

壓縮輸出.

設置 enableGZIP 屬性爲 true 可用 gzip 壓縮響應輸出. 在請求後 "Accept-Encoding" 頭中必須包含  "gzip" 才能正常工作.

1
2
3
<result type="json">
  <param name="enableGZIP">true</param>
</result>

防止瀏覽器緩存響應數據

noCache 設置爲 true(默認爲 false) 會設置如下響應頭:

  • Cache-Control: no-cache
  • Expires: 0
  • Pragma: No-cache
1
2
3
<result type="json">
  <param name="noCache">true</param>
</result>

排除值爲 null 的屬性

默認的,爲 null 的字段也被序列化,生成像 {property_name: null}. 這能夠通過設置 excludeNullProperties 爲 true 來防止.

1
2
3
<result type="json">
  <param name="excludeNullProperties">true</param>
</result>

狀態和錯誤代碼

使用 statusCode 來設置響應狀態代碼:

1
2
3
<result type="json">
  <param name="statusCode">304</param>
</result>

同時可用 errorCode 來發送一個錯誤(the server might end up sending something to the client which is not the serialized JSON):

1
2
3
<result type="json">
  <param name="errorCode">404</param>
</result>

JSONP

To enable JSONP, set the parameter callbackParameter in either the JSON Result or the Interceptor. A parameter with that name will be read from the request, and it value will be used as the JSONP function. Assuming that a request is made with the parameter "callback"="exec":

1
2
3
<result type="json">
  <param name="callbackParameter">callback</param>
</result>

And that the serialized JSON is {name: 'El Zorro'}. Then the output will be: exec({name: 'El Zorro'})

Content Type

Content type will be set to application/json-rpc by default if SMD is being used, or application/json otherwise. Sometimes it is necessary to set the content type to something else, like when uploading files with Dojo and YUI. Use the contentType parameter in those cases.

1
2
3
<result type="json">
  <param name="contentType">text/html</param>
</result>

Example

Setup Action

This simple action has some fields:

Example:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import java.util.HashMap;
import java.util.Map;
  
import com.opensymphony.xwork2.Action;
  
public class JSONExample {
    private String field1 = "str";
    private int[] ints = {10, 20};
    private Map map = new HashMap();
    private String customName = "custom";
  
    //'transient' fields are not serialized
    private transient String field2;
  
    //fields without getter method are not serialized
    private String field3;
  
    public String execute() {
        map.put("John", "Galt");
        return Action.SUCCESS;
    }
  
    public String getField1() {
        return field1;
    }
  
    public void setField1(String field1) {
        this.field1 = field1;
    }
  
    public int[] getInts() {
        return ints;
    }
  
    public void setInts(int[] ints) {
        this.ints = ints;
    }
  
    public Map getMap() {
        return map;
    }
  
    public void setMap(Map map) {
        this.map = map;
    }
  
    @JSON(name="newName")
    public String getCustomName() {
        return this.customName;
    }
}

Write the mapping for the action

  1. Add the map inside a package that extends "json-default"
  2. Add a result of type "json"

Example:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
    "http://struts.apache.org/dtds/struts-2.0.dtd">
  
<struts>
  
  <package name="example"  extends="json-default">
     <action name="JSONExample">
        <result type="json"/>
     </action>
  </package>
  
</struts>

JSON example output

1
2
3
4
5
6
7
8
   "field1" : "str",
   "ints": [10, 20],
   "map": {
       "John":"Galt"
   },
   "newName": "custom"
}

JSON RPC

The json plugin can be used to execute action methods from javascript and return the output. This feature was developed with Dojo in mind, so it uses Simple Method Definition to advertise the remote service. Let's work it out with an example(useless as most examples).

First write the action:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
package smd;
  
import com.googlecode.jsonplugin.annotations.SMDMethod;
import com.opensymphony.xwork2.Action;
  
public class SMDAction {
    public String smd() {
        return Action.SUCCESS;
    }
  
    @SMDMethod
    public Bean doSomething(Bean bean, int quantity) {
        bean.setPrice(quantity * 10);
        return bean;
    }
}

Methods that will be called remotely must be annotated with the SMDMethod annotation, for security reasons. The method will take a bean object, modify its price and return it. The action can be annotated with the SMD annotation to customize the generated SMD (more on that soon), and parameters can be annotated with SMDMethodParameter. As you can see, we have a "dummy", smd method. This method will be used to generate the Simple Method Definition (a definition of all the services provided by this class), using the "json" result.

The bean class:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package smd;
  
public class Bean {
    private String type;
    private int price;
  
    public String getType() {
        return type;
    }
  
    public void setType(String type) {
        this.type = type;
    }
  
    public int getPrice() {
        return price;
    }
  
    public void setPrice(int price) {
        this.price = price;
    }
  
}

The mapping:

01
02
03
04
05
06
07
08
09
10
<package name="RPC" namespace="/nodecorate" extends="json-default">
    <action name="SMDAction" method="smd">
        <interceptor-ref name="json">
            <param name="enableSMD">true</param>
        </interceptor-ref>
        <result type="json">
             <param name="enableSMD">true</param>
        </result>
    </action>
</package>

Nothing special here, except that both the interceptor and the result must be applied to the action, and "enableSMD" must be enabled for both.

Now the javascript code:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
<s:url id="smdUrl" namespace="/nodecorate" action="SMDAction" />
<script type="text/javascript">
    //load dojo RPC
    dojo.require("dojo.rpc.*");
  
    //create service object(proxy) using SMD (generated by the json result)
    var service = new dojo.rpc.JsonService("${smdUrl}");
  
    //function called when remote method returns
    var callback = function(bean) {
        alert("Price for " + bean.type + " is " + bean.price);
    };
  
    //parameter
    var bean = {type: "Mocca"};
  
    //execute remote method
    var defered = service.doSomething(bean, 5);
  
    //attach callback to defered object
    defered.addCallback(callback);
</script>

Dojo's JsonService will make a request to the action to load the SMD, which will return a JSON object with the definition of the available remote methods, using that information Dojo creates a "proxy" for those methods. Because of the asynchronous nature of the request, when the method is executed, a deferred object is returned, to which a callback function can be attached. The callback function will receive as a parameter the object returned from your action. That's it.

Proxied objects

As annotations are not inherited in Java, some user might experience problems while trying to serialize objects that are proxied. eg. when you have attached AOP interceptors to your action.

In this situation, the plugin will not detect the annotations on methods in your action.

To overcome this, set the "ignoreInterfaces" result parameter to false (true by default) to request that the plugin inspects all interfaces and superclasses of the action for annotations on the action's methods.

NOTE: This parameter should only be set to false if your action could be a proxy as there is a performance cost caused by recursion through the interfaces.

01
02
03
04
05
06
07
08
09
10
11
<action name="contact" method="smd">
   <interceptor-ref name="json">
      <param name="enableSMD">true</param>
      <param name="ignoreSMDMethodInterfaces">false</param>
   </interceptor-ref>
   <result type="json">
      <param name="enableSMD">true</param>
      <param name="ignoreInterfaces">false</param>
   </result>
   <interceptor-ref name="default"/>
</action>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章