在 Spring Web MVC 环境下使用 DWR

levysnail 2013-01-18

DWR 简介

目前 Ajax 的开发框架有很多,使用这些框架可以简化 Ajax 的开发。DWR (Direct Web Remoting) 是一个用于改善 Web 页面与 Java 类交互的远程服务器端 Ajax 开源框架。DWR 可以动态生成基于 Java 类的 JavaScript 代码。对于公开的每个类,DWR 帮我们做好了创建对象、发送数据、接受响应等许多繁琐的工作,大大节省了客户端代码和工作量。

示例应用程序:用户注册

本文使用的示例应用程序是一个简单的用户注册过程。借助简化了的数据模型,主要介绍 DWR 和 Spring MVC 的结合。我们先来研究一下 DWR 如何启用程序的 Ajax 功能。

配置 DWR

首先,需要下载 dwr.jar 文件,把它放在 web 程序的 WEB-INF/lib 目录下面。清单 1 显示了要公开给 Ajax 的 Java 方法。


清单 1. 需要公开的 userManager 类的两个方法

public int findExistedUser(String name) 
 public List<User> showUsers()

接下来需要配置 DWR,告诉它 Ajax 应当如何构建 userManager 并调用这些方法。在清单 2 所示的 dwr.xml 文件中进行配置。


清单 2. 配置 DWR,公开需要远程调用的方法

<?xml version="1.0" encoding="UTF-8"?> 
 <!DOCTYPE dwr PUBLIC "-//GetAhead Limited//DTD Direct Web Remoting 1.0//EN" 
"http://www.getahead.ltd.uk/dwr/dwr10.dtd"> 
 <dwr> 
 <allow> 
 <create creator="spring" javascript="userManager" scope="application"> 
 <param name="beanName" value="userManager"/> 
 <include method="findExistedUser"/> 
 <include method="showAllUsers"/> 
 </create> 
 <convert converter="bean" match="com.ibm.osl.entity.User"> 
 <param name="include" value="name"/> 
 </convert> 
 </allow> 
 </dwr>

creator 属性用来指定使用哪种创造器。如果 creator 属性被设置为值 new,意味着 DWR 调用类的默认构造函数来获得实例。这里通过与 Spring 进行集成来获得实例,creator="spring" 提供了一个 Spring 的创造器,允许直接调用 Spring 容器中的 bean,然后 DWR 将 bean 转换成一个 javascript 对象。SpringCreator 这个创造器会查找 Spring 所配置的 bean,并创建它们。Javascript 属性用于指定浏览器中被创造出来的对象的名字。scope 属性指定这个 bean 的生命周期。嵌套在 create 元素内的 param 元素的 name 属性值可以是 class,beanName 等。此处用 beanName,value 的值 userManager 是在 beans.xml 中定义的某个 id 值。include 元素指定公开的方法名称。也可以用 exclude 元素指定不想被被访问的方法。

convert 元素的作用是告诉 DWR 在服务器端的 Java 对象表示和 JavaScript 之间如何转换数据类型。

DWR 能自动地在 Java 对象和 JavaScript 表示之间转换简单数据类型。这些类型包括 Java 原生类型和它们各自的类表示,还有数组和集合类型。这里 convert 元素告诉 DWR 用 bean 转换器处理 showAllUsers 方法返回的 List<User> 对象,并指定序列化中只包含 User 类 的 name 属性。

接下来在清单 3 中定义需要的 bean 。


清单 3. 配置 beans.xml

<beans> 
    <bean id="userManager" class="developworks.dwrspring.service.impl.UserManagerImpl"/> 
 </beans>

有三种方式寻找配置文件 beans.xml,最简单的方式是使用 org.springframework.web.context.ContextLoaderListener。需要在 web.xml 中做如下配置:


清单 4. 使用 ContextLoaderListener 寻找配置文件 beans.xml

