第6部分
本文我们来讨论一下Struts中的输入校验问题。我们知道,信息系统有垃圾进垃圾出的特点,为了避免垃圾数据的输入,对输入进行校验是任何信息系统都要面对的问题。在传统的编程实践中,我们往往在需要进行校验的地方分别对它们进行校验,而实际上需要校验的东西大多都很类似,如必需的字段、日期、范围等等。因此,应用程序中往往到处充斥着这样一些显得冗余的代码。而与此形成鲜明对照的是Struts采用Validator框架(Validator框架现在是Jakarta Commons项目的一部分)来解决校验问题,它将校验规则代码集中到外部的且对具体的应用程序中立的.xml文件中,这样,就将那些到处出现的校验逻辑从应用程序中分离出来,任何一个Struts应用都可以使用这个文件,同时还为校验规则的扩展提供了便利。更难能可贵的是由于Validator框架将校验中要用到的一些消息等信息与资源绑定有机结合在一起,使得校验部分的国际化编程变得十分的便捷和自然。
Validator框架大致有如下几个主要组件:
Validators:是Validator框架调用的一个Java类,它处理那些基本的通用的校验,包括required、mask(匹配正则表达式)、最小长度、最大长度、范围、日期等
.xml配置文件:主要包括两个配置文件,一个是validator-rules.xml,另一个是validation.xml。前者的内容主要包含一些校验规则,后者则包含需要校验的一些form及其组件的集合。
资源绑定:提供(本地化)标签和消息,缺省地共享struts的资源绑定。即校验所用到的一些标签与消息都写在ApplicationResources.properity文件中。
Jsp tag:为给定的form或者action path生成JavaScript validations。
ValidatorForm:它是ActionForm的一个子类。
为了对Validator框架有一个比较直观的认识,我们还是以前面的登陆例子的输入来示范一下Validator框架的使用过程:
首先,找一个validator-rules.xml文件放在mystruts/WEB-INF目录下,下面是该文件中涉及到的required验证部分代码的清单:
<validator name="required" <!--①--> classname="org.apache.struts.validator.FieldChecks" method="validateRequired" methodParams="java.lang.Object, org.apache.commons.validator.ValidatorAction, org.apache.commons.validator.Field, org.apache.struts.action.ActionErrors, javax.servlet.http.HttpServletRequest" <!--②--> msg="errors.required"> <!--③--> <javascript><![CDATA[ function validateRequired(form) { var isValid = true; var focusField = null; var i = 0; var fields = new Array(); oRequired = new required(); for (x in oRequired) { var field = form[oRequired[x][0]]; if (field.type == 'text' || field.type == 'textarea' || field.type == 'file' || field.type == 'select-one' || field.type == 'radio' || field.type == 'password') { var value = ''; // get field's value if (field.type == "select-one") { var si = field.selectedIndex; if (si >= 0) { value = field.options[si].value; } } else { value = field.value; } if (trim(value).length == 0) { if (i == 0) { focusField = field; } fields[i++] = oRequired[x][1]; isValid = false; } } } if (fields.length > 0) { focusField.focus(); alert(fields.join('/n')); } return isValid; } // Trim whitespace from left and right sides of s. function trim(s) { return s.replace( /^/s*/, "" ).replace( //s*$/, "" ); } ]]> </javascript> </validator> |
① 节的代码是引用一个服务器边的验证器,其对应的代码清单如下:
public static boolean validateRequired(Object bean, ValidatorAction va, Field field, ActionErrors errors, HttpServletRequest request) { String value = null; if (isString(bean)) { value = (String) bean; } else { value = ValidatorUtil.getValueAsString(bean, field.getProperty()); } if (GenericValidator.isBlankOrNull(value)) { errors.add(field.getKey(), Resources.getActionError(request, va, field)); return false; } else { return true; } } |
② 节是验证失败后的出错信息,要将对应这些键值的信息写入到ApplicationResources.properity文件中,常见的错误信息如下:
# Standard error messages for validator framework checks errors.required={0} is required. errors.minlength={0} can not be less than {1} characters. errors.maxlength={0} can not be greater than {1} characters. errors.invalid={0} is invalid. errors.byte={0} must be a byte. errors.short={0} must be a short. errors.integer={0} must be an integer. errors.long={0} must be a long. errors.float={0} must be a float. errors.double={0} must be a double. errors.date={0} is not a date. errors.range={0} is not in the range {1} through {2}. errors.creditcard={0} is an invalid credit card number. errors.email={0} is an invalid e-mail address. |
③ 节的代码用于客户边的JavaScript验证
其次,在validation.xml文件中配置要验证的form极其相应的字段,下面是该文件中的代码:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE form-validation PUBLIC "-//Apache Software Foundation //DTD Commons Validator Rules Configuration 1.0//EN" "http://jakarta.apache.org/commons/dtds/validator_1_0.dtd"> <form-validation> <formset> <form name="userInfoForm"> <field property="username" depends="required,mask,minlength,maxlength"> <arg0 key="logon.jsp.prompt.username" resource="true"/> <arg1 name="minlength" key="${var:minlength}" resource="false"/> <arg1 name="maxlength" key="${var:maxlength}" resource="false"/> <var> <var-name>mask</var-name> <var-value>^/w</var-value> </var> <var> <var-name>minlength</var-name> <var-value>2</var-value> </var> <var> <var-name>maxlength</var-name> <var-value>16</var-value> </var> </field> <field property="password" depends="required,minlength,maxlength"> <arg0 key="logon.jsp.prompt.password" resource="true"/> <arg1 name="minlength" key="${var:minlength}" resource="false"/> <arg1 name="maxlength" key="${var:maxlength}" resource="false"/> <var> <var-name>minlength</var-name> <var-value>2</var-value> </var> <var> <var-name>maxlength</var-name> <var-value>16</var-value> </var> </field> </form> </formset> </form-validation> |
这里要注意的是:该文中的
和
中的键值都是取自资源绑定中的。前面还讲到了出错信息也是写入ApplicationResources.properity文件中,因此,这就为国际化提供了一个很好的基础。
再次,为了使服务器边的验证能够进行,将用到的formBean从ActionForm的子类改为ValidatorForm的子类,即:
将public class UserInfoForm extends ActionForm改为:public class UserInfoForm extends ValidatorForm
到此,进行服务器边的验证工作已经一切准备得差不多了,此时,只要完成最后步骤就可以实验服务器边的验证了。但大多数情况下,人们总希望把这些基本的简单验证放在客户边进行。
为了能进行客户边的验证,我们还要对logon.jsp文件做适当的修改。
将
<html:form action="/logonAction.do" focus="username"> |
改为
<html:form action="/logonAction.do" focus="username" οnsubmit="return validateUserInfoForm(this)"> |
在标签后加上:
<html:javascript dynamicJavascript="true" staticJavascript="true" formName="userInfoForm"/> |
最后,对struts的配置文件struts-config.xml作适当的修改:
1、将
<action input="/logon.jsp" name="userInfoForm" path="/logonAction" scope="session" type="action.LogonAction" validate="false" > |
改为
<action input="/logon.jsp" name="userInfoForm" path="/logonAction" scope="session" type="action.LogonAction" validate="true" > |
其作用是要求进行校验
2、将下列代码放在struts-config.xml文件中的标签前。其作用是将用于校验的各个组件结合在一起。
<plug-in className="org.apache.struts.validator.ValidatorPlugIn"> <set-property property="pathnames" value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml" /> </plug-in> |
到此为止,我们的一切工作准备就绪,您可以享受自己的劳动成果了,试着输入各种组合的用户名和口令,看看它们的验证效果。仔细体会你会发现,服务器边的验证要更全面一些,比如对password的字符长度的验证。
参考文献:
《Struts in Action》Ted Husted Cedric Dumoulin George Franciscus David Winterfeldt著
《Programming Jakarta Struts》Chuck Cavaness著
第7部分
上一篇文章中介绍校验时提到客户边的校验用到了JavaScript,实际上用Struts配合JavaScript还可以实现许多有用的功能,比如,级联下拉菜单的实现就是一个典型的例子:
本例假设要实现的是一个文章发布系统,我们要发布的文章分为新闻类和技术类,其中新闻类又分为时事新闻和行业动态;技术类又分为操作系统、数据库、和编程语言等,为了便于添加新的条目,所有这些都保存在数据库表中。
为此,我们建立一个名为articleClass的表和一个名为articleSubClass的表。
articleClass表的结构如下: articleClassID字段:char类型,长度为2,主键 articleClassName字段:varchar类型,长度为20 articleSubClass表的结构如下: articleClassID字段:char类型,长度为2 articleSubClassID字段:char类型,长度为2与articleClassID一起构成主键 articleSubClassName字段:varchar类型,长度为20 |
表建好后,在articleClass表中录入如下数据:如,01、新闻类;02、技术类
在articleSubClass表中录入:01、01、时事新闻;01、02、行业动态;02、01、操作系统等记录。到这里,数据库方面的准备工作就已做好。
有了前面做登录例子的基础,理解下面要进行的工作就没有什么难点了,我们现在的工作也在原来mystruts项目中进行。首先,建立需要用到的formbean即ArticleClassForm,其代码如下:
package entity; import org.apache.struts.action.*; import javax.servlet.http.*; import java.util.Collection; public class ArticleClassForm extends ActionForm { //为select的option做准备 private Collection beanCollection; private String singleSelect = ""; private String[] beanCollectionSelect = { "" }; private String articleClassID; private String articleClassName; private String subI;//子类所在行数 private String subJ;//子类所在列数 private String articleSubClassID; private String articleSubClassName; public Collection getBeanCollection(){ return beanCollection; } public void setBeanCollection(Collection beanCollection){ this.beanCollection=beanCollection; } public String getSingleSelect() { return (this.singleSelect); } public void setSingleSelect(String singleSelect) { this.singleSelect = singleSelect; } public String[] getBeanCollectionSelect() { return (this.beanCollectionSelect); } public void setBeanCollectionSelect(String beanCollectionSelect[]) { this.beanCollectionSelect = beanCollectionSelect; } public String getArticleClassID() { return articleClassID; } public void setArticleClassID(String articleClassID) { this.articleClassID = articleClassID; } public String getArticleClassName() { return articleClassName; } public void setArticleClassName(String articleClassName) { this.articleClassName = articleClassName; } public String getSubI() { return subI; } public void setSubI(String subI) { this.subI = subI; } public String getSubJ() { return subJ; } public void setSubJ(String subJ) { this.subJ = subJ; } public String getArticleSubClassID() { return articleSubClassID; } public void setArticleSubClassID(String articleSubClassID) { this.articleSubClassID = articleSubClassID; } public String getArticleSubClassName() { return articleSubClassName; } public void setArticleSubClassName(String articleSubClassName) { this.articleSubClassName = articleSubClassName; } } |
将它放在包entity中。其次,我们的系统要访问数据库,因此也要建立相应的数据库访问对象ArticleClassDao,其代码如下:
package db; import entity.ArticleClassForm; import db.*; import java.sql.*; import java.util.Collection; import java.util.ArrayList; import org.apache.struts.util.LabelValueBean; public class ArticleClassDao { private Connection con; public ArticleClassDao(Connection con) { this.con=con; } public Collection findInUseForSelect(){ PreparedStatement ps=null; ResultSet rs=null; ArrayList list=new ArrayList(); String sql="select * from articleClass order by articleClassID"; try{ if(con.isClosed()){ throw new IllegalStateException("error.unexpected"); } ps=con.prepareStatement(sql); rs=ps.executeQuery(); while(rs.next()){ String value=rs.getString("articleClassID"); String label=rs.getString("articleClassName"); list.add(new LabelValueBean(label,value)); } return list; } catch(SQLException e){ e.printStackTrace(); throw new RuntimeException("error.unexpected"); } finally{ try{ if(ps!=null) ps.close(); if(rs!=null) rs.close(); } catch(SQLException e){ e.printStackTrace(); throw new RuntimeException("error.unexpected"); } } } public Collection findInUseForSubSelect(){ PreparedStatement ps=null; ResultSet rs=null; PreparedStatement psSub=null; ResultSet rsSub=null; int i=0;//大类记数器 int j=0;//小类记数器 String classID=""; String subClassID=""; String subClassName=""; ArrayList list=new ArrayList(); ArticleClassForm articleClassForm; String sql="select * from articleClass order by articleClassID"; try{ if(con.isClosed()){ throw new IllegalStateException("error.unexpected"); } ps=con.prepareStatement(sql); rs=ps.executeQuery(); while(rs.next()){ i++; classID=rs.getString("articleClassID"); String sqlSub="select * from articleSubClass where articleClassID=? order by articleSubClassID"; psSub=con.prepareStatement(sqlSub); psSub.setString(1,classID); rsSub=psSub.executeQuery(); articleClassForm=new ArticleClassForm(); articleClassForm.setSubI(""+i); articleClassForm.setSubJ(""+j); articleClassForm.setArticleSubClassID("请输入一个小类"); articleClassForm.setArticleSubClassName("请输入一个小类"); list.add(articleClassForm); while(rsSub.next()){ subClassID=rsSub.getString("articleSubClassID"); subClassName=rsSub.getString("articleSubClassName"); j++; //optionStr="articleSubClassGroup[" + i + "][" + j + "]= new Option('"+ subClassName +"','"+ subClassID+ "')"; articleClassForm=new ArticleClassForm(); articleClassForm.setSubI(""+i); articleClassForm.setSubJ(""+j); articleClassForm.setArticleSubClassID(subClassID); articleClassForm.setArticleSubClassName(subClassName); list.add(articleClassForm); } j=0; } return list; } catch(SQLException e){ e.printStackTrace(); throw new RuntimeException("error.unexpected"); } finally{ try{ if(ps!=null) ps.close(); if(rs!=null) rs.close(); } catch(SQLException e){ e.printStackTrace(); throw new RuntimeException("error.unexpected"); } } } } |
将它保存在db目录中。它们的目的是将文章的类和子类信息从数据库表中读出,以一定的格式保存在集合对象中以供页面显示。
再次,我们要建立相应的jsp文件,文件名为selectArticleClass.jsp,代码如下:
<%@ page contentType="text/html; charset=UTF-8" %> <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %> <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %> <%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %> <html> <head> <title> 选择文件类别 </title> </head> <body bgcolor="#ffffff"> <h3> 选择文件所属类型 </h3> <html:errors/> <table width="500" border="0" cellspacing="0" cellpadding="0"> <tr> <td><html:form name="articleClassForm" type="entity.ArticleClassForm" action="selectArticleClassAction.do"> <table width="500" border="0" cellspacing="0" cellpadding="0"> <tr> <td align="right">文章大类*</td> <td> <html:select property="articleClassID" styleClass="word" οnchange="articleClassFormredirect(this.options.selectedIndex)"> <html:option value="">请选择一个大类</html:option> <html:optionsCollection name="articleClassForm" property="beanCollection" styleClass="word"/> </html:select> </td> </tr> <tr> <td align="right">文章小类*</td> <td> <select name="articleSubClassID" Class="word" > <option value="">请选择一个小类</option> </select> <SCRIPT language=JavaScript> <!-- var articleSubClassGroups=document.articleClassForm.articleClassID. options.length var articleSubClassGroup=new Array(articleSubClassGroups) for (i=0; i<articleSubClassGroups; i++) articleSubClassGroup[i]=new Array() <logic:iterate name="articleSubClassList" id="articleClassForm" scope="request" type="entity.ArticleClassForm"> articleSubClassGroup[<bean:write name="articleClassForm" property="subI"/>][<bean:write name="articleClassForm" property="subJ"/>]=new Option("<bean:write name="articleClassForm" property="articleSubClassName"/>","<bean:write name="articleClassForm" property="articleSubClassID"/>") </logic:iterate> var articleSubClassTemp=document.articleClassForm.articleSubClassID function articleClassFormredirect(x){ for (m=articleSubClassTemp.options.length-1;m>0;m--) articleSubClassTemp.options[m]=null for (i=0;i<articleSubClassGroup[x].length;i++){ articleSubClassTemp.options[i]=new Option(articleSubClassGroup[x][i].text, articleSubClassGroup[x][i].value) } articleSubClassTemp.options[0].selected=true } //--> </SCRIPT> </td> </tr> </table> </html:form> </td> </tr> </table> </body> </html> |
这里值得重点关注的是其中的JavaScript代码,有兴趣的可以仔细分析一下它们是怎样配合集合中的元素来实现级联选择的。
最后,为了例子的完整。我们将涉及到action代码和必要的配置代码在下面列出:其中,action的文件名为SelectArticleClassAction.java,代码如下:
package action; import entity.*; import org.apache.struts.action.*; import javax.servlet.http.*; import javax.sql.DataSource; import java.sql.Connection; import db.ArticleClassDao; import java.util.Collection; import java.sql.SQLException; public class SelectArticleClassAction extends Action { public ActionForward execute(ActionMapping actionMapping, ActionForm actionForm, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { /**@todo: complete the business logic here, this is just a skeleton.*/ ArticleClassForm articleClassForm = (ArticleClassForm) actionForm; DataSource dataSource; Connection cnn=null; ActionErrors errors=new ActionErrors(); try{ dataSource = getDataSource(httpServletRequest,"A"); cnn = dataSource.getConnection(); ArticleClassDao articleClassDao=new ArticleClassDao(cnn); Collection col=articleClassDao.findInUseForSelect(); articleClassForm.setBeanCollection(col); httpServletRequest.setAttribute("articleClassList",col); //处理子类选项 Collection subCol=articleClassDao.findInUseForSubSelect(); httpServletRequest.setAttribute("articleSubClassList",subCol); return actionMapping.findForward("success"); } catch(Throwable e){ e.printStackTrace(); //throw new RuntimeException("未能与数据库连接"); ActionError error=new ActionError(e.getMessage()); errors.add(ActionErrors.GLOBAL_ERROR,error); } finally{ try{ if(cnn!=null) cnn.close(); } catch(SQLException e){ throw new RuntimeException(e.getMessage()); } } saveErrors(httpServletRequest,errors); return actionMapping.findForward("fail"); } } |
将其保存在action目录中。
在struts-config.xml文件中做如下配置:
在
<form-beans> |
<form-bean name="articleClassForm" type="entity.ArticleClassForm" /> |
在
><action-mappings> |
<action name="articleClassForm" path="/selectArticleClassAction" scope="session" type="action.SelectArticleClassAction" validate="false"> <forward name="success" path="/selectArticleClass.jsp" /> <forward name="fail" path="/genericError.jsp" /> </action> |
为了对应配置中的
<forward name="fail" path="/genericError.jsp" /> |
<%@ page contentType="text/html; charset=UTF-8" %> <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %> <html> <head> <title> genericError </title> <link href="css/mycss.css" rel="stylesheet" type="text/css"> </head> <body bgcolor="#ffffff"> <html:errors/> </body> </html> |
现在一切就绪,可以编译执行了。在浏览器中输入:http://127.0.0.1:8080/mystruts/selectArticleClassAction.do就可以看到该例子的运行结果了。(T111)
本文作者:张永美 罗会波 湖北省当阳市国税局 可通过lhbf@sina.com与他们联系