一、選擇的意義
在異步應用程序中發送和接收信息時,可以選擇以純文本和 XML 作為數據格式。為了更好的使用ajax, 我們將學習一種有用的數據格式 JavaScript Object Notation (JSON ),以及如何使用它更輕松地在應用程序中移動數據和對象。JSON 是一種簡單的數據交換格式,在某些方面,它的作用與XML 非常類似,但比XML 更為簡單,JSON 的語法簡化了數據交換的難度,而且提供了一種 偽對象 的方式。
Java 的對象 < - >JavaScript 對象(json 數據格式)
簡單地說, JSON 可以將 JavaScript 對象中表示的一組數據轉換為字符串(偽對象) ,然后就可以在函數之間輕松地傳遞這個字符串,或者 在異步應用程序中將字符串從 Web 客戶端傳遞給服務器端程序 。這個字符串看起來有點兒古怪(稍后會看到幾個示例),但是 JavaScript 很容易解釋它,而且 JSON 可以表示比名稱/ 值對更復雜的結構。例如,可以表示數組和復雜的對象,而不僅僅是鍵和值的簡單列表。
關於 JSON 對象
1 、使用JavaScript 語法創建對象
// 定義一個函數,作為構造函數
fucntion person(name,sex)
{
this.name=name;
this.sex=sex;
}
// 創建一個實例
var p=new Person(‘ 張三’,’ 男’);
// 輸出Person 實例
alert(p.name);
注意:通過該方式創建的對象是一般的腳本對象
2 、從JavaScript1.2 開始創建對象有了一種更快捷的語法(Json 的語法) ,如下:
var obj= { name: " 張三 " , "sex" : ' 男 ' } ;
alert(obj.sex);
關於數組
1 、早期的JavaScript 數組
var arr=new Array();
arr[0]=’a’;
arr[1]=’bbc’
我們也可以通過如下方式創建數組
var arr=new Array(‘a’,’bbc’);
2 、使用JSON 語法,則可以通過如下方式創建數組
var arr=[‘a’,’bbc’];
按照最簡單的形式,可以用下面這樣的 JSON 表示名稱/ 值對:
{ "firstName":"Brett" } |
這個示例非常基本,而且實際上比等效的純文本名稱/ 值對占用更多的空間:
firstName=Brett |
但是,當將多個名稱/ 值對串在一起時,JSON 就會體現出它的價值了。首先,可以創建包含多個名稱/ 值對的記錄 ,比如:
{"firstName": "Brett", "lastName":"McLaughlin", "email": "brett@newInstance.com"}
|
從語法方面來看,這與名稱/ 值對相比並沒有很大的優勢,但是在這種情況下 JSON 更容易使用,而且可讀性更好。例如,它明確地表示以上三個值都是同一記錄的一部分;花括號使這些值有了某種聯系。
當需要表示一組值時,JSON 不但能夠提高可讀性,而且可以減少復雜性。例如,假設您希望表示一個人名列表。在 XML 中,需要許多開始標記和結束標記;如果使用典型的名稱/ 值對(就像在本系列前面文章中看到的那種名稱/ 值對),那么必須建立一種專有的數據格式,或者將鍵名稱修改為 person1-firstName
這樣的形式。
如果使用 JSON ,就只需將多個帶花括號的記錄分組在一起:
{ "people": [ { "firstName": "Brett", "lastName":"McLaughlin", "email": "brett@newInstance.com" }, { "firstName": "Jason", "lastName":"Hunter", "email": "jason@servlets.com" }, { "firstName": "Elliotte", "lastName":"Harold", "email": "elharo@macfaq.com" } ]} |
這不難理解。在這個示例中,只有一個名為 people
的變量,值是包含三個條目的數組,每個條目是一個人的記錄,其中包含名、姓和電子郵件地址。上面的示例演示如何用括號將記錄組合成一個值。當然,可以使用相同的語法表示多個值(每個值包含多個記錄):
{ "programmers": [ { "firstName": "Brett", "lastName":"McLaughlin", "email": "brett@newInstance.com" }, { "firstName": "Jason", "lastName":"Hunter", "email": "jason@servlets.com" }, { "firstName": "Elliotte", "lastName":"Harold", "email": "elharo@macfaq.com" } ], "authors": [ { "firstName": "Isaac", "lastName": "Asimov", "genre": "science fiction" }, { "firstName": "Tad", "lastName": "Williams", "genre": "fantasy" }, { "firstName": "Frank", "lastName": "Peretti", "genre": "christian fiction" } ], "musicians": [ { "firstName": "Eric", "lastName": "Clapton", "instrument": "guitar" }, { "firstName": "Sergei", "lastName": "Rachmaninoff", "instrument": "piano" } ] } |
這里最值得注意的是,能夠表示多個值,每個值進而包含多個值。但是還應該注意,在不同的主條目(programmers 、authors 和 musicians )之間,記錄中實際的名稱/ 值對可以不一樣。JSON 是完全動態的,允許在 JSON 結構的中間改變表示數據的方式。甚至可以聲明如下的Json 對象
var obj2= { people: { name: ' 張三 ' ,sex: " 男 " }}
alert(obj2.people.name);
在處理 JSON 格式的數據時,沒有需要遵守的預定義的約束。所以,在同樣的數據結構中,可以改變表示數據的方式,甚至可以以不同方式表示同一事物。
{deptid: ' 1 ' ,deptname: ' 開發部 ' ,deptnum: ' 2 ' ,deptdesc: ' 開發相關 ',
emps:[{empid:1,empname:' 張三 ',sex:’ 男 ’,age:’20’},{empid:2,empname:' 張三 ',sex:’ 男 ’,age:’20’} , {empid:3,empname:' 張三 ',sex:’ 男 ’,age:’20’}] }
掌握了 JSON 格式之后,在 JavaScript 中使用它就很簡單了。JSON 是 JavaScript 原生格式,這意味着 在 JavaScript 中處理 JSON 數據不需要任何特殊的 API 或工具包。
例如,可以創建一個新的 JavaScript 變量,然后將 JSON 格式的數據字符串直接賦值給它:
var people =
{ "programmers": [
{ "firstName": "Brett", "lastName":"McLaughlin", "email": "brett@newInstance.com" },
{ "firstName": "Jason", "lastName":"Hunter", "email": "jason@servlets.com" },
{ "firstName": "Elliotte", "lastName":"Harold", "email": "elharo@macfaq.com" }
],
"authors": [
{ "firstName": "Isaac", "lastName": "Asimov", "genre": "science fiction" },
{ "firstName": "Tad", "lastName": "Williams", "genre": "fantasy" },
{ "firstName": "Frank", "lastName": "Peretti", "genre": "christian fiction" }
],
"musicians": [
{ "firstName": "Eric", "lastName": "Clapton", "instrument": "guitar" },
{ "firstName": "Sergei", "lastName": "Rachmaninoff", "instrument": "piano" }
]
}
|
這非常簡單;現在 people
包含前面看到的 JSON 格式的數據。但是,這還不夠,因為訪問數據的方式似乎還不明顯。
盡管看起來不明顯,但是上面的長字符串實際上只是一個數組;將這個數組放進 JavaScript 變量之后,就可以很輕松地訪問它。實際上,只需用點號表示法來表示數組元素。所以,要想訪問 programmers 列表的第一個條目的姓氏,只需在 JavaScript 中使用下面這樣的代碼:
people.programmers[0].lastName; |
注意,數組索引是從零開始的。所以,這行代碼首先訪問 people
變量中的數據;然后移動到稱為 programmers
的條目,再移動到第一個記錄( [0]
);最后,訪問 lastName
鍵的值。結果是字符串值 “McLaughlin” 。
下面是使用同一變量的幾個示例。
people.authors[1].genre // Value is "fantasy" people.musicians[3].lastName // Undefined. This refers to the fourth entry,and there isn't one people.programmers[2].firstName // Value is "Elliotte" |
利用這樣的語法,可以處理任何 JSON 格式的數據,而不需要使用任何額外的 JavaScript 工具包或 API 。
正如可以用點號和括號訪問數據,也可以按照同樣的方式輕松地修改數據:
people.musicians[1].lastName = "Rachmaninov"; |
在將字符串轉換為 JavaScript json 格式對象之后,就可以像這樣修改變量中的數據。
注意:json 格式的對象和json 文本是不同的
var obj={name:" 張三 ","sex":' 男 '}; //json 格式的對象
var str=" { name: " 張三 " , "sex" : ' 男 ' }" ; //json 格式的字符串( json 格式的文本)
當然,如果不能輕松地將對象轉換回本文提到的文本格式,那么所有數據修改都沒有太大的價值。在 JavaScript 中這種轉換也很簡單:
var newJSONtext = people.toJSONString(); |
這樣就行了!現在就獲得了一個可以在任何地方使用的文本字符串,例如,可以將它用作 Ajax 應用程序中的請求字符串。
更重要的是,可以將任何 JavaScript 對象轉換為 JSON 文本。並非只能處理原來用 JSON 字符串賦值的變量。為了對名為 myObject
的對象進行轉換,只需執行相同形式的命令:
<script type="text/javascript">
function Car(make,model,year,color)
{
this.make=make;
this.model=model;
this.year=year;
this.color=color;
}
function showCar()
{
var carr = new Car("Dodge","Coronet R/T",1968,"yellow");
alert(carr.toJSONString());
}
</script>
這就是 JSON 與其他數據格式之間最大的差異。如果使用 JSON ,只需調用一個簡單的函數,就可以獲得經過格式化的數據,可以直接使用了。對於其他數據格式,需要在原始數據和格式化數據之間進行轉換。即使使用 Document Object Model 這樣的 API (提供了將自己的數據結構轉換為文本的函數),也需要學習這個 API 並使用 API 的對象,而不是使用原生的 JavaScript 對象和語法。
最終結論是,如果要處理大量 JavaScript 對象,那么 JSON 幾乎肯定是一個好選擇,這樣就可以輕松地將數據轉換為可以在請求中發送給服務器端程序的格式(Ajax) 。
四、Struts 2 中使用Json ajax 支持
JSON 插件提供了一種名為json 的ResultType ,一旦為某個Action 指定了一個類型為json 的Result ,則該Result 無需映射到任何視圖資源。因為JSON 插件會負責將Action 里的狀態信息序列化成JSON 格式的數據,並將該數據返回給客戶端頁面的 JavaScript 。
簡單地說,JSON 插件允許我們在JavaScript 中異步調用Action ,而且Action 不再需要使用視圖資源來顯示該Action 里的狀態信息,而是由JSON 插件負責將Action 里的狀態信息返回給調用頁面——通過這種方式,就可以完成Ajax 交互。
Struts2 提供了一種可插拔方式來管理插件,安裝Struts2 的JSON 插件與安裝普通插件並沒有太大的區別,一樣只需要將Struts2 插件的JAR 文件復制到Web 應用的WEB-INF/lib 路徑下即可。
安裝JSON 插件按如下步驟進行:
(1 )登陸http://code.google.com/p/jsonplugin/downloads/list 站點,下載Struts2 的JSON 插件的最新版本,當前最新版本是0.7 ,我們可以下載該版本的JSON 插件。
(2 )將下載到的jsonplugin-0.7.jar 文件復制到Web 應用的WEB-INF 路徑下,即可完成JSON 插件的安裝。
實現Action 邏輯
假設deptnew.html 輸入頁面中包含了四個表單域,這四個表單域對於四個請求參數,因此應該使用Action 來的dept 對象封裝這四個請求參數。四個表單域的name 分別為dept.deptid 、dept.deptname 、dept.deptnum 和dept.deptdesc 。
處理該請求的Action 類代碼如下:
package org.wllt.ajax.actions;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.wllt.ajax.beans.Dept;
import com.googlecode.jsonplugin.annotations.JSON;
import com.opensymphony.xwork2.Action;
public class JSONExample
{
// 封裝請求參數的三個屬性
private String field1;
private transient String field2;
private String field3;
// 封裝處理結果的屬性
private int[] ints ={10, 20};
private Map map = new HashMap();
private String customName = "custom";
// 日期格式的屬性
private Date date;
private Dept dept;
// 三個請求參數對應的 setter 和 getter 方法
@JSON(serialize=false)
public String getField1(){
return field1;
}
public void setField1(String field1)
{
this.field1 = field1;
}
// 封裝處理結果的屬性的 setter 和 getter 方法
public int[] getInts()
{
return ints;
}
public void setInts(int[] ints)
{
this.ints = ints;
}
@JSON(format="yyyy-MM-dd")
public Map getMap()
{
return map;
}
public void setMap(Map map)
{
this.map = map;
}
public String getField2() {
return field2;
}
public void setField2(String field2) {
this.field2 = field2;
}
public String getField3() {
return field3;
}
public void setField3(String field3) {
this.field3 = field3;
}
public Dept getDept() {
return dept;
}
public void setDept(Dept dept) {
this.dept = dept;
}
// 使用注釋語法來改變該屬性序列化后的屬性名
@JSON(name="newName")
public String getCustomName()
{
return this.customName;
}
@JSON(format="yyyy-MM-dd")
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public String execute()
{
map.put("name", "yeeku");
map.put("curdate",new Date());
return Action.SUCCESS;
}
}
在上面代碼中,使用了JSON 注釋,注釋時指定了name 域,name 域指定Action 屬性被序列化成JSON 對象的屬性名。除此之外,JSON 注釋還支持如下幾個域:
Name : Action 屬性被序列化成JSON 對象的屬性名
serialize :設置是否序列化該屬性( 默認的action 的所有屬性都會序列化成json 文本響應到原頁面,也可以根據需要設定某些屬性不序列化 ) 。也可以通過在配置json 類型的結果的時候定義需要不序列化的屬性:
<result name="success" type="json">
<param name="excludeProperties">service,joindate,dept</param>
</result>
deserialize :設置是否反序列化該屬性。
format :設置用於格式化輸出、解析日期表單域的格式。例如"yyyy-MM-dd'T'HH:mm:ss" 。
一般需要注釋到需要轉換日期格式屬性的get 方法上面
配置該Action 與配置普通Action 存在小小的區別,為該Action 配置結果類型為json 的Result 。而這個Result 無需配置任何視圖資源, 只需要把通過該結果把json 文本響應到原頁面。
配置該Action 的struts.xml 文件代碼如下:
<? 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 >
< constant name = "struts.locale" value = "zh_CN" />
< constant name = "struts.action.extension" value = "do,action" />
< package name = "json" extends = "json-default" >
< action name = "dept" class = "org.wllt.ajax.actions.DeptAction" >
< result type = "json" />
</ action >
</ package >
</ struts >
在上面配置文件中有兩個值得注意的地方:
第一個地方是配置struts.i18n.encoding 常量時,不再是使用GBK 編碼,而是UTF-8 編碼,這是因為Ajax 的POST 請求都是以UTF-8 的方式進行編碼的。
第二個地方是配置包時,自己的包繼承了json-default 包,而不再繼承默認的default 包,這是因為只有在該包下才有json 類型的Result 。
實現JSP 頁面first.jsp :
為了簡單地訪問DOM 節點,這里用了JavaScript 框架Prototype.js.
< html >
< head >
< title > 使用 JSON 插件 </ title >
< meta http-equiv = "content-type" content = "text/html; charset=UTF-8" >
< script src = "./js/prototype.js" type = "text/javascript" >
</ script >
< script language = "JavaScript" >
function gotClick()
{
// 請求的地址
var url= './dept.do' ;
// 將 Form 中所有 Input 對象的值轉化為一個 URL String ,方便把 Form 轉為用 URL Get 方式 // 的 Ajax 提交,常用 Ajax 提交 Form 的例子 :
var params=Form.serialize( 'form1' );
// 創建 Ajax.Request 對象,對應於發送請求
var myAjax= new Ajax.Request(url, {
// 請求方式: POST
method: 'post' ,
// 請求參數
parameters:params,
// 指定回調函數
onComplete: processResponse,
// 是否異步發送請求
asynchronous: true
} );
}
function processResponse(request)
{
$( "show" ).innerHTML=request.responseText;
}
</ script >
</ head >
< body >
< form id = "form1" name = "form1" method = "post" >
部門編號 < INPUT TYPE = "text" name = "dept.deptid" id = "deptid" />< br >
部門名稱 < INPUT TYPE = "text" name = "dept.deptname" id = "deptname" />< br >
部門編制 < INPUT TYPE = "text" name = "dept.deptnum" id = "deptnum" />< br >
部門描述 < INPUT TYPE = "text" name = "dept.deptdesc" id = "deptdesc" />< br >
< INPUT TYPE = "button" value = " 提交 " onClick = "gotClick();" />
</ form >
< div id = "show" >
</ div >
</ body >
</ html >
五、當json 碰到hibernate 延時加載
在開發Struts2.0+hibernate3.2+spring2.0 項目過程中,遇到了 failed to lazily initialize a collection of role: org.wllt.www.pojo.Dept.emps, no session or session was closed 這個異常的出現,通過以下方法可以解決這個問題:
1 、是把對應一對多的那兩個列lazy=true 改為lazy =false 即可;
2 、對於查詢中如果用的是xxx.load (class ,id )則改為xxx,get(class ,id) ;
3 、在web.xml 文件中加入:
<filter>
<filter-name>hibernateFilter</filter-name>
<filter-class>
org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
</filter-class>
<init-param>
<param-name>singleSession</param-name>
<param-value>false</param-value>
</init-param>
<!-- 這個 -- <init-param> 一定要加不然很可能會報錯 : org.springframework.dao.InvalidDataAccessApiUsageException:Write operations are not allowed in read-only mode (FlushMode.NEVER) - turn your Session into FlushMode.AUTO or remove 'readOnly' marker from transaction definition
-->
</filter>
<filter-mapping>
<filter-name>hibernateFilter</filter-name>
<url-pattern>*.wllt</url-pattern>
</filter-mapping>
對以上方法進行一一測試,到后來結果都是一樣,出現同樣的異常,其實spring 能很好地解決這個問題,Spring 框架為Hibernate 延遲加載與DAO 模式的整合提供了一種方便的解決方法。以一個Web 應用為例,Spring 提供了OpenSessionInViewFilter 和OpenSessionInViewInterceptor 。我們可以隨意選擇一個類來實現相同的功能。兩種方法唯一的不同就在於interceptor 在Spring 容器中運行並被配置在web 應用的上下文中,而Filter 在Spring 之前運行並被配置在web.xml 中。不管用哪個,他們都在請求將當前會話與當前(數據庫)線程綁定時打開Hibernate 會話。一旦已綁定到線程,這個打開了的Hibernate 會話可以在DAO 實現類中透明地使用。這個會話會為延遲加載數據庫中值對象的視圖保持打開狀態。一旦這個邏輯視圖完成了,Hibernate 會話會在Filter 的doFilter 方法或者Interceptor 的postHandle 方法中被關閉。用spring 解決這個問題而且不用把lazy 設置為false ,提高性能。
方法是:在web.xml 中加入以下配置:
<filter>
<filter-name>hibernateFilter</filter-name>
<filter-class>
org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>hibernateFilter</filter-name>
<url-pattern>*.action</url-pattern>
</filter-mapping>
如果把這個配置隨意地加到web.xml 的最后,會出現相應的錯誤,其實該過濾器和Struts2 的過濾器有順序的問題,應該是:
OpenSessionInViewFilter
FilterDispatcher
的順序,最后調整過濾器的順序,問題解決。
注意:有些時候會出現一些頁面或者一些其他的action 過濾不到,可以修改如下:
<url-pattern>/*</url-pattern>