<context-param> 
 <param-name>contextConfigLocation</param-name> 
 <param-value>/WEB-INF/beans.xml</param-value> 
 </context-param> 
 <listener> 
 <listener-class> 
 org.springframework.web.context.ContextLoaderListener 
 </listener-class> 
 </listener>

测试部署

如果在 web.xml 中把 init-param 的 debug 属性设置为 true,就会启用 DWR 的测试模式,如清单 5 所示。访问 /[web-app]/dwr/ 就可以看到服务器暴露出来的所有类列表。


清单 5. 将 web.xml 的 debug 属性设置为 true

<init-param> 
 <param-name>debug</param-name> 
 <param-value>true</param-value> 
 </init-param>



清单 6. 服务器端暴露出来的所有类列表

Classes known to DWR: 
    * userManager (developworks.dwrspring.service.impl.UserManagerImpl)

点击进入 userManager 可以看到该类暴露出的所有方法,并可以进行测试。这里有两个用户自己定义的方法,可以在可访问的方法旁边的文本框中输入参数值并点击 Execute 按钮调用方法。服务器的响应如果是简单值,会在方法旁边直接显示。其他的输出将在警告框中用 JSON 标注显示出来。这样,测试页面不仅可以检查公开了哪个类和方法用于远程调用,还可以测试每个方法是否正常工作。下面让我们看下本例中的两个方法:

函数 findExistedUser("") 可以查找是否存在该用户。若不输入参数,点击 Execute 按钮,显示 0。在函数参数中输入已存在的 id,如 "[email protected]",点击 Execute 按钮,显示 1。

函数 showAllUsers() 可以显示所有的用户名。点击 Execute 按钮,会弹出一个显示所有 user 信息的警告框,内容如清单 7。


清单 7. 点击 Execute 按钮返回的 JSON 对象

[ 
 {name:"[email protected]"}, 
 {name:"[email protected]"} 
 ]

调用远程对象

对于公开的每个类,DWR 动态地生成包含在 Web 页面中的 JavaScript。生成的 JavaScript 包含存根函数,代表 Java 类对应的方法。Java 方法与对应的 JavaScript 函数之间的映射规则为 JavaScriptName.methodName(methodParams ..., callBack),其中 JavaScriptName 是 creator 属性指定浏览器中被创造出来的对象名字,methodName 是 Java 的方法名,methodParams 代表 Java 方法的参数,最后的 callBack 是 Java 方法返回后要回调的 JavaScript 函数。清单 8 是在 jsp 页面中使用 DWR 创建的 javascript 对象调用公开的方法。


清单 8. 用 javascript 对象调用公开的方法

function callBackfindExistedUser(data){ 
 var userExistedMessage = $("userExistedMessage"); 
 if (data == 1) 
 userExistedMessage.style.display = "block"; 
 else 
 userExistedMessage.style.display = "none"; 
 } 

 function findExistedUser(){ 
 var name = DWRUtil.getValue("name"); 
 if (name != "") 
 userManager.findExistedUser(name,callBackfindExistedUser); 
 }

在 jsp 页面使用 DWR,需要添加清单 9 的代码。


清单 9. 在 jsp 页面使用 DWR 要包含的文件

<script type='text/javascript' src='dwr/interface/userManager.js'></script> 
 <script type='text/javascript' src='dwr/engine.js'></script> 
 <script type='text/javascript' src='dwr/util.js'></script>

DWR 会自动根据 UserManager 类帮我们生成了 userManager.js 文件。engine.js 是用来转换动态生成接口的 javascript 函数调用,util.js 包含了一些工具函数,让页面对 javascript 的调用更加方便。

 

Spring MVC 简介

Spring 框架提供了构建 Web 应用程序的 MVC 模块。Spring MVC 内建了一个请求驱动的 web mvc 框架,以一个 DispatcherServlet 分发器为中心,将 web 请求分发到各个不同的处理器进行处理。Spring MVC 分离了控制器、模型对象、分派器以及处理程序对象的角色。

