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"));
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章