Struts2-13 输入验证

  一个健壮的WEB应用程序必须确保用户的输入是合法有效的;Struts2即提供了一些基于XWork Validation Framework的内建验证程序,使得输入验证不需要编程,直接在XML配置文件中声明(待验证字段、验证规则、验证出错消息)即可。
  
  Struts2提供声明式验证和编程式验证两种方式,常采用声明式验证程序,其主要需要声明如下内容:

  • 对哪个Action类或Model类对象的哪个字段进行验证;
  • 采用哪种验证规则进行验证;
  • 若验证失败,则转向何处、显示什么错误信息。

一、Struts2内建验证程序

  1. required:验证某给定字段的值不是空值null;
  2. requiredstring: 验证某给定字段的值既不是空值 null,也不是空白(trim参数默认为true,可以在验证前先剔除前后空格);
  3. stringlength:验证一个非空的字段值是不是有足够的长度;
  4. date:验证某给定日期字段的值是否落在一个给定的范围内;
  5. email:验证某给定字符串是否是一个合法的E-mail地址;
  6. url:验证某给定字符串是否是一个合法的url;
  7. regex:验证某给定字段的值是否与一个给定的正则表达式模式相匹配(expresssion、caseSensitive、trim);
  8. int:验证某给定整数字段值是否在某一个范围内;
  9. conversion:验证对给定Action属性进行类型转换是否会出错,其可以在默认类型转换消息基础上添加一条自定义的消息;
  10. expression:验证某给定字段是否满足一个OGNL表达式,其是非字段验证程序,验证失败时将生成action错误(expression参数指用来验证的OGNL表达式);
  11. fieldexpression:验证某给定字段是否满足一个OGNL表达式,其是字段验证程序,验证失败时将生成字段错误(expression参数指用来验证的OGNL表达式);

二、声明式验证

  声明式验证程序分为字段验证和非字段验证两类,字段验证主要判断某字段属性的输入是否有效,而非字段验证主要是针对多个字段输入值之间的逻辑关系进行校验,如密码再次输入确认。

2.1 基于字段的声明式验证

(1)基于字段的声明式验证程序的具体实现步骤

  1. 确定验证字段:确定哪个Action类或Model类对象的哪个字段需要验证;
  2. 编写验证程序配置文件:若一个Action类的多个action请求使用相同的验证规则,则其文件名为ActionClassName-validation.xml,而不同则为ActionClassName-User_create-validation.xml
  3. 确定验证失败时的响应页面:在struts.xml文件中定义<result name=“input”> 元素,用于指定转向页面。

(2)编写验证程序配置文件

  • 将struts-2.3.15.3\apps\struts2-blank\WEB-INF\classes\example下的Login-validation.xml文件复制到当前Action类文件所在路径下;
  • 修改配置文件名为ActionClassName-validation.xml;
  • 编写验证规则: 参见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>

(3)显示验证程序错误消息

<!-- 非simple主题自动显示错误消息 -->
<s:form action="testValidation">
    <s:textfield name="age" label="Age"></s:textfield>
    <s:submit></s:submit>
</s:form>

<br><hr><br>

<!-- simple主题需要使用s:fielderror标签或EL货OGNL表达式来显示错误消息 -->
<s:form action="testValidation2" theme="simple">
    Age: <s:textfield name="age" label="Age"></s:textfield>
    <br>
    ${fieldErrors.age[0] }
    <br>
    <s:fielderror name="age"></s:fielderror>
    <s:submit></s:submit>
</s:form>
2.2 声明式验证框架的原理
  • Struts2 默认拦截器栈中提供了validation拦截器,负责加载和执行已注册的验证程序;
  • 每个具体的验证程序都会对应一个具体的验证器;
  • 配置文件default.xml(位于com.opensymphony.xwork2.validator.validators下)将验证规则名称和验证器关联起来,实际验证的是验证器。
    这里写图片描述
2.3 短路验证
<!-- 设置短路验证:若当前验证没有通过,则不再进行后续的验证 -->
        <field-validator type="conversion" short-circuit="true">
            <message>Conversion Error Occurred.</message>
        </field-validator>

        <field-validator type="int">
            <param name="min">0</param>
            <param name="max">100</param>
            <message>Age needs to be between ${min} and ${max}.</message>
        </field-validator>

  注意:若类型转换失败,默认情况下仍会执行后续的输入验证拦截器。可通过修改ConversionErrorInterceptor源代码的方式使
类型转换失败时,直接返回input的result,即不再执行后续的验证拦截器。

// 创建相同的包 -> 新建相同的类 -> 复制源代码 -> 修改即可
Object action = invocation.getAction();
        if (action instanceof ValidationAware) {
            ValidationAware va = (ValidationAware) action;

            if(va.hasFieldErrors() || va.hasActionErrors()) {
                return "input";
            }
        }
2.4 非字段验证

  非字段验证不是针对单一字段的验证,可以通过<s:actionerror/>标签显示错误消息;非字段验证的配置实例如下:

<!--非字段验证的配置:实现密码再次确认的功能-->
<validators>
    <validator type="expression">
        <param name="expression"><![CDATA[password1==password2]]></param>
        <message>password1 != password2</message>
    </validator>
</validators>
2.5 字段验证 VS 非字段验证
  • 字段验证:字段优先,可以为一个字段配置多个验证规则;
  • 非字段验证:验证规则优先;
  • 大部分验证规则支持两种验证器,但个别的验证规则只能使用非字段验证,例如表达式验证。
2.6 错误消息的重用性

  问题:多个字段使用同样的验证规则,可否使用同一条验证消息 ?
  解决:利用国际化资源配置文件和fieldName特殊属性结合来实现,英文资源文件内容如下所示:

error.int=${getText(fieldName)} needs to be between ${min} and ${max}
age=Age
count=Count

三、自定义验证器

3.1 自定义验证器类
  • 自定义验证器类需要实现Validator接口,可选择继承ValidatorSupport或 FieldValidatorSupport类;
  • 自定义一般验证器类可继承ValidatorSupport,自定义字段验证器类可继承FieldValidatorSupport类;
  • 具体实现可以参考目前已经有的验证器,若验证程序需要接受输入参数,则需要为该参数增加对应的属性和setter()方法。
3.2 配置自定义验证器
  • 默认情况下, Struts2会在类路径的根目录下加载validators.xml文件,在该文件中加载验证器;
  • 若加载失败,则从com.opensymphony.xwork2.validator.validators下default.xml中加载验证器;
  • 自定义验证器配置同默认配置文件,配置完毕即可使用。
3.3 自定义18位身份证验证器

  具体代码见附录所示。


四、编码式验证

  Struts2提供Validateable接口,可以使Action类实现该接口以提供编程验证功能,常选择继承于实现了Validateable 接口的ActionSupport 类。

@Override
public void validate() {
    if(name == null || name.trim().equals("")) {
        addFieldError("name", getText("name.null"));
    }
}

附录:核心程序代码

这里写图片描述

IDCard.java:

package com.qiaobc.struts.action;

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.qiaobc.struts.action;

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);
        }   
    }
}

TestValidationAction.java:

package com.qiaobc.struts.action;

import com.opensymphony.xwork2.ActionSupport;

public class TestValidationAction extends ActionSupport {

    private static final long serialVersionUID = 1L;

    private Integer age;

    private String password1;

    private String password2;

    private String idcard;

    public String getPassword1() {
        return password1;
    }

    public String getIdcard() {
        return idcard;
    }

    public void setIdcard(String idcard) {
        this.idcard = idcard;
    }

    public void setPassword1(String password1) {
        this.password1 = password1;
    }

    public String getPassword2() {
        return password2;
    }

    public void setPassword2(String password2) {
        this.password2 = password2;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String execute() throws Exception {

        System.out.println("age:" + age);
        System.out.println("password1:" + password1);
        System.out.println("password2:" + password2);

        return SUCCESS;
    }
}

TestValidationAction-testValidation-validation.xml:

<!DOCTYPE validators PUBLIC
        "-//Apache Struts//XWork Validator 1.0.2//EN"
        "http://struts.apache.org/dtds/xwork-validator-1.0.2.dtd">

<validators>

    <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-validator type="conversion">
            <message>Conversion Error Occurred.</message>
        </field-validator>
    </field>

    <field name="idcard">
        <field-validator type="idcard">
            <message>It's not a ID Card!</message>
        </field-validator>
    </field>

    <validator type="expression">
        <param name="expression"><![CDATA[password1==password2]]></param>
        <message>password1 != password2</message>
    </validator>

</validators>

TestValidationAction-testValidation2-validation.xml:

<!DOCTYPE validators PUBLIC
        "-//Apache Struts//XWork Validator 1.0.2//EN"
        "http://struts.apache.org/dtds/xwork-validator-1.0.2.dtd">

<validators>

    <field name="age">
        <!-- 设置短路验证:若当前验证没有通过,则不再进行下面的验证 -->
        <field-validator type="conversion" short-circuit="true">
            <message>Conversion Error Occurred.</message>
        </field-validator>

        <field-validator type="int">
            <param name="min">0</param>
            <param name="max">100</param>
            <message>Age needs to be between ${min} and ${max}.</message>
        </field-validator>
    </field>

</validators>

struts.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
    "http://struts.apache.org/dtds/struts-2.3.dtd">
<struts>
    <!-- 配置全局范围内的国际化资源文件 -->
    <constant name="struts.custom.i18n.resources" value="i18n"></constant>

    <package name="validation" namespace="/" extends="struts-default">

        <action name="testValidation" class="com.qiaobc.struts.action.TestValidationAction">
            <result>/success.jsp</result>

            <!-- 配置输入验证失败时的转向页面 -->
            <result name="input">/validation.jsp</result>
        </action>

        <action name="testValidation2" class="com.qiaobc.struts.action.TestValidationAction">
            <result>/success.jsp</result>

            <!-- 配置输入验证失败时的转向页面 -->
            <result name="input">/validation.jsp</result>
        </action>

    </package>
</struts>

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">
<validators>
    <!-- 配置自定义拦截器 -->
    <validator name="idcard" class="com.qiaobc.struts.action.IDCardValidator"/>
</validators>

validation.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

    <s:debug></s:debug>

    <!-- 非simple主题自动显示错误消息 -->
    <s:form action="testValidation">
        <s:textfield name="age" label="Age"></s:textfield>
        <s:password name="password1" label="password1"></s:password>
        <s:password name="password2" label="password2"></s:password>
        <s:actionerror/>
        <s:textfield name="idcard" label="IDCard"></s:textfield>
        <s:submit></s:submit>
    </s:form>

</body>
</html>

validation2.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

    <s:debug></s:debug>

    <!-- simple主题需要使用s:fielderror标签或EL货OGNL表达式来显示错误消息 -->
    <s:form action="testValidation2" theme="simple">
        Age: <s:textfield name="age" label="Age"></s:textfield>
        <br><br>
        <s:fielderror name="age"></s:fielderror>
        <s:submit></s:submit>
    </s:form>

</body>
</html>

ConversionErrorInterceptor.java:

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;

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;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值