配置 Spring MVC

需要下载 spring.jar 和 spring-webmvc.jar 包,将它放在 web 程序的 WEB-INF/lib 目录下面。

本例中使用控制器 AbstractWizardFormController,它的主要功能是允许多个表单共用一个表单对象,通常一些表单填的内容比较多,表单会很长,可以将表单分成几页,让用户一页页去完成。表单对象的属性与多个页面所有需填写的对象绑定。AbstractWizardFormController 类的 processFinish 方法定义的是全部表单完成后提交的处理。所有的表单内容都收集在 command 对象中,通过 ModelAndView 的构造方法把它放在 model 对象中,并在 view 页面上显示出来。


清单 10. processFinish 方法将表单内容传输给 view 页面

protected ModelAndView processFinish(HttpServletRequest request, 
 HttpServletResponse response, 
 Object command, 
 BindException exception) throws Exception { 
 RegisterForm registerForm = (RegisterForm) command; 
 return new ModelAndView(this.getSuccessView(), "registerForm", registerForm); 
 }

清单 11 在 mvc-config.xml 中对 controller 进行配置。


清单 11. 对 controller 进行配置

<bean id="registerController" class=" developworks.dwrspring.action.RegisterController"> 
    <property name="commandClass" value="developworks.dwrspring.vo.RegisterForm"> 
 </property> 
 <property name="userManager" ref="userManager"></property> 
 <property name="successView" value="account/success"/> 
 <property name="pages"> 
 <list> 
 <value>account/register1</value> 
 <value>account/register2</value> 
 </list> 
 </property> 
 </bean>

这里 commandClass 用来绑定 form 的 bean class。list 元素定义的两个 value 值分别表示表单的第一个页面和第二个页面。successView 属性表示表单提交后要转到的页面。

第一个页面中的表单内容如下:


清单 12. 第一个表单页面

<form:form action="register.action" method="post"> 
 <form:input path="name" onblur="javascript:findExistedUser()"/> 
 <form:input path="age"/> 
 <input type="submit" value="Next" name="_target1" /> 
 </form:form>

path 中的属性 name 对应的是表单对象 RegisterForm 的 name 属性。Javascript 方法 findExistedUser 会调用 DWR 产生的 javascript 对象的 findExistedUser 方法来判断是否存在该用户。

_target1 的数字 1 表示在 xml 文件中 <list> 的顺序。这里 _target1 对应的是 account/register2 页面。即提交后转到第二个表单页面继续填写。

第二个页面中的表单内容如下:


清单 13. 第二个表单页面

<form:form action="register.action" method="post"> 
 <form:select path="location"> 
 <form:option value="" label="---"/> 
 <form:options items="${location}"/> 
 </form:select> 
 <input class="button" type="submit" value="Back" name="_target0"/> 
 <input class="button" type="submit" value="Submit" name="_finish" /> 
 </form:form>

_target0 对应的是 account/register1 页面。即回到前一个表单页面。

_finish 会执行 controller 的 processFinish 方法。

path 中的属性 location 所含的数据来自于 controller 的 referenceData 函数为表单页面准备的数据。


清单 14. 为页面的 select 框准备数据

protected Map<String,Object> referenceData(HttpServletRequest request, int page) 
     throws Exception { 
 refData = new HashMap<String,Object>(); 
 if (page == 1){ 
 Map<String,Object> location = new HashMap<String,Object>(); 
 location.put("0", "Beijing"); 
 location.put("1", "Shanghai"); 
 refData.put("location", location); 
 } 
 return refData; 
 }

referenceData 函数为第二个页面 register2.jsp 的 select 框的 location 下拉框添加选项。

最后在 success 页面通过 <c:out value="${registerForm.name}" /> 等 jstl 标签显示先前表单输入的值。用户还可以通过点击按钮 showAllUsers 来显示当前所有用户。因为在 convert 属性中只配置了 name 属性,所以用户只能看到所有的用户名。若输出 age 属性,显示 undefined。


