DWR帮助说明-如何编写通用的列表显示框

本文探讨了使用DWR实现列表显示功能的方法,并提出了一种通用的代码编写方式以提高代码复用率。通过分析示例代码,介绍了如何利用DWR的setValues()函数和自定义函数来简化列表数据的加载过程。

也许朋友们会以为这是DWR官方发布的什么帮助,但非常遗憾这不是。现在不少朋友在使用DWR开发项目,我也是其中之一,但苦于关于DWR的帮助文档实在太少,很多问题都不得不自己去钻研DWR的源码才能解决或理解。经过一段时间的苦苦钻研,总结出那么一点点心得,现在从DWR源码实现的角度详细讲解DWR的使用,写出来与大家分享。今天我谈一谈如何编写通用的页面端DWR代码。

看了DWR官方发布的示例代码,朋友们可以发现,在该示例代码中,每一个功能都至少有一个jsp文件和一个js文件。也就是说,示例中的每一个功能都需要我们为该功能单独地编写javascript代码,而没有能够有效的代码复用,这是我们不愿意看到的。然而,这仅仅是DWR提供给我们的示例代码,它可以不用过多考虑一些技术细节,但我们使用DWR开发项目则不得不考虑。那么,我们如何在使用DWR的过程中尽可能地代码复用呢?我想,我们先梳理一下页面端的DWR代码如何编写,然后一步一步分析总结这些代码应当如何复用,思路会更加清晰一些。

一、列表显示功能

首先,我们先看一看DWR的列表显示功能。DWR示例中的people目录中展示了一个列表显示功能。总结一下示例中的列表显示代码大致有这几个步骤:

1、调用一个服务器端某个对象中的一个查询方法,如DepartmentBus.findDepartment();这里的DepartmentBus和它的方法findDepartment()对应的是服务器端的一个类及其方法,并已经在dwr.xml中注册了的。在这里调用该方法看起来好像与在java中调用比较相似,但有一个很大的区别就是它的返回值。在java中获得返回值是将某个变量等于该函数:

List rs = DepartmentBus.findDepartment(String hql);

但在js中使用DWR应当这样写:

DepartmentBus.findDepartment(String hql, function(resultset){ … });

在这段代码中,function(resultset){ … }中的resultset变量就是返回值,而这个function中的内容就是对返回值的处理过程。在javascript中,一个function也被当作变量看待,只是一个可以调用的特殊变量。因此你也可以将以上代码写为这样:

DepartmentBus.findDepartment(String hql, getResultset);

function getResultset(resultset){ … };

甚至可以将后面这个函数写成这样:

getResultset = function(resultset){ … };

这样的代码比较奇怪是吧,习惯了就不奇怪了。

2、现在应当来实现getResultset中打省略号的部分,示例中是使用一个DWR的函数removeAllRows来删除table中所有的行,除了那个作为模板的行,其id取名为pattern。这个名为pattern的行十分重要,它被设置为不显示,但表格中的其它行都是由这一行进行克隆得到的。

3、执行一个循环语句,从resultset中依次取出值对象,然后使用DWR的函数cloneNode使用模板行克隆一个新行,然后使用数个DWR的函数setValue将值对象中的属性设置到这个新行的对应列中。当然,这个新行的各个列的id应当设置成属性名加列号。比如在设置第三行时,应当将该行的“部门编号”列的id设置为“departmentId002”,并在向该列赋值的时候写为:setValue(‘departmentId002’, vo.departmentId);其中vo就是这个值对象。另外,在完成对该行的赋值操作以后,为了今后的执行效率考虑,将一个叫objCache的变量作为页面的结果集缓存,将该行的值对象放进这个数组变量中。

经过这样三个步骤,DWR就实现了从后台查询出数据集并写入到页面的表格中。但这样一个过程我们可不可以写一个通用的代码,使得各种不同表的不同数据都可以通过少量程序就可以完成了?但不幸地是,以上的代码不能直接抽象出一个通用代码,我们必须需要进行改造。问题出在第三个步骤的setValue()函数。按照示例的思路,对于Department应当这样写:

setValue(‘departmentId002’, vo.departmentId);

setValue(‘departmentName002’, vo.departmentName);

而对于另一个表格Employee应当这样写:

setValue(‘emplyeeId002’, vo.employeeId);

