Struts输入校验
1什么是输入校验
输入校验,最常见的是对用户名和密码的输入错误的校验,实际上这个后台业务逻辑的判断而不是输入校验。输入校验是对像用户名长度,密码长度,限定的年龄值等这些信息进行判断。
2为什么要进行输入校验
输入校验是为了防止用户将错误的数据提交给服务端进行处理。通过输入校验,希望用户提交的是符合要求的数据。很多时候这种符合要求的数据是不符合用户平时使用的习惯的,或者有时候网站希望用户提交某些特殊形式的数据,这时候就必须使用输入校验来控制用户提交的数据符合要求。
3 输入校验的方式
输入校验的方式的方式有客户端校验和服务器端校验。可能有人要问,既然有了客户端校验来校验输入,为什么还需要服务器端的再次校验。实际上,在客户端我们常用的用javascript进行的校验是很容易被恶意的用户绕过去的。比如下载到网页后删掉javascript再提交数据,此时如果没有服务器端的校验,错误的数据就提交到了服务器。这样的话,那有必要再客户端进行校验码,试想一下,如果没有客户端校验,客户每提交一次数据,服务器端就立即进行校验,如果数据有错误返回给用户重写。这样不仅加重了服务器端的负担。用户在输入数据时校验也会非常的麻烦,最后可能是花长时间反复的校验后还提交不了符合要求的数据,可想而知这样的用户体验糟糕透了。所以说客户端校验与服务器端校验二者是相辅相成的。
4 下面用例子来展示如何运用struts2进行输入校验。
注册页面:Register.Jsp
<body>
<s:actionerror/> //打印输入校验错误信息
<form action="register.action" method="post" id="form" οnsubmit="validate()">
<!-- value="${requestScope.username 输入校验后保留用户输入的值 -->
uesrname:<input type="text" name="username" value="${requestScope.username }" /><br/>
password:<input type="password" name="password" value="${requestScope.password }"/><br/>
repassword:<input type="password" name="repassword" value="${ requestScope.repassword}"/><br/>
age:<input type="text" name="age" value="${requestScope.age }"/><br/>
birthday:<input type="text" name="birthday" value="${ requestScope.birthday}"/><br/>
email address:<input type="text" name="email" value="${ requestScope.email}"/><br/>
salary:<input type="text" name="salary" value="${ requestScope.salary}"/><br/>
url:<input type="text" name="url" value="${ requestScope.url}"/><br/>
regStr:<input type="text" name="regStr" value="${ requestScope.regStr}"/><br/>
<input type="submit" value="提交"><br/>
</form>
</body>
以下是客户端校验的代码,使用了正则表达式不懂的读者请自行查阅相关资料。
<!-- 客户端校验 -->
<script type="text/javascript">
//使用此函数截断字符串前后的空格
function trim(str){
return str.replace(/^\s*/,"").replace(/\s*$/,"");
}
function validate(){
//定义错误字符串
var errors = "";
//取出表单中的值并使用trim截断前后空格
var username = trim(document.getElementById("form").username.value);
var password = trim(document.getElementById("form").password.value);
var repassword = trim(document.getElementById("form").repassword.value);
var age = trim(document.getElementById("form").age.value);
var birthday = trim(document.getElementById("form").birthday.value);
var email = trim(document.getElementById("form").email.value);
//判断用户名是否输入,如果输入了再判断格式是否正确
if(username == "" || username == null){
errors += "用户名必须输入";
}else if(! /^\w{6,20}$/.test(username)) {
errors += "\n用户名必须是字母和数字,长度为6到20之间"
}
//判断密码是否输入,如果输入了再判断格式是否正确
if(password == "" || password == null){
errors += "\n密码必须输入";
}else if(! /^\w{6,20}$/.test(password)) {
errors += "\n密码必须是字母和数字,长度为6到20之间"
}
//判断确认密码是否输入,如果输入了再判断格式是否正确
if(repassword == "" || repassword == null){
errors += "\n确认密码必须输入";
}else if(! /^\w{6,20}$/.test(repassword)) {
errors += "\n确认密码必须是字母和数字,长度为6到20之间"
}
//判断确认密码和密码是否相同
if(repassword != password){
errors += "\n确认密码和密码必须相同";
}
//年龄不是必须字段,只需要判断格式是否正确
if(age != "" && !/^[0-1]?[0-9]?[0-9]$/.test(age)){
errors += "\n请输入正确的年龄格式"
}
//出生日期不是必须字段,只需要判断格式是否正确
if( birth != "" && !/^19\d\d\-[0-1]\d\-[0-3]\d$/.test(birth) &&
!/^20[0-1]\d\-[0-1]\d\-[0-3]\d$/.test(birth)){
errors += "\n请输入正确的生日格式(1988-03-31)"
}
//邮箱地址不是必须字段,只需要判断格式是否正确
if(email != "" && !/^[a-zA-Z][a-zA-Z0-9._-]*@([a-zA-Z0-9-_]+.)+(com|gov|net|com.cn|edu.cn)$/.test(email)){
errors += "\n请输入正确的邮箱地址"
}
if(errors == ""){
return true;
}else{
alert(errors);
return false;
}
}
</script>
重点看服务器端的输入校验:RegisterAction中的validate方法自定义输入校验
@Override
publicvoid validate() {
//判断用户名是否输入,如果输入了再判断格式是否正确
if(username == null || "".equals(username)){
this.addActionError("自定义:用户名必须输入");
this.addFieldError("username", "用户名必须输入");
} elseif ( !Pattern.matches("\\w{6,20}", username.trim())) {
this.addActionError("用户名是字母和数字,长度在6到20之间");
this.addFieldError("username", "用户名是数字和字母在6到20之间的长度");
}
//判断密码是否输入,如果输入了再判断格式是否正确
if( password == null || "".equals(password)){
this.addActionError("密码必须输入") ;
this.addFieldError("password", "密码不能为空");
}elseif( !Pattern.matches("\\w{6,20}", password.trim())) {
this.addActionError("密码必须是字母和数字,长度为6到20之间");
this.addFieldError("password", "密码必须是字母和数字长度6到20");
}
//判断确认密码是否输入,如果输入了再判断格式是否正确
if(repassword == null || "".equals(repassword)){
this.addActionError("确认密码必须输入") ;
this.addFieldError("repassword", "确认密码不能为空");
}elseif( !Pattern.matches("\\w{6,20}", repassword.trim())) {
this.addActionError("确认密码必须是字母和数字,长度为6到20之间");
this.addFieldError("repassword", "确认密码必须是字母和数字,长度为6到20之间");
}
//判断确认密码和密码是否相同
if(password != null && repassword != null && ! repassword.equals(password)){
this.addActionError("确认密码和密码必须相同");
this.addFieldError("repassword", "确认密码和密码必须相同");
}
//判断年龄是否合法
if(age < 0 || age >130) {
this.addActionError("请输入有效的年龄");
this.addFieldError("age", "请输入有效的年龄");
}
//判断出生日期是否合法
Calendar start = Calendar.getInstance();
Calendar end = Calendar.getInstance();
start.set(1900, 1,1);
end.set(2010, 1,1);
if(birthday != null && ( birthday.after(end.getTime()) || birthday.before(start.getTime()))) {
this.addActionError("请输入有效的出生日期");
this.addFieldError("birthday", "请输入有效的出生日期");
}
//判断邮箱地址是否合法
if(email != null && !"".equals(email) && email != "" && !Pattern.matches("[a-zA-Z][a-zA-Z0-9._-]*@([a-zA-Z0-9-_]+\\.)+(com|gov|net|com\\.cn|edu\\.cn)", email)){
this.addActionError("请输入正确的邮箱地址");
this.addFieldError("email", "请输入正确的邮箱地址");
}
System.out.println("validate invoke");
}
在strut.xml中配置action,注册页面,输入校验后结果如下:
注意观察注意Register.jsp运行后打印出了放在actionError中的错误信息。FildError中的错误信息没有打印。
修改Register.jsp如下:
<body>
<!-- 显示actionerror:服务端校验错误
<s:actionerror/>
-->
<s:form action="register2">
<s:textfield name="username" label="username"></s:textfield><br/>
<s:password name="password" label="password"></s:password><br/>
<s:password name="repassword" label="repassword"></s:password><br/>
<s:textfield name="age" label="age"></s:textfield><br/>
<s:textfield name="birthday" label="birthday"></s:textfield><br/>
<s:textfield name="email" label="email"></s:textfield><br/>
<s:textfield name="salary" label="salary"></s:textfield><br/>
<s:textfield name="url" label="url"></s:textfield><br/>
<s:textfield name="regStr" label="regStr"></s:textfield><br/>
<s:submit value="提交"></s:submit>
</s:form>
再次运行结果如下:
这里用了strus2 的标签库,就打印出了输入校验的错误的信息,还打印了类型转换错误的信息“birthday类型转换错误”(这里自定义了类型转换错误信息,如何自定义请见下面的讲解。如果你没有这样写,错误信息会是invalid birthday …类似的形式),所以用struts的标签库给我们提供了很多方便的功能。学习struts2标签库请见我的文章struts2标签库详解。
注意:
4.1 addActionError与addFieldError
• 在前面介绍了使用actionError来保存输入校验错误提示信息。actionError其实就是一个ArrayList,将错误信息保存在actionError中,其实就是保存在一个ArrayList中。前面曾讲过类型转换的错误信息是保存在fieldError中,同样输入校验的错误信息也可以通过addFieldError方法来保存到fieldError中。fieldError和actionError不同的是,fieldError是采用Map结构来存储的,所以都是以键值对来保存信息。
• 那到底是使用fieldError来保存错误提示信息还是使用actionError好呢?这个就依据项目具体要求而定了,如果只是希望在页面中单纯的显示错误提示信息,可以使用actionError来保存错误提示信息;如果希望在相应的文本框中显示错误提示信息,则需要使用fieldError来保存错误提示信息。
• 下面来看如何将错误提示信息保存到fieldError中。首先可以使用addFieldError方法来替代addActionError方法,从而将错误提示信息保存到fieldError中。其中addFieldError方法中包含两个参数,第一个参数用来输入参数名(也可以说是Action中属性名或者说是表单元素中的name属性值),第二个参数用来输入校验错误提示信息。
本例中通过在validate方法中使用addFieldError来添加了错误信息。如果你没有这样做,将没有错误信息显示字输入页面上。
4.2 输入校验与类型转换的关系
观察运行结果页面知道,打印了两类错误信息,一类是输入校验错误信息,如:“用户名是数字和字母在6到20之间的长度”,“密码不能为空”等等,一类是类型转换错误信息,如“birthday类型转换错误”。这两者是何关系了。
》用户提交数据后,首先会调用类型转换器来进行类型转换。如果转换失败,则会将错误信息保存到fieldError中,然后不管类型转换成功失败与否,都会进行输入校验,如果校验错误的话则将错误信息保存到fieldError或actionError中。最后系统判断actonError或fieldError中是否存在错误信息,如果存在,页面将跳转到啊input对应的视图资源处将错误信息打印处理。所以最后会显示出两类错误信息。
如何处理好类型转换的错误信息和输入校验的错误信息呢?记住一点:类型装转换对数据的格式进行校验,输入校验对数据的取值进行校验。
4.3 自定义类型转换错误信息
》定义局部类型转换错误信息
在对应的Action目录下定义ActonName.propertier资源文件,与定义局部类型转换器非常相似。资源文件内容如下
invalid.fieldvalue.属性名=自定义类型转换错误信息
》定义局部类型转换错误信息的中英文资源文件
分别在对应的Action目录下添加ActionName_en_US.properties和ActionName_zh_CN.properties资源文件,内容如下
invalid.fieldvalue.属性名=自定义中(英)文的类型转换错误信息
5 对自定义action处理方法的校验
Action默认的处理方法为execute。默认的校验方法为validate方法
》这里我们自定义处理方法myExecute,在action中指定method属性为myExecute,如何校验这个自定的这个处理方法了,只需要定义validateMyExecute方法即可,在validateMyExecute,Myexecute,validate,execute方法中分别只简单大打印一句话。运行观察控制台输出如下:
如果在action中定义validateExecute方法呢,method属性为默认值,运行:
可见,不管是对execute还是自定义的action处理方法。validate方法总会执行,自定的输入校验方法优先执行,执行顺序如图。这样做有什么意义了。
》自定义校验方法对自定义的处理逻辑方法进行校验。且validate的校验总会执行。
》struts2 允许在包含多个处理器(execute方法和自定义的execute方法),对多个处理器可分别定义其校验方法。只有当多个Action中接受的用户参数相同时,才可以考虑将多个action放到一个action中。例如在同一个表单中,为同一批请求数据,再action中可以调用不同的方法来处理。
》可以将处理逻辑中相同的校验代码放在validate方法中。不同的校验代码则放到对应的validate……()方法中。
6 struts2输入校验流程
》用户提交请求数据,客户端校验,提交数据到服务器端
》类型转换器负责将用户提交的数据进行类型转换,并将类型转换的值赋值到action中的相应属性长,如果有错误struts2将自动调用conversionError拦截器将类型转换错误信息保存到fieldError中
》不管类型转换失败与否都会执行validate……(),validate()校验方法。如果有错误则将错误信息保存到actionError或fieldError中。
》自动判断actionError或fieldError是否为空。如果不为空,则表示有错误提示信息,自动跳转到input对应的逻辑视图资源。否则执行action中的处理逻辑方法:execute或自定义的execute方法。处理完成后根据结果处理值跳转到对应的逻辑视图资源。