清单 15. 在页面中显示所有的用户名

function showAllUser(){ 
    userManager.showUsers(callBackAllUser); 
 } 
 function callBackAllUser(data){ 
    var result = ""; 
    for (var i = 0; i < data.length; i++){ 
        result += data[i].name + ", "; 
    } 
    DWRUtil.setValue("demoAllUser",result); 
 }
 

使用 DWR 标注

DWR 从 2.0 版本开始支持标注,需要在 JDK1.5 以上使用。使用标注可以省掉 dwr.xml,把这个文件的配置信息都移植到程序代码中。下面看一下配置过程。首先,需要在 web.xml 中配置,把使用 DWR 注释的类都要加在这里。


清单 16. 对 web.xml 进行配置

<init-param> 
    <param-name>classes</param-name> 
    <param-value> 
        com.ibm.osl.service.impl.UserManagerImpl, 
        com.ibm.osl.entity.User, 
    </param-value> 
 </init-param>

接下来对要远程调用的方法进行标注。


清单 17. 配置要远程调用的方法

@RemoteProxy(name="userManager",creator=SpringCreator.class, 
 creatorParams={@Param(name="beanName",value="userManager"),}) 
 public class UserManagerImpl implements UserManager { 
    @RemoteMethod 
    public int findExistedUser(String name){ 
 return accountDAO.findExistedUser(String name); 
    } 
    @RemoteMethod 
    public List<User> showUsers(){ 
 return accountDAO.showUsers(); 
    } 
 }

@RemoteProxy 标注要远程调用的类,也就是告诉 DWR,这个 class 是要暴露出来的。相当于 dwr.xml 中的 <creator> 标签。RemoteProxy 的 name 指定暴露出来的 DWR 接口的名字,creator 指定使用哪种创造器,本例中使用 SpringCreator。creatorParams 指定创造器的其他参数。

@RemoteMethod 标注要远程调用的方法。只有加了 @RemoteMethod 标注的方法才会被暴露。

接下来对 bean 的转换进行配置。


清单 18. 配置要转换的 bean

@DataTransferObject(converter=BeanConverter.class) 
 public class User { 
    private String name; 
    private Integer age; 
    public String getName() { 
        return name; 
    } 

    @RemoteProperty 
    public void setName(String name) { 
        this.name = name; 
    } 

    public Integer getAge() { 
        return age; 
    } 

    public void setAge(Integer age) { 
        this.age = age; 
    } 
 }

@DataTransferObject 标注在客户端和服务器之间转换类。相当于 dwr.xml 中的 <convert> 标签。@RemoteProperty 标注在类中需要转换的属性。这里只对 name 进行标注。进行以上配置后,就可以达到和使用 dwr 的 xml 配置文件一样的效果了。

 

DWR 的安全性

在使用 DWR 时要明确哪些类和方法是可以被远程调用的。dwr.xml 要求为每一个远程类定义一个 create 项。还可以通过指定 include 和 exclude 元素控制远程调用 Bean 中可以被调用的方法,而不是把所有的方法都暴露给服务器端。除此之外,如果要 DWR 在转换 JavaBean 到 Javascript 时有一定限制,可以控制哪些 bean 的属性可以被转换。特别地,在做删除和更新操作时,可以通过 WebContext 得到 session 来判断用户是否有权限进行该操作。另外,不要在生产环境中打开 debug 属性。

 

结束语

DWR 为在客户端使用服务器端的对象和方法提供了一条捷径,并且 DWR 还提供了一个 Spring 的创建器,允许直接调用 Spring 容器中的 bean,将 bean 转换成一个 javascript 对象。通过本文,您了解了如何结合使用 DWR 和 Spring MVC。

参考资料

学习

讨论

关于作者

高乐怡,目前是 IBM 中国系统与科技中心的软件工程师,从事测试工具的开发工作。

相关推荐