setValue(‘name002’, vo.name);

setValue(‘sex002’, vo.sex);

也就是说,我们不得不根据值对象的不同分别去写setValue语句而不是一个通用语句。但所幸的是,DWR还提供另一个函数setValues(),你只需要将值对象传给它,它将把id等于属性名的对象设置为该属性的值,比如有个id等于departmentId的text框就会被setValues()设置成显示departmentId对应的值。然而这里使用setValues()有一个问题,值对象中只有departmentId属性,而在表格中我们为了区别不同的行,将每行的“部门编号”标签的id设置为departmentId000、departmentId001、departmentId002……与属性名不一致了。怎么办呢?如果我们把setValues()进行一些改造,使其能够提供后缀的功能就迎刃而解了,即将后面的序号作为后缀,以参数的形式传给setValues(),这样一个通用的列表查询函数就写出来了。具体的做法是我重新实现了DWR的setValues()和_getDataProperties()并提供了3个将结果集填充到表格的函数fillTable()fillResult()fillTableWithCache()fillTable()用于直接将结果集填充到表格中,fillResult()将表格填充的同时还实现了无刷新页面翻页的功能,而fillTableWithCache()则用于当数据更新时不查询后台而直接通过页面的结果集缓存变量刷新数据。具体的实现见我提供的示例。

二、前缀

前面我提到了后缀的功能,即页面在列表显示的时候,为了使表格中的数据既可以与值对象的属性对应,又可以使各列之间的id名称有所区别,在将标签的id名称设置为“属性名+行号”,这个行号就是后缀。然而DWR还提供了前缀的功能,这又是什么意思呢?

DWR一个非常重要的特点是将页面的id与后台值对象的属性名对应起来。通过这样,过去我们在MVC需要做的非常烦杂的,将页面的form中一个一个取出数据,再一个一个放到值对象的工作,变成了一个简单的setValues()的操作。但是这样却带来了另一个问题:同一个表的数据在一个页面中需要在多个部分显示,就会出现多个标签使用同一个id形成冲突的问题。比如,一个非常普遍的应用,在一个页面中同时包含一个列表显示框和一个单行编辑框。当用户选择列表显示框中的某行并点击“编辑”按钮,该行的信息将显示在单行编辑框中进行编辑,编辑结束并保存后将更新该行到列表显示框中。在列表显示框中如果显示的是所有部门的信息时,虽然各行标签的id取名为“属性名+行号”,但列表框中那个用于克隆的模板行却是取名为值对象的属性名,如departmentId、departmentName等。而在该页面中的单行编辑框中显示的也是部门信息,因此取名也是部门值对象的属性名。这样,在这个页面中将有两个id为departmentId的标签,两个id为departmentName的标签,id出现了冲突。众所周知,在一个页面中id应当是唯一的,如果出现id冲突将出现错误。解决这个问题的办法当然就是使用前缀。

如果在设计页面的时候出现id冲突,我们可以在id前添加一个前缀,如在departmentId前添加一个tbody,写成tbody. departmentId,而另一个添加另外的前缀,id冲突的问题就解决了。对于添加了前缀的标签,在调用setValues()和getValues()函数的时候应当将前缀作为参数传递过去,写法如下:

setValues(department, { prefix:tbodyid, postfix:id });

后面的后缀就是我重写setValues后提供的功能。

