J2EE表单验证的前后台统一实现
Wayne Huang
2011年11月
1 背景介绍
在很多基于web的应用中,我们常常会面临一个问题,那就是表单验证。表单验证的重要性我想就不必要再反复的说了,总之就是很重要。通常情况下,对于用户从客户端递交到表单的数据我们所作的验证主要集中在数据的完整性和正确性检查上,当然其中还可能涉及一些比较怪异的数据间的逻辑关系。通常情况下基于表单的验证主要有三种情况。偷懒的,就是在web页面上做一套基于js 的表单验证,然后验证好之后就扔到后台了。这种验证机制,对于大多数"老实的"用户来说,的确是可行的手段,但是如果对于不怎么"老实"的用户呢?或者用户浏览器的js 被禁用的情况,我们的验证机制就失效了。于是,有些人就只在服务器端实现表单的验证,但是那样进行表单验证对服务器的压力太大,表单可能会多次被递交,而实际有意义的表单递交却只有一次。于是,为了达到安全性和减轻服务器压力这两个特性,我们就不得不把这两种方式结合起来用。但是,众所周知,客户端用的脚本是js ,而服务器端在J2EE下是用Java的。虽然名字差不多,但是实际运用起来差别还是很大的。于是为了实现客户端和服务器端的表单验证,开发人员不得不用两种语言实现两套验证。暂且不说工作量,如果表单验证的规则变化一下,开发人员就不得不修改两次验证的代码。如上所说,比较好的方法是在客户端和服务器端各进行一次表单验证,但是这样做的问题是,两处需要用不同的语言实现两次。于是乎,就有了类似struts2 这样的基于xml规则文件的表单验证机制。我想对于众多struts2 的fans 来说,我要说这个机制不好的话,估计要被烂番茄,臭鸡蛋砸成半残的,所以我就不多做评价了。但这种机制的问题是,需要有学习成本,而且对于数据间复杂的逻辑关系,这种xml规则文件的实现并不是太灵活。所以,我就来介绍一个比较轻量级的另类实现方式。2 实现分析
话说,拿破仑说过:"不想当将军的士兵不是好士兵,不想偷懒的程序员不是好程序员。"好吧,后面那半句是我加上去的。所以,我们的实现必须且只能让验证的核心部分实现一次。同时,对于现在生活压力那么大的当今,为了偷个懒就花很多精力去学一个东西实在有些不合时宜,所以,我们的实现最好能够是零学习成本的。毕竟程序廉价生命无价啊。而且,最好还有一定的灵活性和扩展性。当然,如果能有单元测试的能力,易于使用的话就更好了。2.1 前期分析
那么,愿已经许好了,接下来我们看看怎么去实现这个愿望。毕竟,我们都不是阿拉丁。想一下,到底表单验证分为几个步骤呢?我们可以归结为如下3个-
数据收集
- 从html表单或者从request里面将表单的数据提取出来。 数据校验
- 将表单数据按照一定的规则进行校验并指出数据的错误。 错误反馈
- 将表单校验的错误信息反馈给客户端,并显示出来。
2.2 实现思路
既然我们已经找到了突破口,那我们如何实现呢?这个思路和struts2 的实现思路很像,只是有些区别。首先,我们要统一"数据收集"和"错误反馈"的接口。为了方便直观,我们就用Map 类型来存储吧,Map 的key 就是表单数据的name ,value 的话,就用一个String 的数据来存吧。毕竟,一个表单里可能会存在一个或多个同名的数据。而反馈的错误信息也是类似,只是value 部分换成了该数据错误信息的数组。因为一个数据,可能会存在多个错误。那么,数据校验部分,如果用一个函数表示的话,可以抽象成如下的结构定义。接下来,我们考虑一下怎么实现一次编码。话说,对于一个Web应用的开发者来说,会写javascript 应该是很基本的了。javascript 的话,浏览器端可以实现,服务器端的话实现应该也是不难的,毕竟java6 已经包含了javascript 的执行引擎了。所以,我们就用javascript 来实现表单验证的核心功能吧。或许有些人看到这里就会嚷嚷了,有些服务器是用java5的怎么办?没关系,其实java6 所包含的执行引擎是mozilla 实现的java 版,那个本身就可以在java5 下使用的。于是乎,我们的实现思路就很明朗了。在客户端和服务器端分别实现表单数据的收集和反馈功能,这个不同的项目基本只要实现一次就OK了。然后,用javascript 写的校验脚本,去校验表单的数据。这样做的话,校验代码只要实现一次即可。浏览器端原本的校验就是用javascript 实现的,而服务器端也有javascript 的执行引擎可以执行js 的校验。这样,虽然校验脚本是一个,但是两次校验是互相独立的。Map<String, String[]> validate(Map<String, String[]> params);
3 框架实现与使用
到了这里,是不是各位看官有些跃跃欲试了呢?是不是开始期待接下来的内容呢?如果是连续剧,一般这个时候会出现一个短暂的暂停镜头,然后就是一段让人熟悉的片尾曲。但我知道,如果我也那么做的话,估计要被掀桌子的。所以,考虑到桌子是无辜的,我们还是继续吧。3.1 再造轮子vs拿来主义
至于代码重用和再造轮子这种讨论,我想软件工程已经讨论的够多的了,所以我不想再多做阐述了。而至于框架的实现部分,其实要在一篇文章里说清楚还不如直接看源码。所以,想到了鲁迅先生所说的"拿来主义"。毕竟,我们的出发点是为了将偷懒进行到底,那么,没有道理说,有现成的实现我们不用,不是么?在google code 上有一个工程叫做mustardseed,里面包含了许多常用的轻量级通用模块,目前还在发展当中,其中有一个模块叫做Validation,就是我们所需要的实现。在上面有个叫ValidDemo.7z 的文件,就是Validation 在Struts2 下使用的Demo,有兴趣的可以先体验一下。工程的地址如下所示。除了Validation 还有其他一些模块,在平时的工程中也有所帮助。
http://code.google.com/p/mustardseed
3.2 框架的使用
话说,Validation 使用基本都是通过Spring 来配置的。现在这年头,你做Java 的Webapp你可以不用struts2 你可以不用hibernate,但是Spring 好像还是很必要的。所以,我想用Spring 来配置的东西估计问题也不会很大,mustardseed 的模块可以用于Struts2 和Spring MVC 上面,因此不论你是Struts2 的fans 还是SpringMVC的fans,你们都可以用mustardseed 的模块,而且配置几乎是一样的,这么看来,这真是和谐世界啊。Validation 在服务器上只要配置两处。一个是名为ValidationHandler 的拦截器,这个拦截器在Struts2 和SpringMVC 下有两个实现,具体的你选择一下就是了。还有一个就是名为为Validator 的验证用的Bean。 而在客户端这里,Validation 有一个名为 validation.js 的脚本,这是一个jquery 的插件,在你要有表单验证的地方导入那个js 就可以用了。不过前提是你要有jquery 的库,当然如果你厌恶jquery 那也可以按照样子自己实现一个。更多具体的细节,在mustardseed 里有些文档以及Demo 可以参考。鉴于现在国内Struts2 的Fans 队伍比较壮大,我这里就用Struts2 做个例子。3.2.1 SpringBean 的配置
上面是Spring 配置文件中你所要加的内容。其中 validator.js 就是你所要实现的校验逻辑的核心部分。注意,这个路径是你当前应用的相对路径。<bean name="validator" class="org.mustardseed.validation.Validator"> <property name="scriptPath" value="/script/validator.js"/> </bean>
3.2.2 validator.js 的样子
上面是校验框架的核心逻辑部分。其中addErrorField 是一个为了方便而自己添加的函数,其中的registerValid 才是我们用来校验的函数。只要保证你的校验函数传入和返回的参数和这个函数一样就没问题了。另外,这个脚本有些小限制,不能在其中调用浏览器相关的函数以及对象。不过,表单验证之涉及数据本身,所以这点限制可以无视掉的。function addErrorField(obj, name, msg) { if (!obj[name]) obj[name] = new Array(); obj[name].push(msg); } function registerValid(data) { var ret = {}; if (data["name"][0] == "") { addErrorField(ret, "name", "用户名不能为空"); } else if (data["name"][0].length < 3 || data["name"][0].length > 20) { addErrorField(ret, "name", "用户名限制在3到20个字符"); } if (data["password"][0] == "" || data["password"][1] == "") { addErrorField(ret, "password", "密码均不能空"); } else if (data["password"][0] != data["password"][1]) { addErrorField(ret, "password", "两个密码必须相同"); } else if (data["password"][0].length > 8) { addErrorField(ret, "password", "密码必须在8个字符以内"); } return ret; }
3.2.3 加个拦截器拦截一下下
在你的Struts2 的配置文件中加上那个拦截器配置。你可以考虑把这个拦截器设为全局的,并不会影响你正常的action 调用。当然啦,如果你用MVC,也就是这里的配置不同罢了,具体的可以参考mustardseed 下的MVCDemo。<interceptor name="validationInterceptor" class="org.mustardseed.validation.struts.ValidationHandler"/>
3.2.4 前台页面的使用
上面就是用jquery 和Validation 的jquery 插件实现的表单验证代码。其中验证的部分就是formValid 然后传入你要用来校验的函数。这样就完成了前台的校验了。如果返回一个非空值,那就说明校验发生错误了,接下来是把错误信息显示到页面上。当然你也可以不显示,或者用其他任何你喜欢的方式处理这些错误信息。$("#register").submit(function(e) { var err = $("#register").formValid(registerValid); if (err) { var msg = "" for (var i in err) { for (var j = 0; j < err[i].length; j += 1) msg += err[i][j] + "<br/>"; } $("#valid_result").html(msg); return false; } return true; });
3.2.5 后台校验的使用
对于Struts2 后台的使用更是简单。直接在Action 的用来处理表单的成员函数上,加如下的一个注解就可以了。后面传入的参数就是你用来进行校验的函数名。如果表单校验出错了会按照struts2 默认的情况跳转到input 的视图上的,这点对于Struts2 的Fans 来说并不算太陌生,也不太难理解。//org.mustardseed.validation.FormValidResult; @FormValidResult("registerValid")