(来源:http://jiangning85.blog.sohu.com/148177571.html)
JSON(Java Script Object Notation),是一种语言无关的数据交换格式。 JSON插件是Structs 2 的Ajax插件,通过利用JSON插件,开发者可以很方便,灵活的利用Ajax进行开发。 Json是一种轻量级的数据交换格式,JSon插件提供了一种名为json的Action ResultType 。 一旦为Action指定了该结果处理类型,JSON插件就会自动将Action里的数据序列化成JSON格式的数据, 并返回给客户端物理视图的JavaScript。简单的说,JSON插件允许我们在JavaScript中异步的调用Action,而且Action不需 要指定视图来显示Action的信息显示。 而是由JSON插件来负责具体将Action里面具体的信息返回给调用页面。 Json的数据格式可简单如下形式: person = { name: 'Jim',age: 18,gender: 'man'}。 如果action的属性很多,我们想要从Action返回到调用页面的数据。 这个时候配置includeProperties或者excludeProperties拦截器即可。
而这2个拦截器的定义都在struts2的json-default包内,所以要使用该拦截器的包都要继承自json-default。
<struts>
<constant name="struts.objectFactory" value="spring"/>
<include file="struts-admin.xml"></include>
<package name="default" extends="json-default">
<action name="person" class="com.person.PersonAction" method="view">
<result type="json">
<param name="includeProperties">
person/.name,persoon/.age,person/.gender
</param>>
</result>
</action>
</package>
</struts>
利用Struts 2的支持的可配置结果,可以达到过滤器的效果。Action的处理结果配置支持正则表达式。
但是如果返回的对象是一个数组格式的Json数据。比如peson Bean中有对象persion1...person9,而我只要person1的json数据,
则可以用如下的正则表达式。
<struts>
<constant name="struts.objectFactory" value="spring"/>
<include file="struts-admin.xml"></include>
<package name="default" extends="json-default">
<action name="person" class="com.person.PersonAction" method="view">
<result type="json">
<param name="includeProperties">
person/[/d+/]/.person1
</param>>
</result>
</action>
</package>
</struts>
excludeProperties拦截器的用法与此类同,如果拦截的仅仅是一个对象,如果拦截掉person Bean的整个对象,使用如下配置
<struts>
<constant name="struts.objectFactory" value="spring"/>
<include file="struts-admin.xml"></include>
<package name="default" extends="json-default">
<action name="person" class="com.person.PersonAction" method="view">
<result type="json">
<param name="excludeProperties">
person
</param>>
</result>
</action>
</package>
</struts>
需要注意的是,如果用JSON插件把返回结果定为JSON。而JSON的原理是在ACTION中的get方法都会序列化,
所以前面是get的方法只要没指定不序列化,都会执行。
如果该方法一定要命名为get*(比如实现了什么接口),
那么可以在该方法的前面加注解声明该方法不做序列化。
注解的方式为:@JSON(serialize=false)
除此之外,JSON注释还支持如下几个域:
serialize:设置是否序列化该属性
deserialize:设置是否反序列化该属性。
format:设置用于格式化输出、解析日期表单域的格式。例如"yyyy-MM-dd'T'HH:mm:ss"。
//使用注释语法来改变该属性序列化后的属性名
@JSON(name="newName")
public String getName()
{
return this.name;
}
需要引入 import com.googlecode.jsonplugin.annotations.JSON;
@JSON(serialize=false)
public User getUser() {
return this.User;
}
@JSON(format="yyyy-MM-dd")
public Date getStartDate() {
return this.startDate;
}
实际在使用的时候,如果不指定includeProperties或者excludeProperties,同时又没有 @JSON(serialize=false),那么会默认返回所有Action中的有get、set方法的属性,通常这样做工程上也没什么问题,而且如 果我们在使用extjs的话,那么当发起一个请求如下所示:
Ext.Ajax.request({
url : 'json/ViewShowAction_getChildrenDevices.do?viewName='+viewName,
disableCaching : true,
method : 'GET',
。。。
})
然后如果我们想要获取服务器端返回的结果,需要在method后面再添加处理函数,如下所示:
success : function(result, request) {
// 获取下级路由器列表并显示
thisDevice.children = [];
//以下两句是获得该视图的背景图片url,同时对页面背景进行设置,与该函数的其它功能没有交叉,写在这里不够优雅
var background=Ext.decode(result.responseText).background;
var deviceList = Ext.decode(result.responseText).deviceList;
for (i = 0; i < deviceList.length; i++) {
thisDevice.children[i] = new cernet2.Device(deviceList[i],thisDevice);
thisDevice.children[i].showInBigMap();
。。。
}
thisDevice.fetchChildrenDone = true;
thisDevice.showChildrenLinkInMap(viewName);
},
failure : function(result, request) {
//Ext.log('获取路由器时有错误发生,错误内容为:'+ result.responseText);
}
实际上extjs是把所有的Action类里面有get set方法的属性都已JSON格式存入了result.responseText中,我们从那里就可以获得所要的数据,比如var background=Ext.decode(result.responseText).background;一句,是得到服务器端的 background数据(String类型),而var deviceList = Ext.decode(result.responseText).deviceList;则是从服务器端获得了一个deviceList(List类 型)。
“在struts2中使用JSON(一)”中讲述了如何从服务器端得到数据,那么如何才能将请求以JSON的格式传给客户端呢?这个需求可以表述为,如果 客户端要传一个JavaScript对象给服务器端,而且服务器端正好可以有一个与之对应的java对象进行接收。
实现这个需求的方式有两种:1.使用表单,这里同样以extjs的表单为例:
var addSwitch = new Ext.FormPanel( {
labelWidth : 113, // label settings here cascade unless overridden
frame : true,
title : '',
bodyStyle : 'padding:5px 5px 0',
width : 420,
defaults : {
width : 230
},
labelAlign: 'left',
defaultType : 'textfield',
items : [ {
fieldLabel : 'Name',
name : 'switch1.name',
allowBlank:true
},{
id:'ipv4Address',
fieldLabel : 'IPV4Address',
name : 'switch1.ipv4Address',
allowBlank:true
}, {
id:'ipv6Address',
fieldLabel : 'IPV6Address',
name : 'switch1.ipv6Address',
allowBlank:true
}, {
fieldLabel : 'CommunityName',
allowBlank:false,
blankText:"can't be empty!",
name : 'switch1.communityName'
},
。。。。
对于items中的每一项,我们的name属性都设置成如"switch1.communityName",switch1对应着服务器端action里的switch1属性(它是个对象),switch1对象对应的java类如下:
public class Switch {
private Long id;
private String ipv4Address;
private String ipv6Address;
private String name;
private String communityName;
private String SNMPVersion;
private String status;
。。。
}
注意:Switch类对于每个属性都要有get set方法,这样服务器端的switch1对象就可以接收来自前台表单的各个元素。而且此种方法前端是不需要有switch的js类的只需要设置name字段即可。
这种方法虽然简单,但是局限性比较大,如果我不想传表单的元素,而是直接想把前台的js对象传给后台该怎么做呢?下篇文章会进行详细讲解。
对于我们想把前端的js对象传给后台的java对象接收,可以使用DWR(Direct Web Remoting),是一个开源 的类库,可以帮助开发人员开发包含AJAX 技术的网站.它可以允许在浏览器里的代码使用运行在WEB服务器上的JAVA函数,就像它就在浏览器里一样.作为一个java open source library,DWR 可以帮助开发人员完成应用AJAX技术的web程序。它可以让浏览器上的javascript方法调用运行在web服务器上java方法。 DWR 主要由两部门组成.javascript与web服务器通信并更新web页;运行在web服务器的Servlet处理请求并把响应发回浏览器。 DWR 采 用新颖的方法实现了AJAX(本来也没有确切的定义),在java代码基础上动态的生成javascript代码。web开发者可以直接调用这些 javascript代码,然而真正的代码是运行在web服务器上的java code。出与安全考虑,开发者必须配置哪些java class暴露给DWR .(dwr .xml) 。这种从(java到javascript)调用机制给用户一种感觉,好象常规的RPC机制,或RMI or SOAP.但是它运行在web上,不需要任何浏览器插件。 DWR 不认为浏览器和web服务器之间协议重要,把系统界面放在首位。最大挑战是java method call的同步特征与ajax异步特性之间的矛盾。在异步模型里,结果只有在方法结束后才有效。DWR 解决了这个问题,把回调函数当成参数传给方法,处理完成后,自动调用回调方法。
一、使用方法
1.1、dwr.xml的配置
<!DOCTYPE dwr PUBLIC
"-//GetAhead Limited//DTD Direct Web Remoting 2.0//EN"
"http://getahead.org/dwr/dwr20.dtd">
<dwr>
<allow>
<create creator="new" javascript="InterfaceAction" scope="script">
<param name="class" value="com.config.action.InterfaceAction"/>
<include method="changeInterfaceInfo"/>
</create>
<convert match="com.config.dto.IfInterface" converter="bean"/>
</allow>
</dwr>
<allow>标签中包括可以暴露给javascript访问的东西。<create>标签中指定javascript中可以访 问的java类,并定义DWR应当如何获得要进行远程的类的实例。creator="new"属性指定java类实例的生成方式,new意味着DWR应当 调用类的默认构造函数来获得实例,其他的还有spring方式,通过与IOC容器Spring进行集成来获得实例等等。 javascript="InterfaceAction"属性指定javascript代码访问对象时使用的名称。<param name="class" value="com.config.action.InterfaceAction"/>指定相关联的java类,<include method="changeInterfaceInfo"/>表明InterfaceAction的changeInterfaceInfo方法 可以暴露给js使用。<convert match="com.config.dto.IfInterface" converter="bean"/>这一项非常关键,表明了前端生成一个IfInterface的JS对象,可以直接传给后端,并将 IfInterface的java对象赋值。注意:IfInterface必须每个属性的有get set方法。地方
1.2、web.xml的配置
在web.xml中加上如下配置项:
<servlet-name>dwr-invoker</servlet-name>
<servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>true</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dwr-invoker</servlet-name>
<url-pattern>/dwr/*</url-pattern>
</servlet-mapping>
表明所有的/dwr/*请求都交由此servlet处理。
1.3、javascript中调用
首先,引入javascript脚本
<script type='text/javascript' src='dwr/interface/SwitchAction.js'></script>
<script type='text/javascript' src='dwr/engine.js'></script>
<script type='text/javascript' src='dwr/util.js'></script>(可选)
其中SwitchAction.js是dwr根据配置文件自动生成的,engine.js和util.js是dwr自带的脚本文件。
其次,编写调用java方法的javascript函数
InterfaceAction.changeInterfaceInfo(interface,ipv4Address,ipv6Address, { callback:handleChangeInterfaceInfo,
timeout:5000,
errorHandler:function(message) { alert("Oops: " + message);}
});
其中handleChangeInterfaceInfo是接收服务器端结果的回调函数,如下所示:
function handleChangeInterfaceInfo(result) {
if(result=='success'){
store.reload();
}else{
Ext.MessageBox.alert('',"Set operation has failed!");
store.reload();
}
}
其中的参数interface(js类)定义如下:
savi.Interface = function() {
this.ifIndex;
this.validationStatus;
this.trustStatus;
this.bindingNum;
this.ipVersion;
}
然后通过 var interface=new savi.Interface();并对其属性赋值。
而对应的java端方法如下:
public String changeInterfaceInfo(IfInterface ifInterface,String ipv4Address,String ipv6Address){
}
IfInterface是于js中的Interface对应的java类,定义如下:
public class IfInterface {
private Integer ifIndex;
private Integer ipVersion;
private Integer validationStatus;
private Integer trustStatus;
private Integer bindingNum;
。。。
}
注意:所有属性要有get set方法。
二、源码解析
dwr 的设计很象webwork2的设计,隐藏http协议,扩展性,兼容性及强。
通过研究uk.ltd.getahead.dwr .DWRServlet这个servlet来研究下dwr 到底是如何工作的。这样所有的/dwr /*所有请求都由这个servlet来处理,它到底处理了些什么?
1 、 web服务器启动,DWRServlet init()方法调用,init主要做了以下工作。
设置日志级别、实例化DWR 用到的单例类(这些类在jvm中只有一个实例对象)、读去配置文件(包括dwr .jar包中的dwr .xml,WEB-INF/dwr .xml. config*.xml)。
2 、请求处理
DWRServlet.doGet, doPost方法都调用processor.handle(req, resp)方法处理。Processor对象在init()方法中已经初始化了。
public void handle(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String pathinfo = req.getPathInfo();;
if (pathinfo == null || pathinfo.length(); == 0 || pathinfo.equals( "/" ););
{
resp.sendRedirect(req.getContextPath(); + req.getServletPath (); + '/' + "index.html" );;
} else
if (pathinfo != null && pathinfo.equalsIgnoreCase( "/index.html" ););
{
doIndex(req, resp);;
} else
if (pathinfo != null && pathinfo.startsWith( "/test/" ););
{
doTest(req, resp);;
} else
if (pathinfo != null && pathinfo.equalsIgnoreCase( "/engine.js" ););
{
doFile(resp, "engine.js" , "text/javascript" );;
} else
if (pathinfo != null && pathinfo.equalsIgnoreCase( "/util.js" ););
{
doFile(resp, "util.js" , "text/javascript" );;
} else
if (pathinfo != null && pathinfo.equalsIgnoreCase( "/deprecated.js" ););
{
doFile(resp, "deprecated.js" , "text/javascript" );;
} else
if (pathinfo != null && pathinfo.startsWith( "/interface/" ););
{
doInterface(req, resp);;
} else
if (pathinfo != null && pathinfo.startsWith( "/exec" ););
{
doExec(req, resp);;
} else
{
log.warn( "Page not found. In debug/test mode try viewing /[WEB-APP]/dwr /" );;
resp.sendError( 404 );;
}
}
哦。这些恍然大悟。dwr /*处理的请求也就这几种。
( 1 )dwr /index.html,dwr /test/这种只能在debug模式下使用,调试用。
dwr /engine.js,dwr /util.js,dwr /deprecated.js当这个请求到达,从dwr .jar包中读取文件流,响应回去。(重复请求有缓存)
( 2 )当dwr / interface /这种请求到来,(例如我们在index.html中的 <script type= 'text/javascript' src= 'dwr /interface/JDate.js' ></script>)DWR 做一件伟大的事。把我们在WEB-INF/dwr .xml中的
<create creator= "new" javascript= "JDate" >
<param name= "class" value= "java.util.Date" />
</create>
Java.util.Date转化为javascript函数。
http: //localhost:port/simpledwr/dwr /interface/JDate.js看看吧。
细节也比较简单,通过java反射,把方法都写成javascript特定的方法。(我觉得这些转换可以放到缓存里,下次调用没必要再生成一遍,不知道作者为什么没这样做)。
( 3 )dwr /exec
javascript调用方法时发送这种请求,可能是XMLHttpRequest或IFrame发送。
当然,javascript调用的方法签名与java代码一致,包括参数,还有javascript的回调方法也传到了服务器端,在服 务器端很容易实现。回调方法的java的执行结果 返回类似 <script>callMethod(结果)<script>的 javascript字符串,在浏览器执行。哈,一切就这么简单,巧妙。
dwr 的设计构思很是巧妙。
第一、把java类转化为javascript类由dwr 自动完成,只需简单的配置。
第二、应用起来极其简单。开发者不要该服务器代码就可以集成。
第三、容易测试。和webwork一样,隐藏的http协议。
第四、及强扩展性。例如与spring集成,只需修改一点代码。
第五、性能。就我与jason,等简单比较,dwr 性能可能是最好的。
第六、自动把java对象转化为javascript对象,并且及易扩展。
二、参考网址
1.http://www.javaeye.com/topic/16424
2.http://baike.baidu.com/view/73492.htm
3.http://directwebremoting.org/dwr/introduction/scripting-dwr.html