感谢原文作者:http://www.javaeye.com/topic/119767
也许朋友们会以为这是 DWR 官方 发布的什么帮助,但非常遗憾这不是。现在不少朋友在使用 DWR 开发项目,我也是其中之一,但苦于关于 DWR 的帮助文档实在太少,很多问题都不得不自己去钻研 DWR 的源码才能解决或理解。经过一段时间的苦苦钻研,总结出那么一点点心得,现在从 DWR 源码实现的角度详细讲解 DWR 的使用,写出来与大家分享。今天我先讲一讲 dwr .xml 的配置。
一、为什么要配置 dwr .xml
要理解 dwr .xml 的配置首先要理解 DWR 的基本功能。 DWR 其功能的强大在于它可以用一种前所未有的便利方式将前端页面中的 js 与后端服务器中的 java 程序进行直接的转换。比如,它可以将 java 程序中的某个 XxxBus 转换成 js 中的一个对象,并将 XxxBus 中的某个方法如 getXXX() 转换成 js 中的一个名为 XxxBus.getXXX() 的 function 。同时,它可以将 java 中的值对象或其它数据包转换成 js 中的对象。理解这一点非常重要。配置文件 dwr .xml 就是让你告诉 DWR 哪些 Bus 你需要在页面中调用,以及你需要调用这些 Bus 中的哪些方法,哪些方法你不需要调用。一旦告诉了 DWR 这些信息,你似乎就可以在页面中像写 java 程序一样的直接调用 Bus ,而跳过了 MVC 层(如 struts 中的 action 和 actionform )以及 MVC 层的繁琐配置与编程。为了帮助你理解这一点我举一个例子,已经清楚的朋友可以跳过。
假如我在项目的 BUS 层写了一个类叫 DepartmentBus 并在这个类中定义了一个方法 getDepartment(String id) ,那么我们先把这个类和它的方法配置到 dwr .xml 中(如何配置我后面再讲)。当 DWR 启动的时候,它会将 java 中的 DepartmentBus 及其方法 getDepartment(String id) 转换成 js 中的 DepartmentBus 及其方法 getDepartment(String id) 。更具体地说,它会动态产生一个叫 DepartmentBus.js 的文件(这个文件可以在测试状态下从“项目地址端口 /dwr /test/DepartmentBus ”中看到)。在这个文件中,会创建一个 DepartmentBus 的对象,一个叫 DepartmentBus.fincDepartment(p0, callback) 的函数。如果你在页面中引入了 DepartmentBus.js 文件,你就可以在该页面的 中调用 DepartmentBus.fincDepartment(p0, callback) 方法, DWR 就可以通过 Ajax 的方式直接去调用服务器端的 DepartmentBus 中的 fincDepartment(String id) 。同理,如果 DepartmentBus 中还有其它的函数,如 createDepartment(Department vo) 、 updateDepartment(Department vo) 等, DWR 也会做如此的转换。
前面我们提到, DWR 可以将服务器端 java 中的对象及其方法转换成客户端的 js 中的对象及其方法,一个棘手的问题就出现了。在 java 中的许多方法都是将某个对象作为它参数或返回值。这些对象往往都是一个个的 JOPO ,即它们有许多的属性,其中包含了需要传递的数据(最典型的就是值对象)。当 js 在调用 java 的某个方法时需要提供这个对象参数,同时在调用完成时需要将对象返回值以 js 可以接受的方式返回。怎样做到这一点呢?最简单的方式当然是让 DWR 再做一次转换,将这些 java 对象转换成 js 的对象,或者将 js 的对象转换成 java 对象。 Ok ,经过 DWR 这样的两次转换,我们就可以在页面与后台间自由地进行函数调用和数据交换。
二、如何配置 dwr .xml 文件
讲到这里,我们现在重新回到 dwr .xml 文件。前面我们提到,如果你需要在页面中调用服务器端的某个类中的方法,你需要在 dwr .xml 中注册这个类及其方法,注册方法如下:
- < create creator = "spring" javascript = "DepartmentBus" scope = "script" >
- < param name = "beanName" value = "departmentBus" />
- create >
Creator是创建这个对象所使用的构建器,如果你希望使用传统的 new 方法就写成 new ,如果你希望使用 spring 来创建则写成 spring 。当然也有其它的创建方法,有哪些方法呢?你可以打开 dwr .jar ,在 org.directwebremoting 包中找到一个 dwr .xml 的文件。在该文件 init 下的 create 中可以看到。
Javascript 是用于你在 js 中调用这个对象时使用什么名称,强烈建议你使用首字母大写,这样在页面中很容易看出这是一个与后台对应的对象。 如果你使用了 spring 并且在 creator 中选择了 spring ,那么你需要一个叫 beanName 的参数,而其值就是在 spring 配置文件中配置的 beanName ,如 departmentBus 。 另外 2 个非常有用的参数是 exclude 和 include , exclude 可以禁止页面调用后台的某个或某些方法,具体的写法是在 create 中加入:
- < exclude method = "createDepartment" />
method部分写的是这些方法不带括号的方法名,如果有多个就写多行 exclude ; include 则规定页面只能调用某些方法。
在 dwr .xml 中注册了需要调用的对象及其方法以后,你应当注册其所有方法的参数和返回值所涉及的对象,注册方法如下 :
- < convert match = "com.htxx.demo.datasource1.model.Department"
- javascript = "Department" converter = "hibernate3" />
match用于告诉 DWR 你将需要把 java 中的哪些类转换成 js 。你可以写成 com.htxx.demo.model.* ,但我并不推荐大家这样使用。为什么呢?如果你像前面那样一个一个地注册对象,则就可以在页面使用这个的语句初始化一个对象:
var dep = new Department();
如果你使用后一种方法去批量注册对象,那么你就不能这样初始化这个对象而只能这样手动注册:
Var dep = {departmentId:null, departmentName:null, ……};
我推荐大家采用第一种方法的好处可以在我后面写的《DWR 帮助说明-如何编写通用的单行编辑框 》充分展现出来。但采用这种方法在 DWR 的现有版本中似乎还有点儿 BUG ,如何解决这个 BUG 我也将在后面的《DWR 帮助说明-dwr 的bug及其解决方法 》中解答。
Javascript 用于说明你在页面中使用这个对象的名称,也强烈建议大家使用 首字母大写。 Converter 用于告诉 DWR 用什么 DWR 的类来执行转换,常用的转换器有 bean 、 object 、 hibernate2 、 hibernate3 等。 DWR 有哪些转换器可以在 dwr .jar 的 dwr .xml (该文件的位置见前文)中找到。我需要强调的是,如果朋友们使用了 hibernate ,那么你需要将需要使用的所有值对象都通过转换器注册。但是我在网上看见很多朋友都使用 bean 转换器来注册。如果你使用 bean 来转换值对象,在运行程序的时候会出现很多问题(这些问题我就不详述了),同时还会出现效率的问题,因为 DWR 会将该值对象的所有属性,及其这些属性的所有属性,所有属性的属性,都以穷举的方式取出来。熟悉 hibernate 的朋友应当马上明白这样将是数据库操作的一个灾难。如果你使用 hibernate2 或 hibernate3 作为转换器将不会发生这样的事,同时, hibernate3 还较好地解决了延迟查询的问题,但 DWR 官方建议我们使用 hibernate 的 openSessionInViewFilter ,这我也不再详述,不清楚的朋友可以查阅 hibernate3 的帮助文档。但另一个问题我不得不提, DWR 在使用延迟查询的时候其实还是有问题的。譬如有一个值对象 Employee 包含一个 Department 的属性,根据延迟查询的规则,在 get 某个 Employee 时,属性 Department 不会马上装载,即使执行 getDepartment() 也不会装载。必须到真正对这个 Department 操作的时候才会装载。既然如此,问题就来了,我们使用 DWR 执行查询的时候,常常是真正到页面才会读取 Department ,这时已经是脱离服务器端到页面端了而不能再得到 Department 。这个问题怎么办呢,最好的办法是在服务器端就提前装载页面需要使用的属性,因为作为开发者他肯定知道哪些属性要在客户端使用,哪些属性不需要。至于如何在服务器端就提前装载,感兴趣的朋友在我的示例中看到。另外一个需要提的是,与 creator 一样,转换器也可以一样地设置 exclude 和 include 参数。但是与 creator 不同的是,它们说明 DWR 在转换对象的时候需要转换或不转换某些属性。这个参数对于 hibernate 的一对一关联非常重要。在 hibernate 中一对一关联是不做延迟查询的,假如有一个值对象 Employee 与值对象 Address 是一对一关联,那么 Employee 中有 Address 的属性,而 Address 中有 Employee 的属性。由于一对一关联不做延迟查询,当 DWR 在转换一个 Employee 是会装载它的属性 Address ,然后在装载 Address 的时候,又会去装载 Address 中的 Employee 属性。如此这样,就会形成一个死循环,最后以堆栈溢出告终。解决这个问题的办法就是禁掉 Address 中的 Employee 属性,避免产生死循环。具体写法如下:
- < convert match = "com.htxx.demo.model.Address" javascript = "Address" converter = "hibernate3" >
- < param name="exclude" value = "employee" />
- </ convert>
Value部分是需要转换的属性,如果有多个则用逗号隔开就可以了。 (一个dwr +spring+hibernate的示例 )