一、类型转换
1、继承ValidationAware 接口,进行错误捕捉
若 Action 类实现 ValidationAware 接口:Struts 在遇到类型转换错误时将不会继续调用其 Action 方法。Struts 将检查相关 action 元素的声明是否包含着一个 name=input 的 result.如果有, Struts 将把控制权转交给那个 result 元素; 若没有 input 结果, Struts 将抛出一个异常
//继承 ActionSupport,因为ActionSupport实现了ValidationAware 接口 public class ConversionAction extends ActionSupport //在struts.xml中配置result input <action name="testConversion" class="com.atguigu.struts2.app.ConversionAction"> <result>/success.jsp</result> <result name="input">/index.jsp</result> </action>
2、如何覆盖默认的错误消息?
1). 在对应的 Action 类所在的包中新建ActionClassName.properties 文件, ActionClassName 即为包含着输入字段的 Action 类的类名
2). 在属性文件中添加如下键值对: invalid.fieldvalue.fieldName=xxx
如:为Action类 ConversionAction.java新建配置文件ConversionAction.properties。invalid.fieldvalue.age=请输入正确的年龄值.
3、simple 主题, 如何自动显示错误消息
1). 值栈的 Action(实现了 ValidationAware 接口) 对象中有一个 fieldErrors 属性.该属性的类型为 Map<String, List<String>> 键: 字段(属性名), 值: 错误消息组成的 List. 所以可以使用 LE 或 OGNL 的方式来显示错误消息:
${fieldErrors.age[0]}
2). 还可以使用 s:fielderror 标签来显示. 可以通过 fieldName 属性显示指定字段的错误.
<s:fielderror fieldName="age"></s:fielderror>
注:该消息显示在一个 ul, li, span 中. 如何去除 ul, li, span 呢 ?
在 template.simple 下面的 fielderror.ftl 定义了 simple 主题下, s:fielderror 标签显示错误消息的样式. 所以修改该配置文件即可. 在 src 下新建 template.simple 包, 新建 fielderror.ftl 文件, 把原生的 fielderror.ftl 中的内容复制到新建的 fielderror.ftl 中, 然后剔除 ul, li, span 部分即可.
4. 如何自定义类型转换器
I. 开发类型转换器的类: 扩展 StrutsTypeConverter 类.
package com.iThings.struts.convertor; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Map; import org.apache.struts2.util.StrutsTypeConverter; public class DateTypeConvertor extends StrutsTypeConverter { @Override public Object convertFromString(Map arg0, String[] arg1, Class arg2) { if(arg2 == Date.class){ if(arg1!=null && arg1.length>0){ String value = arg1[0]; DateFormat df = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); try { return df.parseObject(value); } catch (ParseException e) { e.printStackTrace(); } } } //若没有转换成功,则返回arg1 return arg1; } @Override public String convertToString(Map arg0, Object arg1) { if(arg1 instanceof Date){ DateFormat df = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); Date date = (Date)arg1; return df.format(date); } //若转换失败,则返回空 return null; } }
II. 配置类型转换器:
有两种方式①. 基于字段的配置:
> 在字段所在的 Model(可能是 Action, 可能是一个 JavaBean) 的包下, 新建一个 ModelClassName-conversion.properties 文件> 在该文件中输入键值对: fieldName=类型转换器的全类名. java.util.Date = com.iThings.conversion.DateConverter> 第一次使用该转换器时创建实例.> 类型转换器是单实例的!
Customer-conversion.properties
<span style="font-size:18px;">birth =com.iThings.struts.convertor.DateTypeConvertor</span>
②. 基于类型的配置:
> 在 src 下新建 xwork-conversion.properties> 键入: 待转换的类型=类型转换器的全类名. java.util.Date = com.iThings.conversion.DateConverter> 在当前 Struts2 应用被加载时创建实例.
xwork-conversion.properties
<span style="font-size: 18px;">java.util.Date </span>= com.iThings.struts.convertor.DateTypeConvertor
5.类型转换器与复杂属性&集合协同使用
1)form 标签的 name 属性可以被映射到一个属性的属性.
<s:form action="testConversion"> <s:textfield name="age" label="Age"></s:textfield> <s:textfield name="birth" label="Birth"></s:textfield> <s:textfield name="emp.firstName" label="FirstName"></s:textfield> <s:textfield name="emp.lastName" label="lastName"></s:textfield> <br><br> <s:submit></s:submit> </s:form>
2)Struts 还允许填充 Collection 里的对象, 这常见于需要快速录入批量数据的场合
<s:form action="testComplexPropertyAction"> <s:textfield name="emps[0].firstName" label="firstName"></s:textfield> <s:textfield name="emps[0].lastName" label="lastName"></s:textfield> <s:textfield name="emps[1].firstName" label="firstName"></s:textfield> <s:textfield name="emps[1].lastName" label="lastName"></s:textfield> <s:textfield name="emps[2].firstName" label="firstName"></s:textfield> <s:textfield name="emps[2].lastName" label="lastName"></s:textfield> <s:submit></s:submit> </s:form> public class TestComplexPropertyAction extends ActionSupport { private static final long serialVersionUID = 1L; private Collection<Employee> emps = null; public Collection<Employee> getEmps() { return emps; } public void setEmps(Collection<Employee> emps) { this.emps = emps; } public String execute(){ System.out.println(emps); return "success"; } }
二、输入验证
1、声明式验证
I. 编写配置文件:> 把 struts-2.3.15.3\apps\struts2-blank\WEB-INF\classes\example 下的 Login-validation.xml 文件复制到当前 Action 所在的包下.
> 把该配置文件改为: 把 Login 改为当前 Action 的名字.
> 编写验证规则: 参见 struts-2.3.15.3/docs/WW/docs/validation.html 文档即可.
> 在配置文件中可以定义错误消息:
<field name="age">
<field-validator type="int">
<param name="min">20</param><param name="max">50</param><message>Age needs to be between ${min} and ${max}</message>
</field-validator>
</field>
> 该错误消息国际化:在国际化资源文件 中加入一个键值对:
error.int=Age needs to be between ${min} and ${max}
<message key="error.int"></message>.
II. 若验证失败, 则转向 input 的那个 result. 所以需要配置 name=input 的 result
<result name="input">/validation.jsp</result>
III. 如何显示错误消息
> 若使用的是非 simple, 则自动显示错误消息.
> 若使用的是 simple 主题, 则需要 s:fielderror 标签或直接使用 EL 表达式(使用 OGNL)
${fieldErrors.age[0] }OR<s:fielderror fieldName="age"></s:fielderror>
注意: 可以为每一个不同的 action 请求定义其对应的验证文件: ActionClassName-AliasName-validation.xml
不带别名的配置文件: ActionClassName-validation.xml 中的验证规则依然会发生作用. 可以把各个 action 公有的验证规则配置在其中. 但需要注意的是, 只适用于某一个 action 的请求的验证规则就不要这里再配置了.
Struts.xml文件中的配置:
<action name="validation" class="com.iThings.validation.ValiditionAction"
method="execute">
<result name="success">/Success.jsp</result>
<result name="input">/testValidation.jsp</result>
</action>
<action name="validation2" class="com.iThings.validation.ValiditionAction"
method="execute">
<result name="success">/Success.jsp</result>
<result name="input">/testValidation2.jsp</result>
</action>
验证配置文件命名(中间的别名为请求的action方法名)
ValidationAction-validation.xml
ValidationAction-validation-validation.xml
ValidationAction-validation2-validation.xml
2 短路验证: 若对一个字段使用多个验证器, 默认情况下会执行所有的验证. 若希望前面的验证器验证没有通过, 后面的就不再验证, 可以使用短路验证
<!-- 设置短路验证: 若当前验证没有通过, 则不再进行下面的验证 -->
<field-validator type="conversion" short-circuit="true">
<message>^Conversion Error Occurred</message>
</field-validator>
<field-validator type="int">
<param name="min">20</param>
<param name="max">60</param>
<message key="error.int"></message>
</field-validator>
3 修改包文件:若类型转换失败,则不执行输入验证
新建的包名:com.opensymphony.xwork2.interceptor;类名:ConversionErrorInterceptor
当类型转换失败时, 不再执行后续的验证拦截器, 而直接返回 input 的 result
<span style="white-space:pre"> </span>Object action = invocation.getAction();
if (action instanceof ValidationAware) {
ValidationAware va = (ValidationAware) action;
if(va.hasFieldErrors() || va.hasActionErrors()){
return "input";
}
}
***************** 类ConversionErrorInterceptor.java********************
/*
* Copyright 2002-2007,2009 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.opensymphony.xwork2.interceptor;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.ValidationAware;
import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
import com.opensymphony.xwork2.util.ValueStack;
import org.apache.commons.lang3.StringEscapeUtils;
import java.util.HashMap;
import java.util.Map;
/**
* <!-- START SNIPPET: description -->
* ConversionErrorInterceptor adds conversion errors from the ActionContext to the Action's field errors.
*
* <p/>
* This interceptor adds any error found in the {@link ActionContext}'s conversionErrors map as a field error (provided
* that the action implements {@link ValidationAware}). In addition, any field that contains a validation error has its
* original value saved such that any subsequent requests for that value return the original value rather than the value
* in the action. This is important because if the value "abc" is submitted and can't be converted to an int, we want to
* display the original string ("abc") again rather than the int value (likely 0, which would make very little sense to
* the user).
*
*
* <!-- END SNIPPET: description -->
*
* <p/> <u>Interceptor parameters:</u>
*
* <!-- START SNIPPET: parameters -->
*
* <ul>
*
* <li>None</li>
*
* </ul>
*
* <!-- END SNIPPET: parameters -->
*
* <p/> <u>Extending the interceptor:</u>
*
* <p/>
*
* <!-- START SNIPPET: extending -->
*
* Because this interceptor is not web-specific, it abstracts the logic for whether an error should be added. This
* allows for web-specific interceptors to use more complex logic in the {@link #shouldAddError} method for when a value
* has a conversion error but is null or empty or otherwise indicates that the value was never actually entered by the
* user.
*
* <!-- END SNIPPET: extending -->
*
* <p/> <u>Example code:</u>
*
* <pre>
* <!-- START SNIPPET: example -->
* <action name="someAction" class="com.examples.SomeAction">
* <interceptor-ref name="params"/>
* <interceptor-ref name="conversionError"/>
* <result name="success">good_result.ftl</result>
* </action>
* <!-- END SNIPPET: example -->
* </pre>
*
* @author Jason Carreira
*/
public class ConversionErrorInterceptor extends AbstractInterceptor {
public static final String ORIGINAL_PROPERTY_OVERRIDE = "original.property.override";
protected Object getOverrideExpr(ActionInvocation invocation, Object value) {
return escape(value);
}
protected String escape(Object value) {
return "\"" + StringEscapeUtils.escapeJava(String.valueOf(value)) + "\"";
}
@Override
public String intercept(ActionInvocation invocation) throws Exception {
ActionContext invocationContext = invocation.getInvocationContext();
Map<String, Object> conversionErrors = invocationContext.getConversionErrors();
ValueStack stack = invocationContext.getValueStack();
HashMap<Object, Object> fakie = null;
for (Map.Entry<String, Object> entry : conversionErrors.entrySet()) {
String propertyName = entry.getKey();
Object value = entry.getValue();
if (shouldAddError(propertyName, value)) {
String message = XWorkConverter.getConversionErrorMessage(propertyName, stack);
Object action = invocation.getAction();
if (action instanceof ValidationAware) {
ValidationAware va = (ValidationAware) action;
va.addFieldError(propertyName, message);
}
if (fakie == null) {
fakie = new HashMap<Object, Object>();
}
fakie.put(propertyName, getOverrideExpr(invocation, value));
}
}
if (fakie != null) {
// if there were some errors, put the original (fake) values in place right before the result
stack.getContext().put(ORIGINAL_PROPERTY_OVERRIDE, fakie);
invocation.addPreResultListener(new PreResultListener() {
public void beforeResult(ActionInvocation invocation, String resultCode) {
Map<Object, Object> fakie = (Map<Object, Object>) invocation.getInvocationContext().get(ORIGINAL_PROPERTY_OVERRIDE);
if (fakie != null) {
invocation.getStack().setExprOverrides(fakie);
}
}
});
}
Object action = invocation.getAction();
if (action instanceof ValidationAware) {
ValidationAware va = (ValidationAware) action;
if(va.hasFieldErrors() || va.hasActionErrors()){
return "input";
}
}
return invocation.invoke();
}
protected boolean shouldAddError(String propertyName, Object value) {
return true;
}
}
4 非字段验证 expressionValidition
<validator type="expression">
<param name="expression"><![CDATA[password==password2]]></param>
<message>Password is not equals to password2</message>
</validator>
显示非字段验证的错误消息, 使用 s:actionerror 标签: <s:actionerror/>
5 不同的字段使用同样的验证规则, 而且使用同样的响应消息
i18n.properties中的配置:
error.int=${getText(fieldName)} needs to be between ${min} and ${max}
age=age
count=count
6 自定义验证器
I.定义一个验证器的类
> 自定义的验证器都需要实现 Validator.
> 可以选择继承 ValidatorSupport 或 FieldValidatorSupport 类
> 若希望实现一个一般的验证器, 则可以继承 ValidatorSupport
> 若希望实现一个字段验证器, 则可以继承 FieldValidatorSupport
> 具体实现可以参考目前已经有的验证器.
> 若验证程序需要接受一个输入参数, 需要为这个参数增加一个相应的属性
II. 在配置文件中配置验证器
> 默认情况下下, Struts2 会在 类路径的根目录下加载 validators.xml 文件. 在该文件中加载验证器.
该文件的定义方式同默认的验证器的那个配置文件: 位于 com.opensymphony.xwork2.validator.validators 下的 default.xml
> 若类路径下没有指定的验证器, 则从 com.opensymphony.xwork2.validator.validators 下的 default.xml 中的验证器加载
III. 实例
1)实现Validator
*************** IDCard.java *******************
package com.iThings.validation;
public class IDCard {
final int[] wi = { 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2, 1 };
final int[] vi = { 1, 0, 'X', 9, 8, 7, 6, 5, 4, 3, 2 };
private int[] ai = new int[18];
public IDCard() {}
public boolean Verify(String idcard) {
if (idcard.length() == 15) {
idcard = uptoeighteen(idcard);
}
if (idcard.length() != 18) {
return false;
}
String verify = idcard.substring(17, 18);
if (verify.equals(getVerify(idcard))) {
return true;
}
return false;
}
public String getVerify(String eightcardid) {
int remaining = 0;
if (eightcardid.length() == 18) {
eightcardid = eightcardid.substring(0, 17);
}
if (eightcardid.length() == 17) {
int sum = 0;
for (int i = 0; i < 17; i++) {
String k = eightcardid.substring(i, i + 1);
ai[i] = Integer.parseInt(k);
}
for (int i = 0; i < 17; i++) {
sum = sum + wi[i] * ai[i];
}
remaining = sum % 11;
}
return remaining == 2 ? "X" : String.valueOf(vi[remaining]);
}
public String uptoeighteen(String fifteencardid) {
String eightcardid = fifteencardid.substring(0, 6);
eightcardid = eightcardid + "19";
eightcardid = eightcardid + fifteencardid.substring(6, 15);
eightcardid = eightcardid + getVerify(eightcardid);
return eightcardid;
}
public static void main(String[] args) {
String idcard1 = "350211197607142059";
String idcard2 = "350211197607442059";
IDCard idcard = new IDCard();
System.out.println(idcard.Verify(idcard1));
System.out.println(idcard.Verify(idcard2));
}
}
*************** IDCardValidator.java *******************
package com.iThings.validation;
import com.opensymphony.xwork2.validator.ValidationException;
import com.opensymphony.xwork2.validator.validators.FieldValidatorSupport;
public class IDCardValidator extends FieldValidatorSupport {
@Override
public void validate(Object object) throws ValidationException {
//1. 获取字段的名字和值
String fieldName = getFieldName();
Object value = this.getFieldValue(fieldName, object);
//2. 验证
IDCard idCard = new IDCard();
boolean result = idCard.Verify((String)value);
//3. 若验证失败, 则 ...
if(!result){
addFieldError(fieldName, object);
}
}
}
2)在src目录下新建validators.xml文件。配置验证器
*************** validators.xml *******************
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE validators PUBLIC
"-//Apache Struts//XWork Validator Definition 1.0//EN"
"http://struts.apache.org/dtds/xwork-validator-definition-1.0.dtd">
<!-- START SNIPPET: validators-default -->
<validators>
<validator name="idcard" class="com.atguigu.struts2.validation.app.IDCardValidator"/>
</validators>
3)在验证配置文件中,使用该验证器(同一般验证器一样)
<field name="idCard">
<field-validator type="idcard">
<message>It is not a idcard</message>
</field-validator>
</field>