为什么要有输入校验
输入校验也是所有Web应用必须处理的问题,因为Web应用的开放性, 网络上所有的浏览者都可以自由使用该应用,因此该应用通过输入页面收集的数据是非常复杂的,不仅会包含正常用户的误输入,还可能包含恶意用户的恶意输入。一个健壮的应用系统必须将这些非法输入阻止在应用之外,防止这些非法输入进入系统,这样才可以保证系统不受影响。
异常的输入,轻则导致系统非正常中断,重则导致系统崩溃。应用程序必须能正常处理表现层接收的各种数据,通常的做法是遇到异常输入时应用程序直接返回,提示浏览者必须重新输入,也就是将那些异常输入过滤掉。对异常输入的过滤,就是输入校验, 也称为数据校验。
输入校验分为客户端校验和服务器校验,客户端校验主要是过滤正常用户的误操作,主要通过JavaScript代码完成;服务器端校验是整个应用阻止非法数据的最后防线,主要通过在应用中编程实现。
客户端校验的主要作用是防止正常浏览者的误输入,仅能对输入进行初步过滤;对于恶意用户的恶意行为,客户端校验将无能为力。因此,客户端校验绝不可代替服务器端校验。当然,客户端校验也绝不可少,因为Web应用大部分浏览者都是正常的浏览者,他们的输入可能包含了大量的误输入,客户端校验把这些误输入阻止在客户端,从而降低了服务器的负载。(如果你都把数据放在服务端校验,这些错误的数据会在网络传输,浪费带宽,你觉得合适吗?)
Struts 2的输入校验既包括服务器端校验,也包括客户端校验。
编写检验规则文件
校验文件就是每一个表单域要满足的条件。
这里我们需要注意的就是,我们表单校验是基于你输入表单数据类型要正确那个转换。
<s:form action="login" method="get">
<s:textfield name="name" label="姓名"/>
<s:textfield name="pass" label="密码"/>
<s:textfield name="age" label="年纪"/>
<s:textfield name="birth" label="生日"/>
<s:submit value="注册"/>
</s:form>
Action 就是提供表单属性的set、get方法。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE validators PUBLIC
"-//Apache Struts//XWork Validator 1.0.3//EN"
"http://struts.apache.org/dtds/xwork-validator-1.0.3.dtd">
<!-- 校验文件的根元素 -->
<validators>
<field name="name">
<field-validator type="requiredstring">
<param name="trim">true</param>
<message>名称必须填</message>
</field-validator>
<field-validator type="regex">
<param name="regexExpression">![CDATA[(\w{4,25})]]</param><!--regex,regexExpression name是这两种类型都可以 -->
<message>用户长度在4-25之间</message>
</field-validator>
</field>
<field name="age">
<field-validator type="int">
<param name="min">1</param>
<param name="max">150</param>
<message>年级在1-150之间</message>
</field-validator>
</field>
<field name="birth">
<field-validator type="date">
<param name="min">1900-01-01</param>
<param name="max">2020-09-09</param>
<message>时间不能超过规定的日期哦</message>
</field-validator>
</field>
<field name="pass">
<field-validator type="requiredstring">
<param name="trim">true</param>
<message>密码必须填</message>
</field-validator>
<field-validator type="regex">
<param name="regex">![CDATA[(\w{4,25})]]</param>
<message>密码长度在4-25之间</message>
</field-validator>
</field>
</validators>
我这里写的类型器,就这几个,至于其他的,自己需要的时候自己百度。
编写校验规则文件注意点:
<!DOCTYPE validators PUBLIC
"-//Apache Struts//XWork Validator 1.0.3//EN"
"http://struts.apache.org/dtds/xwork-validator-1.0.3.dtd">
这段话是引入校验器的环境,没有这个,首先标签就不能自动提示。你可以不能乱写,这里的版本号一定要对应你的xworkjar包下面的,校验器配置文件,这里才可以哦。具体,你可以自己解压jar包,支持哪个版本。
这里也提醒了大家,大家在使用框架的配置文件的时候,在导入环境的时候,你最好自己看看它是怎么引用,以免版本问题。
- 和类型转换一样,校验失败的时候,会返回input视图,所以,你需要配置一个input视图。
- 查看校验失败信息的时候,你需要来显示。
配置国际化资源
国际化资源文件存放在src下。
在上述的代码:做如下的修改
<field-validator type="requiredstring">
<param name="trim">true</param>
<message key="name.require"></message>
</field-validator>
创建资源文件,具体可以参考上面链接
name.require=\u540D\u5B57\u5FC5\u987B\u7684
<constant name="struts.custom.i18n.resources" value="base" />
在struts中配置常量属性,上面链接中,如果是配置包的资源属性和action的资源属性文件是不需要配置该常量属性。
使用客户端校验
客户端的校验是基于JavaScript的。
在MVC的思想中,用户是不可以向视图,也就是Jsp发出请求,而是应该向控制器(action)发送请求,由控制器返回视图。
当我们请求jsp的时候,我们需要配置一个通用的action来返回jsp。
<action name="*">
<result >/{1}.jsp</result>
</action>
这个表示什么意思,就是当我们在请求的时候,输入的action,*是匹配任意的,这时就是转到/你输入action名字.jsp。
至于校验文件,按照上面的,没有如何改变。
在表单form中添加该属性:
validate="true"
注意运行之后,地址栏的变化。
地址栏没有变化,没有提交到对应的action中校验,这就是完成在客户端的校验。
客户端的校验实际上,减少不必要的带宽,你把错误数据传过去,是不是浪费带宽,如果说必须是服务器的检验,那是没办法。
客户端支持的校验器:
客户端校验的注意点:
- Struts2的表单有一个theme属性,不要将该属性指定为simple。
- 启用客户端校验的表单页面的action和namespace要分开写,例如我们向namespace 为/lee, name为registPro 的Action请求,应写成, 而不应该写成。一般不写namespace默认是在跟目录下。
字段校验器配置风格
字段是优先的。
之前的校验文件就是基于字段的,这里的例子,就不在演示,只是做些文字说明。
field元素是校验规则文件的基本组成单位,每个field元素指定一个Action属性必须遵守的规则,该元素的name属性指定了被校验的字段;如果该属性需要满足多个规则,则在该field元素下增加多个field-validator元素。
每个field-validator元素指定一个校验规则,该元素的type属性指定了校验器名称,该元素可以包含多个param子元素,用于指定该校验器的参数;除此之外,每个field-validator元素都有一个必需的message元素,该元素确定校验失败后的提示信息。
message元素的key属性指定了校验失败后提示因际化信息对应的key,该元素的内容是校验失败后的默认提示信息。
非字段校验器
校验器优先。
每个validator元素定义了一个校验规则,该元素需要-个type属性,该type属性指定了该校验器的名字。
使用非字段校验器的配置风格时,采用的是校验器优先的方式,故必须为validator配置一个fieldName参数,该参数的值就是被校验的Action属性名。除此之外,还需要指定数量不等的param元素,这些都是指定校验器所需的参数。
<validators>
<validator type="requiredstring">
<param name="fieldName">name</param>//这个是必须要有的
<param name="trim">true</param>
<message>名称必须有</message>
</validator>
</validators>
字段校验器和非字段校验器使用,主要看你喜欢哪一种。
短路校验器
短路这个概念,我相信大家应该都懂,一个不成功,后面的就不要判断,直接否定。
在刚才上面的校验器中,我们对于名字字段,我们设置了两个校验器,一个是必填字段,还有一个是用户名长度,校验的时候,你如果两个都不符合,它就会两个提示信息,这又必要吗,如果都不输入用户名,何来长度之说。
修改校验文件:
<field name="name">
<field-validator type="requiredstring" short-circuit="true">
<param name="trim">true</param>
<message key="name.require"></message>
</field-validator>
<field-validator type="regex">
<param name="regexExpression">![CDATA[(\w{4,25})]]</param>
<message key="name.length"></message>
</field-validator>
</field>
我们给用户名的必填字段设置了短路,也就是说如果你没有填用户名,其他校验器是不会起作用的。
字段的校验器的短路属性,默认是false。如果设置为true,一旦短路校验器不通过,后面的校验器是不会起作用的。
校验文件搜索规则
Struts2的一个Action中可能包含了多个处理逻辑,当一个Action类中包含多个类似于execute的方法时,每个方法都是一个处理逻辑。不同的处理逻辑可能需要不同的校验规则,Struts2 允许为不同控制逻辑指定不同校验规则的支持。
当需要让一个Action可以处理多个请求时,应该在配置该Action时指定method属性。通过这种方式,就可以将一个Action处理类配置成多个逻辑Action。
我们修改action,配置多个逻辑处理方法。
public String regist() throws Exception {
// TODO Auto-generated method stub
System.out.println(name+"jjjj");
return SUCCESS;
}
public String login() throws Exception{
return SUCCESS;
}
你自己定义方法的时候,一定要删除重写的那个注解。
配置Strut配置文件:
<action name="*pro" class="com.example.test.action.LoginAction" method="{1}">
<result name="input">/index.jsp</result>
<result name="success">/show.jsp</result>
<allowed-methods>login,regist</allowed-methods>
</action>
这里你的action名称是loginpro,它就会对应的执行login方法。
但是这里有一个需要注意的地方,allowed-methods 这个标签,在struts 2.5的时候,必须要写这个,否则,它会报找不到对应的action。
修改jsp:
<s:submit value="注册" onclick="this.form.action='registpro';"/>
<s:submit value="登录" onclick="this.form.action='loginpro';"/>
两个按钮提交到不同的action。
我在登录,我们希望,判断它的用户名和密码是否一样。
<validators>
<field name="name">
<field-validator type="fieldexpression">
<param name="expression">![CDATA[(name==pass)]]</param>
<message>用户名和密码不能相同</message>
</field-validator>
</field>
</validators>
<ActionClassName>-<ActionAliasName> - validation.xml
其中ActionClassName是Action处理类的类名,而ActionAliasName 就是在struts.xml 中配置该Action时所指定的name属性。
基于上面上面的配置,我们就可以完成action多个处理方法的校验
<ActionClassName>-validation.xml
<ActionClassName>-<ActionAliasName> - validation.xml
这个两个校验文件,我们通过执行发现,ActionAliasName的action的校验规则是包含上面两个校验规则的。而如果不是ActionAliasName的action,则只会执行ActionClassName-validation.xml。在这里里面ActionClassName-validation.xml就像总的,所有人都要听,每个人都要执行的校验,只要是对应的是ActionClassName类,ActionClassNameActionAliasName-validation.xml,只是一个局部,就针对ActionAliasName action的校验。
注意点:
假设系统有两个Action: BaseAction 和RegistAction,则系统搜索规则文件顺序如下:
- BaseAction-validation.xml
- BaseAction-别名-validation.xml
- RegistAction-validation.xml
- RegistAction-别名-validation.xml
由上面可以看出,校验文件的搜索是一直进行的,只有有对应的校验文件,即使找到第一个校验文件,也还是会继续往下,直到结束。如果校验规则冲突,则搜索靠后的获胜。
校验顺序和短路
内建校验器
Struts2提供了大量的内建校验器,这些内建的校验器可以满足大部分应用的校验需求,开发者只需要使用这些校验器即可。如果应用有一个特别复杂的校验需求,而且该校验有很好的复用性,开发者可以开发自己的校验器。
你可以在xwork下的validators路径下找到一个default.xml文件,这个文件就是Struts2默认的校验器注册文件,包含了struts全部支持的校验器。
注册一个校验器是如此简单:通过一个validator元素即可注册一个校验器,每个validator元素的name属性指定该校验器的名字,class 属性指定该校验器的实现类。
如果开发者开发了一个自己的校验器,则可以通过添加一个validators.xml文件(该文件应该放在WEB-INF/classes路径下来注册校验器。validators.xml文件的内容也是由多个validator元素组成的,每个
<s:form action="login" >
<s:textfield name="user.name" label="姓名"/>
<s:submit value="注册" />
</s:form>
在action中定义这个属性,是复合属性User类型,并且提供set、get方法。
private User user;
定义action的校验文件
LoginAction-validation.xml
<field name="user">
<field-validator type="visitor">
<param name="context">usercontex</param>
<message>the name of user is error</message>
</field-validator>
</field>
这个校验文件只是指定user字段的校验方式,并没有指出User中name字段的校验规则,所以我们还是需要为User定义校验文件。
这个校验没啥难的,就是前面校验文件的name的校验,不过这个文件的名字因为是针对的是User,User-usercontex-validation.xml ,usercontex是上面参数的值。另外,还要注意的是该文件是针对User,应该和bean放在同一个包下
基于注解的输入校验
有jdk1.5之后2,支持注解,这真的是太方便了。
@RequiredStringValidator(message="你的名字有误啦")
我们直接可以在对应的action文件中的属性的set方法上面,使用各种注解校验器。
在这里需要提醒大家的是,一旦使用注解校验,你原来的校验文件,就要删除,否则,它还是找到那个文件,以防带来错误。
在注解校验的几个属性中:
- key 是指定国际资源文件的的键key,如果你指定key,就是代表错误信息使用的是国际资源文件的,message属性就不要填任何东西,但是必须要有message属性,它是api规定的。
- 还有一个api规定的属性是type,该属性是说明,该校验器是非字段校验,还是字段校验,它默认是字段校验。
基于这种方式比较简单,但是它所有的代码都集中到了action的java中,还是有不好的地方。
手动完成校验
基于Struts的校验可以说,已经能够满足绝大多数的需求,但是这些校验器的逻辑是固定的,无法满足一些固定的需求。
重写action的validate方法
@Override
public void validate() {
// TODO Auto-generated method stub
super.validate();
}
在这个方法里面,你可以定义自己的校验逻辑。通过addFieldError()方法把错误信息添加到系统中,Struts发现系统FieldError不为空,就会转入input页面。
ValidateXxxx 方法
Struts 2的Action类里可以包含多个处理逻辑,不同的处理逻辑对应不同的方法。即Struts2的Action类里定义了几个类似于execute的方法, 只是方法名不是execute。
如果我们的输入校验只想校验某个处理逻辑,也就是仅校验某个处理方法,则重写validate 方法显然不够,validate方法无法准确知道需要校验哪个处理方法。实际上,如果我们重写了Action的validate 方法,则该方法会校验所有的处理逻辑。
为了实现校验指定处理逻辑的功能,Struts 2的Action允许提供一个validateXxx0方法,其中xxx即是Action对应的处理逻辑方法,首字母要大写。
下面给出了局部代码:
<action name="*pro" class="com.example.test.action.LoginAction" method="{1}">
<result name="input">/index.jsp</result>
<result name="success">/show.jsp</result>
<allowed-methods>login,regist</allowed-methods>
</action>
public void validateLogin() {
// TODO Auto-generated method stub
super.validate();
if(name.contains("laoqiang")){
addFieldError("user", "name is error");
}
}
public void validateRegist() {
// TODO Auto-generated method stub
super.validate();
if(name.contains("laoqiang")){
addFieldError("user", "pass is error");
}
}