2.11用AJAX获取数据(通用做法) 承保子系统的投保单录入页面的“条款信息”TAB页的保单条款部分的条款内容是用AJAX实现了从后台异步获取数据。 本节以“查看条款内容”为例讲述在系统用AJAX从后台异步获取数据的通用做法。 2.11.1“查看条款内容”的操作情景 操作情景是:用户输入条款编码后点击条款内容按钮,此时由页面向后台发送一个查询请求,根据用户输入的条款编码查询出条款内容,并显示在条款内容的<span>区域内。在从页面发送请求到从后台返回查询结果的过程中,页面右上角将显示红色背景的LOADING信息用于提示用户此时前台和后台正在交互。 由于查看条款内容这个操作页面不能提交表单、不能有刷新、并且所有的条款内容又不能事先都查出来,那样效率太低,只能是选择一个条款后再查询它的内容,所以在这些提前下用AJAX技术实现前台和后台的交互是比较合理的。 2.11.2前台JS函数调用后台JAVA类 简单的讲,我们的系统是通过前台JS函数与后台JAVA类之间实现交互的。下面讲解查看条款内容这个例子的实现方法。 首先打开\webapps\prpall\common\clauses\EditClauses.jsp文件,这就是条款信息TAB页的JSP文件。在“条款内容”按钮的onclick事件调用了openPolicyClausesContext()函数,见下图: 我们在调用这个函数时传入了this,表示把当前这个按钮对象作为入参传入方法中。我们来看一下openPolicyClausesContext()函数,这个函数就在这个JSP页面的下方: 在多行输入域模式下,每行的同一列的元素都是同名的,所以我们首先要知道我点击的是哪行的按钮,之后也就可以得到这行的条款代码,并且还要把查询出的条款内容写在这行的条款内容<span>中。函数首先通过调用getElementOrder()函数得到某个页面对象在整个页面的同名对象中是第几个。我们把按钮对象作为入参传入getElementOrder()方法,函数返回的是“单击的是第几个条款内容按钮”,同时也就知道了当前操作的是第几行。有一点要说明,getElementOrder()方法的返回值是从1开始编排的,而数组的下标是从0开始的,所以得到返回值后还要“减1”。 我们通过order变量,可以得到这行的条款代码,我们的条款代码输入域的name是GuClausesClauseCode,我们把“fm.GuClausesClauseCode[]”和order变量作为入参传进getElementObjectByOrder()方法可以得到这行的条款代码输入域对象。fm是当前这个表单的名字,“fm.GuClausesClauseCode[]”这个拼写格式是固定的,正如图中所示。我们得到inputObject对象后,通过调用inputObject.value可以得到这行输入的条款代码。按照同样的方法,我们可以得到<span>中存放条款内容的<TEXTAREA>对象。 接下来我们要判断如果用户输入了条款代码并且此时条款内容<TEXTAREA>中还没有数据,那么我们就调用dwrInvokeData函数实现前台JS与后台JAVA之间的交互。dwrInvokeData()、getElementObjectByOrder()、getElementOrder()函数都是系统提供的公共的JS函数,已经在\webapps\common\StaticJavascript.jsp文件中引入了,所以我们在函数中直接调用即可。 dwrInvokeData()函数接受5个入参:“"openPolicyClausesContext"”—将要调用的后台JAVA类的方法名;clauseCode—这个方法的入参,我们要求这些后台的JAVA方法的入参只能有一个,但不必须是字符串,也可以是一个数组,常用的JS变量就是单值的变量或者是数组,这个入参也可以是我们自定义的JS对象,但这个对象必须与后台某个DTO对象对应;“"openPolicyClausesContextCallback"”—是当dwrInvokeData()函数运行后回调的页面上的JS函数,这个JS函数是要我们自己写的;inputObject—是传入参数的输入域对象,实际上我们可以在这里传入任何一个输入域对象,或者是一组对象的数组;outputObject—用于显示后台查询结果的输入域对象,可以是一个单一的对象也可以是一组对象的数组。 通过上面的讲述可以看出,我们对第二、四、五个参数几乎没有限制,它们都有很大的利用空间。调用dwrInvokeData()函数之后,请求就被发送到后台,下面讲述后台JAVA类的编写方法。 对于用AJAX从后台获取数据的方法,我们都写在平台子系统的DwrInvokeDataAction类中,它在com.chinainsurance.application.platform.web.action包下。它用于接收从前台JS函数发来的请求,在这个例子中我们传给dwrInvokeData()函数的第一个参数是openPolicyClausesContext,表明在后台的DwrInvokeDataAction类中要有一个同名方法与之对应。如下图所示: 上面这个例子中函数的入参是String类型,这是因为我们在前台JS中定义的入参是“var clauseCode = “”;”并且我们也已经知道clauseCode这个参数肯定是一个字符串,所以我们在后台JAVA方法的入参也对应的指定为String类型。 如果我们可以明确的知道这个前台的JS参数肯定是一个数字,那我们在后台的JAVA方法的入参可以直接定义为Integer、Double或Long类型的,系统会自动把参数从JS变量转换为JAVA对象传进方法。由于在JS语法中所有数据类型的变量都可以用“var”来定义,所以在这里我们必须要确保在JS中指定的入参(例子中是var clauseCode)的数据内容一定能转换为JAVA方法的入参(例子中是String clauseCode),否则JS函数会弹出“[Object Error]”错误。 如果我们在前台JS中定义的参数是一个数组,像下面这样: 为了演示功能,我们特意定义了一个inputArgs数组并且把clauseCode作为它的第一个元素。我们再来看一下这样定义JS参数之后,对应的后台类该怎样写: 对于JS数组inputArgs我们已经知道它存放的元素都是字符串,所以在后台类的方法中我们可以明确定义入参就是String[]类型,系统会自动把JS数组转换为JAVA对象字符串数组。同理,如果我们能事先明确JS数组存放的都是数字,那我们也可以把JAVA方法的入参类型指定为Integer[]、Double[]或Long[]。 以上我们的入参都是JDK标准的对象类型,除此之外我们也可以使用自定义的对象。我们再来修改一下JS函数,像下面这样: 我们在第一个红内自定义了JS对象类型GgClauseTextDto并且定义了两个属性clauseCode和lineNo;在第二个红内我们创建了这个对象类型的一个实例并且把clauseCode值赋给了这个对象的clauseCode属性;在第三个红内我们把这个对象作为参数传递到后台。下面接着看后台的JAVA类: 后台JAVA方法入参我们指定的是一个DTO,由于我们自定义的JS对象的属性与后台JAVA对象GgClauseTextDto的属性是一致的,所以系统可以自动把JS对象转换为JAVA对象。这里需要说明:1,JS对象不用把后台DTO的所有属性都定义进去,用到哪个属性就定义哪个属性就行了,像我们的例子中只定义了clauseCode和lineNo,实际上我们的lineNo属性没有用到,在这里不定义也是可以的;2,JS对象的属性名必须与后台DTO的属性名一致,否则系统无法自动把JS对象的属性值复制到JAVA对象中。3,JS对象的类型名称不必须要与DTO类名相同,但我们建议定义为一样的,这样方便前后台对应维护。 还有一种更简便的写法,就是在JS对象中连属性都不用定义,在用到这个对象时需要哪个属性就直接为这个属性赋值就可以了。像下面这样: 我们在第一个红内已经不定义属性了;在第二个红内依然跟前面的写法一样,直接调用ggClauseTextDto.clauseCode为其clauseCode属性赋值。 我们也可以把GgClauseTextDto对象放到JS数组中传到后台,像下面这样: 对应的后台类是: 我们把JAVA方法的入参指定为GgClauseTextDto数组,是因为我们已经事先知道JS数组中存放的都是GgClauseTextDto对象。如果我们在JS数组中放入了各种各样的对象,那我们后台的JAVA方法的入参要指定为Object[],然后在JAVA方法中再使用强制类型转换。 从上面的例子可以看出,JS方法入参和JAVA方法入参的对应关系是很灵活的,虽然我们的入参只有一个,但这一个参数有很大的利用空间。 在DwrInvokeDataAction的方法中得到入参后,调用Service查询数据的方法跟普通的Action类似,没有什么特别的。 下面讲解后台的返回值以及前台的回调函数。 2.11.3后台JAVA类为前台JS函数返回结果 回忆一下我们调用dwrInvokeData()函数时的入参: 红内的参数是后台执行完查询后要调用的前台的JS函数,这个函数是要开发人员自己编写的,像下面这样: 这个方法的入参是固定的三个:第1个和第2个跟调用dwrInvokeData()方法时传入的inputObject和outputObject一样,我们只是简单的把传给dwrInvokeData()方法的inputObject和outputObject对象又传给了我们自己写的这个回调函数;第3个参数是后台JAVA方法返回给JS函数的数据。 我们上面给出的JAVA方法的返回值就是一个String数据存储着条款内容,所以系统会自动把JAVA字符串转换为JS字符串,所以我们就直接把returnObject赋给条款内容输入域就可以了。我们系统可以自动转换的JAVA对象包括:String、Integer、Double、Long,还包括所有的DTO对象。例如如果后台返回的是一个DTO,像下面这样: 为了演示功能,我们特意把条款内容查询出来之后放进了一个DTO的context属性。返回到前台后我们看一下回调函数该怎样写: 后台JAVA对象返回到JS函数时系统已经把它转换为了JS对象,所以我们在回调函数中直接调用returnObject的属性就可以了,不用再写任何关于类型属性定义或转换的语句。调用returnObject的属性时要注意属性名与后台返回的DTO的属性名是一致的。 从后台也可以返回一个存储DTO对象的java.util.List,在前台JS中可以这样获取List中的每个对象: 如果从后台返回的对象是java.util.List,在调用前台的JS回调函数时系统已经把java.util.List对象转换为JS数组。我们可以像操作普通的JS数组一样来操作returnObject。如果后台返回的是某个对象的数组,那么前台JS的操作方式与上面一样,只是通常情况下我们从后台都不返回数组对象,要么返回的是单个的DTO对象,要么返回的是List对象,或者返回的是JDK标准的对象类型。 2.11.4dwr的同步执行问题 Application.js文件里的function dwrInvokeData()函数,增加了一个入参async,用于指定这个函数在本次执行时是否是异步的。这个入参是boolean型变量true/false。如果传入true,表示是异步的,如果传入false,表示是同步的;如果不传,默认是true。 以前dwrInvokeData函数有6个入参,但我们通常调用它时只传入5个,例如:dwrInvokeData(p1, p2, p3, p4, p5)。现在入参是7个了,调用时一定别忘记把原来第6个入参的位置传入null,否则参数就错位了。例如:dwrInvokeData(p1, p2, p3, p4, p5, null, false)。 原先第6个入参是自定义的提示信息,我们现在dwr在工作时页面右上角显示Loading,这个信息是可以通过第6个入参来自定义的。但我们通常不需要这样做。所以第6个入参传入null即可。 2.11.5总结 我们用AJAX从后台获取数据时要编写的代码是: 1.编写一个JS调用函数,例如openPolicyClausesContext()函数,用于组织参数并通过它来调用dwrInvokeData()函数; 2.在DwrInvokeDataAction类中写对应的方法,用于查询数据; 3.编写一个JS回调函数,用于把从后台返回的数据按照自己的规则显示到页面上。 简单的讲,我们就是通过JS调用JAVA来实现获取数据的。在获取数据的过程中,页面右上角会显示LOADING信息,在这期间不适合做页面校验、不适合提交表单,在这期间请耐心等待,但用AJAX异步获取数据的速度是很快的,并不会等待太长时间。 对于JS入参是DTO的和后台返回DTO对象的情况要注意,JS不能读取DTO对象中嵌套的DTO对象的属性。我们的系统只能把DTO对象中的JDK标准数据类型的属性在JS和JAVA之间互相转换。JS中调用的DTO的属性名必须和JAVA的DTO对象的属性名一致。 怎么快速理解以上的开发文档知识
10-28
内容概要:本文详细介绍了“秒杀商城”微服务架构的设计与实战全过程,涵盖系统从需求分析、服务拆分、技术选型到核心功能开发、分布式事务处理、容器化部署及监控链路追踪的完整流程。重点解决了高并发场景下的超卖问题,采用Redis预减库存、消息队列削峰、数据库乐观锁等手段保障数据一致性,并通过Nacos实现服务注册发现与配置管理,利用Seata处理跨服务分布式事务,结合RabbitMQ实现异步下单,提升系统吞吐能力。同时,项目支持Docker Compose快速部署和Kubernetes生产级编排,集成Sleuth+Zipkin链路追踪与Prometheus+Grafana监控体系,构建可观测性强的微服务系统。; 适合人群:具备Java基础和Spring Boot开发经验,熟悉微服务基本概念的中高级研发人员,尤其是希望深入理解高并发系统设计、分布式事务、服务治理等核心技术的开发者;适合工作2-5年、有志于转型微服务或提升架构能力的工程师; 使用场景及目标:①学习如何基于Spring Cloud Alibaba构建完整的微服务项目;②掌握秒杀场景下高并发、超卖控制、异步化、削峰填谷等关键技术方案;③实践分布式事务(Seata)、服务熔断降级、链路追踪、统一配置中心等企业级中间件的应用;④完成从本地开发到容器化部署的全流程落地; 阅读建议:建议按照文档提供的七个阶段循序渐进地动手实践,重点关注秒杀流程设计、服务间通信机制、分布式事务实现和系统性能优化部分,结合代码调试与监控工具深入理解各组件协作原理,真正掌握高并发微服务系统的构建能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值