Struts2学习笔记 | 输入验证

Struts2的输入验证

  • 基于XWork Validation Frameword的声明式验证
    Struts2提供了一些基于XWork Validation Frameword的内建验证程序,使用这些验证程序不需要编程,只要在一个XML文件里对验证程序应该如何工作作出声明就可以了。
  • 编程验证
    通过编写代码来验证用户输入

声明式验证

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

需要验证的内容包括:
哪些字段需要验证?
使用什么验证规则?
在验证失败时应该把什么样的出错消息发送到浏览器端?

验证程序配置文件的命名规则

  • 若一个Action类的多个action使用同样的验证规则
    命名为ActionClassName-validation.xml,但是如果只适用于某一个action的请求的验证规则就不要这里再配置了。
  • 若一个Action类的多个action使用不同的验证规则
    为每一个不同的action请求定义其对应的验证文件。
    命名为ActionClassName-alias-validation.xml,例如UserAction-User_create-validation.xml

编写一个声明式验证的demo

  • 先明确对哪一个Action的哪一个字段进行验证:age字段

  • 编写配置文件ActionName-validation.xml
    在此新建TestValidationAction-validation.xml文件并编写验证规则,代码如下:

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE validators PUBLIC "-//Apache Struts//XWork Validator 1.0//EN" "http://struts.apache.org/dtds/xwork-validator-1.0.dtd">
<validators>
    <!-- 针对age属性进行验证 -->
    <field name="age">
        <field-validator type="int">
            <param name="min">20</param>
            <param name="max">50</param>
            <!-- 显示的错误消息-->
            <message>年龄不合法....</message>
        </field-validator>
    </field>
</validators>

  • 配置result节点,当验证失败时将会转向name=inputresult节点。
    因此在struts.xml文件中添加:
<action name="testValidation" class="com.cerr.struts2.validation.app.TestValidationAction" method="execute">
      <result>/success.jsp</result>
      <!-- 若验证失败则转向 input-->
      <result name="input">/validation.jsp</result>
</action>
  • 关于显示错误消息
  • 如果使用的是非simple主题,则自动显示错误消息
  • 如果使用的是simple主题,则需要自己编写代码显示错误消息
    可以使用<s:fielderror name="要验证的字段名"></s:fielderror>或者使用el表达式
    在此示例:<s:fielderror name="age"></s:fielderror>${fieldErrors.age[0]}
  • 错误消息可以国际化。
    可以使用<message key="error.int"></message>,再在国际化资源文件中加入一个键值对:error.int=age need to be between ${min} and ${max}
  • 声明式验证框架的原理
    Struts2默认的拦截器栈中提供了一个validation拦截器
    每个具体的验证规则都会对应具体的验证器,有一个配置文件把验证规则的名称和验证器关联起来了。实际起作用的是验证器。该文件位于com.opensymphony.xwork2.validator.validators下的default.xml
    配置文件与验证器属性其实是有一定的对应关系的。

短路验证器(conversion)

我们有时候要对一个字段进行多种验证,如果按前面那种方式在配置文件中一一配置的话,那么所有的验证都会一一执行,但是有时候我们如果在第一次验证不过的时候就不验证后面的了。这个时候就需要用到短路验证器了。

  • <valiadtor .../>元素和<field-validator... />元素可以指定一个可选的short-circuit属性,该属性指定该验证器是否是短路验证器,默认值为false

  • 对同一个字段内的多个验证器,如果一个短路验证器验证失败,其他验证器不会继续校验。

举个例子:对于前面的age字段,我们要验证其类型转换是否错误。
TestValidationAction-validation.xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE validators PUBLIC "-//Apache Struts//XWork Validator 1.0.2//EN" "http://struts.apache.org/dtds/xwork-validator-1.0.2.dtd">
<validators>
    
    <!-- 针对age属性进行验证 -->
    <field name="age">
        <field-validator type="conversion">
            <message>类型转换出错</message>
        </field-validator>
        <field-validator type="int">
            <param name="min">20</param>
            <param name="max">50</param>
            <message>年龄不合法....</message>
        </field-validator>
    </field>
</validators>

如果单纯这样配置的话,那么它如果在第一个验证出错的话,还会验证第二个合法验证。
如果将<field-validator type="conversion">改成<field-validator type="conversion" short-circuit="true">,就会变成一个短路验证,如果该验证出错,后面的验证不会继续。

这个Demo更改之后,其实还有一点问题,虽然这个已经是短路验证了。如果在该字段验证出错的话就不会执行下一个验证。但是呢,我们在前面学习类型转换的时候说过类型转换出错的话它会有错误消息,那么如果使在前面类型转换出错之后不会进行其它的验证呢,阅读源码后发现不管出不出错,其都会去执行下一个拦截器。因此我们要修改源代码。

