Overview

secretepdb这个项目需要展现一些统计图表,我们使用了googlegchart,图表的数据源从数据库异步获取。这里就用到了Struts2Jquery Ajax的集成。本身都是很规范的东西,应该不会出什么问题,但是特定条件下里面有个坑,在这里梳理一遍。

既然是Struts2Ajax集成,肯定也会用到Struts2本身的一些东西,可以先看看我之前写的:Struts2总结 这篇文章,接下来可能就好读一些。

Struts2总结 中提到的一样,本文使用的依然是struts-2.3.24.1

2.1 需要的jar

2.1.1 Struts2接口包

Struts2内置提供了对各种插件的支持,比如dojojsonsitemesh等,并在doc文件夹中中提供了帮助文章,doc/struts2-plugins这个文件夹中就是所有支持的插件的帮助文档。

我们跟Ajax通讯所需要的就是就是struts2-json-plugin这个插件,等等...我们是要集成Ajax,可为什么要集成struts2-json-plugin这个看起来毫不相关的插件呢。这是因为,异步传输的通讯方式本身Struts2就支持,配置下struts.xml就可以实现,但是JAVA后台跟前端传输需要使用Json格式传输,而JAVA对象和Json格式的相互转换,就需要用到专有的Json包,所以Struts2提供了下面的插件:

  • struts2-json-plugin-2.3.24.1.jar

这个插件也可以从struts-2.3.24.1的官方下载包的struts-2.3.24.1-all.zip中的lib文件夹中找到。

2.1.2 Json需要的包

处理Json格式需要使用下面的包:

  • json-lib-2.4-jdk15.jar

因为Json包是独立的存在,所以jar的版本号跟Struts2没什么联系。整体来看就是,Struts2Json都是独立存在的,而Struts2提供了一个插件包,用来将Json集成到Struts2中。

json-lib-2.4-jdk15.jar本身还依赖下面的包:

  • commons-beanutils-1.8.0.jar
  • commons-lang-2.4.jar
  • ezmorph-1.0.6.jar
  • log4j-1.2.15.jar
  • commons-collections-3.2.1.jar
  • commons-logging-1.1.14.jar

Struts2所依赖的包情况一样,除了commons-lang-2.4.jar,其他包版本号都没有限制,最简单的方式就是从struts-2.3.24.1-all.zip中的lib文件夹中找,里面都有,实际上因为依赖包跟Struts2有重叠,只需要添加三个包:commons-beanutils-1.8.0.jarcommons-collections-3.2.1.jarcommons-lang-2.4.jar

特别需要说明的就是commons-lang-2.4.jar,因为commons-lang-2.4.jarstruts-2.3.24.1依赖的commons-lang3-3.2.jar包名都不同了,所以同时引入不会产生冲突,而json-lib-2.4-jdk15.jar依赖的是struts-2.3.24.1commons-lang 2.0版本的包都可以),而不是commons-lang3-3.2.jar,如果不引入commons-lang-2.4.jar,不会报错,但是就是没办法成功转化数据格式。

2.2 集成配置

2.2.1 前端js请求

$.ajax({
                type:"post",
                url:"ajaxAction/getDataByTypeStatisticsAction",//需要用来处理ajax请求的action,getDataByType为处理的方法名,StatisticsAction为action名,注意这里的ajaxAction/的写法,后面配置struts.xml的时候会提到。
async: true,//通讯是否需要异步执行,默认是true,也就是异步传输,当执行完$.ajax()函数后,js代码继续向下执行,不会等待结果返回。在这里特别的写出来是因为有时候,比如画图,你需要先拿到数据才能画图,因此这里可以设置为false,这样就可以拿到结果,程序才会执行$.ajax()后的代码。
                data:{//向后台传递的参数
                    name:"Chris",
                    age:5,
                    position:"tt"//这里不要加","  不然会报错,而且根本不会提示错误地方
                },
                dataType:"json",//设置需要返回的数据类型
                success:function(data){//回调函数,通讯成功会调用此函数,参数data用于接收后台的返回值。
                    var d = eval("("+data+")");//将数据转换成json类型,可以把data用alert()输出出来看看到底是什么样的结构
                    //得到的d是一个形如{"key":"value","key1":"value1"}的数据类型,然后取值出来
                    
                    alert(""+d.name+"");
                    alert(""+d.age+"");
                    alert(""+d.position+"");
                    
                },
                error:function(){//回调函数,通讯失败会调用此函数
                    alert("系统异常,请稍后重试!");
                }//这里不要加","
});

上面的Js脚本,向名为StatisticsActionaction发送异步传输的请求,并传递了三个参数namegenderposition,并指定了StatisticsAction中的getDataByType方法处理该请求。具体解释已经注释在代码中了,很容易明白。

2.2.2 配置struts.xml

struts.xml就是前台和后台的连接,我们已经知道了普通的action请求应该怎么配,下面给出Struts2Ajax通讯的配置方法:

<struts>
<constant name="struts.devMode" value="true" />
<constant name="struts.action.excludePattern" value="/static/.*?" />
<!--普通的action配置,extends是struts-default-->
<package name="front" extends="struts-default" >
    <action name="searchDBByID" class="action.SearchDBByIDAction" method="execute">
        <result>
            /searchResults.jsp
        </result>
        <result name="noResults">
            /searchNoResults.html
        </result>
    </action>
