在struts2中,若对一个字段使用多个验证器, 默认情况下会执行所有的验证. 若希望前面的验证器验证没有通过, 后面的就不再验证, 可以使用短路验证。
那就不得不说conversion验证器,当转换出现异常,此验证器就会报错。写一个简单的例子:
(1)Action类 只需一个年龄(int类型)字段
import com.opensymphony.xwork2.ActionSupport;
public class ShortCircuitAction extends ActionSupport {
private static final long serialVersionUID = 1L;
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String execute() throws Exception {
return SUCCESS;
}
}
(2)配置验证文件 要求年龄在20-30之间
<?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="age">
<field-validator type="int">
<param name="min">20</param>
<param name="max">30</param>
<message>年龄必须在${min}与${max}之间</message>
</field-validator>
</field>
</validators>
(3)在struts.xml文件中配置和界面代码就省略,都是最基本的配置,只需看验证结果:
可以看出,在输入的是非int类型的字符串的时候,struts2框架会报字段不合法的错误,但是后面的年龄验证还是会执行,如果不想让它继续执行,就要用到上述的短路验证,就是配置conversion验证器,那么修改验证配置文件:
<?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="age">
<!-- 短路验证:若当前验证出错将不会继续进行下一步验证 -->
<!-- conversion: 检查对给定 Action 属性进行的类型转换是否会导致一个转换错误.
该验证程序还可以在默认的类型转换消息的基础上添加一条自定义的消息
short-circuit是是否短路的属性,默认为false -->
<field-validator type="conversion" short-circuit="true">
<message>转换出现错误</message>
</field-validator>
<field-validator type="int">
<param name="min">20</param>
<param name="max">30</param>
<message>年龄必须在${min}与${max}之间</message>
</field-validator>
</field>
</validators>
再看一下结果:
当出现转换错误的时候不会在继续执行之后的验证,这里值得注意的一点是在conversion验证器中的short-circurt默认属性是false,如果不修改,就相当于没有任何作用,只多了一条错误信息,结果如图:
好了,例子到这就完了,可是即使这样还是会出现两个错误,一个框架自带的字段类型错误,一个是短路验证的错误,有么有更好的办法,是其只出现一条呢?
在我理解来,要到到这个效果,短路验证是做不到的,因为若类型转换失败, 默认情况下还会执行后面的拦截器, 还会进行验证.我们 可以通过修改ConversionErrorInterceptor 源代码的方式使当类型转换失败时, 不再执行后续的验证拦截器, 而直接返回值为“ input” 的 result
先来看修改过后的源码:
/*
* 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();//获取当前action
if (action instanceof ValidationAware) {//判断Action类是否实现了ValidationAware接口
ValidationAware va = (ValidationAware) action;
//如果含有错误就返回result不在继续执行
if(va.hasFieldErrors() || va.hasActionErrors())
return "input";
}
return invocation.invoke();
}
protected boolean shouldAddError(String propertyName, Object value) {
return true;
}
}
因为是源码,所以才这么多,其实只加了几句代码而已,上面已经标注并且解释过了。在struts2中,并没有专门的转换拦截器,转换是发生在params拦截器执行的时候,但是如果出错了就会调用conversionError拦截器,在struts2框架中对应的是StrutsConversionErrorInterceptor类,但是这里重写的是他的父类ConversionErrorInterceptor。在ConversionErrorInterceptor拦截器中,在调用invoke()方法后才会调用下一个拦截器,直到最后返回一个result对应的物理视图,所以,在调用invoke()方法之前判断有没有转换错误,如果有就直接返回值为input的result对应的物理视图。需要注意的一点是包名一定要和源码一样。
再运行后的结果:
到这里就好了,已经能很好的解决这个问题了。