首先在自己的项目目录下新建一个com.opensymphony.xwork2.interceptor包,然后新建ConversionErrorInterceptor类,然后复制源代码粘贴到我们自己新建的这个类。在要返回之前进行判断类型转换是否失败,如果失败则不再执行后续的拦截器,而直接返回inputresult
添加的代码如下:

        Object action = invocation.getAction();
        if (action instanceof ValidationAware) {
            ValidationAware va = (ValidationAware)action;
            //如果验证有错
            if(va.hasFieldErrors() || va.hasActionErrors()){
                return "input";
            }
        }

添加后完整的源代码如下:


package com.opensymphony.xwork2.interceptor;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
import com.opensymphony.xwork2.util.ValueStack;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.lang3.StringEscapeUtils;

public class ConversionErrorInterceptor extends MethodFilterInterceptor {
    public static final String ORIGINAL_PROPERTY_OVERRIDE = "original.property.override";

    public ConversionErrorInterceptor() {
    }

    protected Object getOverrideExpr(ActionInvocation invocation, Object value) {
        return this.escape(value);
    }

    protected String escape(Object value) {
        return "\"" + StringEscapeUtils.escapeJava(String.valueOf(value)) + "\"";
    }

    public String doIntercept(ActionInvocation invocation) throws Exception {
        ActionContext invocationContext = invocation.getInvocationContext();
        Map<String, Object> conversionErrors = invocationContext.getConversionErrors();
        ValueStack stack = invocationContext.getValueStack();
        HashMap<Object, Object> fakie = null;
        Iterator i$ = conversionErrors.entrySet().iterator();

        while(i$.hasNext()) {
            Entry<String, Object> entry = (Entry)i$.next();
            String propertyName = (String)entry.getKey();
            Object value = entry.getValue();
            if (this.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();
                }

                fakie.put(propertyName, this.getOverrideExpr(invocation, value));
            }
        }

        if (fakie != null) {
            stack.getContext().put("original.property.override", fakie);
            invocation.addPreResultListener(new PreResultListener() {
                public void beforeResult(ActionInvocation invocation, String resultCode) {
                    Map<Object, Object> fakie = (Map)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;
    }
}


非字段验证(expression)

非字段验证:不是针对某一个字段的验证。

例子:验证两次密码输入是否一样
在配置文件TestValidationAction-validation.xml中配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE validators PUBLIC "-//Apache Struts//XWork Validator 1.0.2//EN" "http://struts.apache.org/dtds/xwork-validator-1.0.2.dtd">
<validators>
        <!-- 测试非字段验证 -->
        <field-validator type="expression">
            <param name="expression"><![CDATA[password==password2]]></param>
            <message>Password is not equals to password2</message>
        </field-validator>
    </field>
</validators>

validation.jsp文件:

<%@ taglib prefix="s" uri="/struts-tags" %>
<%--
  Created by IntelliJ IDEA.
  User: 白菜
  Date: 2019/8/6
  Time: 15:07
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <s:debug></s:debug>  
    <s:form action="testValidation.action">
        <s:textfield name="age" label="age"></s:textfield>
        <s:password name="password" label="Password"></s:password>
        <s:password name="password2" label="Password2"></s:password>
        <s:submit></s:submit>
    </s:form>
</body>
</html>

TestValidationAction文件如下:

package com.cerr.struts2.validation.app;

import com.opensymphony.xwork2.ActionSupport;

public class TestValidationAction extends ActionSupport {
    private int age;
    private String password;
    private String password2;

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getPassword2() {
        return password2;
    }

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

    public int getAge() {
        return age;
    }

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

    @Override
    public String execute() throws Exception {
        System.out.println("age"+age);
        return SUCCESS;
    }
}

验证错误后,查看值栈


发现其错误消息是在actionError属性中,因此需要使用<s:actionerror></s:actionerror>标签

加上错误消息显示之后的完整jsp代码如下:

<%@ taglib prefix="s" uri="/struts-tags" %>
<%--
  Created by IntelliJ IDEA.
  User: 白菜
  Date: 2019/8/6
  Time: 15:07
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <s:debug></s:debug>
    <br><br>
    <s:actionerror></s:actionerror>
    <s:form action="testValidation.action">
        <s:textfield name="age" label="age"></s:textfield>
        <s:password name="password" label="Password"></s:password>
        <s:password name="password2" label="Password2"></s:password>
        <s:submit></s:submit>
    </s:form>
</body>
</html>

非字段验证与字段验证区别

  • 对于字段验证,是字段优先。可以为一个字段配置多个验证规则

  • 对于非字段验证,是验证规则优先。

  • 大多数的验证规则都是支持这两种的,但是个别的验证规则只能使用非字段验证,例如表达式验证(上述的例子)。


错误消息的重用性

对于使用同样的验证规则的不同字段,我们要怎么使他们使用同样的响应消息呢?
<field-validator />的子标签<param></param>name属性有个值为fieldName,可以设置属性名,设置后在验证的错误消息中使用${fieldName}就可以实现字段替换了。即不同的字段使用同样的响应消息。

举个例子:count字段和age字段使用同一个错误消息
i18n.properties文件:
error.int=${fieldName} needs to be between ${min} and ${max}
jsp文件:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <s:debug></s:debug>
    <br><br>
    <s:actionerror></s:actionerror>
    <s:form action="testValidation.action">
        <s:textfield name="age" label="age"></s:textfield>
        <s:textfield name="count" label="Count"></s:textfield>
        <s:submit></s:submit>
    </s:form>
</body>
</html>

配置文件TestValidationAction-validation.xml中:

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE validators PUBLIC "-//Apache Struts//XWork Validator 1.0.2//EN" "http://struts.apache.org/dtds/xwork-validator-1.0.2.dtd">
<validators>
    
    <!-- 针对age属性进行验证 -->
    <field name="age">
        <field-validator type="int">
            <param name="fieldName">age</param>
            <param name="min">20</param>
            <param name="max">50</param>
            <message key="error.int"></message>
        </field-validator>
    </field>
    <!-- 针对count属性-->
    <field name="count">
        <field-validator type="int">
            <param name="fieldName">count</param>
            <param name="min">1</param>
            <param name="max">10</param>
            <message key="error.int"></message>
        </field-validator>
    </field>
</validators>

自定义验证器

  • 定义一个验证器的方法
    自定义验证器需要实现Validator接口,可以选择继承其实现类ValidatorSupportFieldValidatorSupport,若希望实现一个一般的验证器,则可以继承ValidatorSupport,若希望实现一个字段验证器,则可以继承FieldValidatorSupport若验证程序需要接受一个输入参数,则需要在Action类中为其添加一个相应的属性

  • 在配置文件中配置验证器
    默认情况下,Struts2会在类路径的根目录下加载validators.xml文件,在该文件中加载验证器。该文件的定义方式同位于com.opensymphony.xwork2.validator.validators下的default.xml
    的默认的验证器的配置文件。
    若类路径下没有指定的验证器,则从com.opensymphony.xwork2.validator.validators下的default.xml中的验证器加载。

  • 使用
    和目前的验证器一样。

  • 示例:自定义一个18位身份证的验证器
    先编写自定义验证器IDCardValidator类:

package com.cerr.struts2.validation.app;

import com.opensymphony.xwork2.validator.ValidationException;
import com.opensymphony.xwork2.validator.validators.FieldValidatorSupport;

public class IDCardValidator extends FieldValidatorSupport {
    @Override
    public void validate(Object o) throws ValidationException {
        //获取字段
        String fieldName = getFieldName();
        Object value = this.getFieldValue(fieldName,o);
        //验证
        IDCard idCard = new IDCard();
        boolean result = idCard.Verify((String) value);
        //验证失败,添加错误消息
        if(!result){
            addFieldError(fieldName,o);
        }
    }
}

再在根目录下新建配置文件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.cerr.struts2.validation.app.IDCardValidator"></validator>
</validators>

这样就定义好了一个自定义验证器,接下来的步骤就是跟我们之前使用验证器一样。
TestValidationAction-validation.xml文件中添加:

<field name="idCard">
        <field-validator type="idcard">
            <message>这不是一个身份证号码</message>
        </field-validator>
    </field>

在Action类中添加icCard属性并且添加对于的get/set方法,添加后的完整代码如下:

package com.cerr.struts2.validation.app;

import com.opensymphony.xwork2.ActionSupport;

public class TestValidationAction extends ActionSupport {
    private int age;
    private String password;
    private String password2;
    private int count;
    private String idCard;

    public String getIdCard() {
        System.out.println(idCard);
        return idCard;
    }

    public void setIdCard(String idCard) {
        this.idCard = idCard;
        System.out.println(idCard);
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getPassword2() {
        return password2;
    }

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

    public int getAge() {
        return age;
    }

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

    @Override
    public String execute() throws Exception {
        System.out.println("age"+age);
        return SUCCESS;
    }
}

jsp文件补充后如下:

<%@ taglib prefix="s" uri="/struts-tags" %>
<%--
  Created by IntelliJ IDEA.
  User: 白菜
  Date: 2019/8/6
  Time: 15:07
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <s:debug></s:debug>
    <br><br>
    <s:actionerror></s:actionerror>
    <s:form action="testValidation.action">
        <s:textfield name="age" label="age"></s:textfield>
        <s:password name="password" label="Password"></s:password>
        <s:password name="password2" label="Password2"></s:password>
        <s:textfield name="count" label="Count"></s:textfield>
        <!-- 身份证验证-->
        <s:textfield name="idCard" label="idcard"></s:textfield>
        <s:submit></s:submit>
    </s:form>
</body>
</html>

在该自定义验证器中验证的方法类IDCard.java代码如下:

package com.cerr.struts2.validation.app;

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

}


编程验证

Struts2提供了一个Validateable接口,可以使Action类实现这个接口以提供编程验证功能。

  • ActionSupport类以及实现了Validateable接口。

  • 示例

public void validate(){
    if(name == null || name.trim.equals("")){
         addFieldError("name",getText("name.null"));
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章