</package>
<!--Ajax通讯需要的action配置,extends必须是json-default-->
<package name="ajax" namespace="/ajaxAction" extends="json-default" >

    <action name="*StatisticsAction" method="{1}" class="action.StatisticsAction">
        <!--注意,这里的result type要设置成json,里面不需要有返回页面,因为返回值是交给前端的Js接收-->
        <result name="success" type="json">
            <!-- result是action中设置的变量名,也是页面需要返回的数据,该变量在action中必须有setter和getter方法 -->
            <param name="root">result</param>  
        </result>
    </action>
    
</package>
</struts>

这里面有两个地方需要注意:

  • 为了防止混淆,我新建了一个nameajax<package>与原来的并列,并将这个<package>namespace属性设置为了/ajaxAction,所以前端在请求的时候就不能直接使用action名字了,而是需要使用/ajaxAction/action名字。
  • <action name="*StatisticsAction" method="{1}" class="action.StatisticsAction">中的name属性是包含通配符的,是为了匹配多action请求,上面Js中请求是url:"ajaxAction/getDataByTypeStatisticsAction",其中,ajaxAction/前缀使得这个请求映射到了nameajax<package>上,而getDataByTypeStatisticsActionaction中的*StatisticsAction匹配,*部分就指代了getDataByTypeactionmethod="{1}"相当于method="第一个通配符配到的字符串",也就是method="getDataByType"

这两个设置,就可以将前台的Js请求,映射到了指定的action的指定方法。

2.2.3 StatisticsAction

public class StatisticsAction extends ActionSupport{

    //用来接收前端Js的参数,名字与Js中的参数名字必须一致。
    private String name;
    private int age;
    private String position;
    
    //省略了name,age,position的get和set方法,这里只是省略不写,程序中必须要有。
    ...

    //用来存储返回给前端页面的值,需要与struts.xml中配置的名字一致,并且必须有get和set方法。
    private String result;
    public String getResult() {
        return result;
    }

    public void setResult(String result) {
        this.result = result;
    }

    //Js请求的action方法

    public String getDataByType() {

        Map<String,Object> map = new HashMap<String,Object>();
        //跟传统的action传值一样,这里的name,gender,position由struts接收前端传参后初始化,在action中直接使用就可以了。
        map.put("name", name);
        map.put("gender",gender);
        map.put("position", position)
        //将一个JAVA对象(这里是map对象),转化为一个JSON对象。需要import net.sf.json.JSONArray。       
        JSONObject json = JSONObject.fromObject(map);
        //注意,这里的result为String类型,内容为:
        //"{name:"Chris",age:5,position:"tt"}"
        result = json.toString();
        
        return SUCCESS;
    }

}

2.2.4 再看前端js

我们回过头再看一个js中的细节,回调函数:

success:function(data){//回调函数,通讯成功会调用此函数,参数data用于接收后台的返回值。
    var d = eval("("+data+")");//将数据转换成json类型,可以把data用alert()输出出来看看到底是什么样的结构
    //得到的d是一个形如{"key":"value","key1":"value1"}的数据类型,然后取值出来                  
    alert(""+d.name+"");
    alert(""+d.age+"");
    alert(""+d.position+"");        
}

因为action返回的是一个String类型的值,所以这里回调函数接收到的data,就是一个String类型的值,但是我们需要的是一个js的对象类型,因此使用了eval("("+data+")")String类型的值变成了一个js对象,这是一个通用的做法,所有通过json格式传送到前端的对象,都需要转化为js对象。

可见,使用JSON是因为jsJAVA两种语言对于对象的定义都有自己的标准,不可能将JAVA中的对象直接传递给js中使用,因此需要使用JSON这种统一规范的形式,在JAVA端,使用了JSON包提供的JSONObject.fromObject方法,将一个JAVA对象转化为JSON格式的字符串,传递给前端的jsjs再使用内置的eval()方法将JSON格式的字符串转化为js的对象,从而实现了JAVAjs之间的对象传递。

2.3 JSON简介

直接看阮一峰写的 数据类型和Json格式 吧,对数据类型和JSON格式解释地简洁又足够好,实在没有理由自己再写一段概念上的介绍。

既然提到阮一峰,不得不推荐一下,黑客与画家的译者,中国少有的文采好,能把技术层面的东西讲的深入浅出的软件开发者。他让我想到了台湾的侯捷先生,有涵养,文化底蕴好,文字写得有情感,不生硬,非常值得学习。

My name is Ruan YiFeng(阮一峰). You can call me Frank. I was born in 1970s.
I am an IT developer focusing on web technology, and a strong advocate and believer of Free Software. Now I am employed by Alipay.com as an Node/JavaScript engineer.
I have an Economics degree, and once worked for a local college in Shanghai as an assistant professor.
In spare time, I like reading book, surfing internet, watching movie and taking a leisurely walk outdoors.

好像有点跑题了,下面给出几个不错的JAVA处理JSON的文章:

入门之后也可以直接查看JSON in Java的官方API文档: