Spring4新特性——集成Bean Validation 1.1(JSR-349)到SpringMVC

Spring4新特性——泛型限定式依賴注入javascript

Spring4新特性——核心容器的其餘改進前端

Spring4新特性——Web開發的加強java

Spring4新特性——集成Bean Validation 1.1(JSR-349)到SpringMVC git

Spring4新特性——Groovy Bean定義DSLgithub

Spring4新特性——更好的Java泛型操做API web

Spring4新特性——JSR310日期API的支持spring

Spring4新特性——註解、腳本、任務、MVC等其餘特性改進 編程

 

在以前的《跟我學SpringMVC》中的《第七章 註解式控制器的數據驗證、類型轉換及格式化》中已經介紹過SpringMVC集成Bean Validation 1.0(JSR-303),目前Bean Validation最新版本是Bean Validation 1.1(JSR-349),新特性能夠到官網查看,筆者最喜歡的兩個特性是:跨參數驗證(好比密碼和確認密碼的驗證)和支持在消息中使用EL表達式,其餘的還有如方法參數/返回值驗證、CDI和依賴注入、分組轉換等。對於方法參數/返回值驗證,你們能夠參閱《Spring3.1 對Bean Validation規範的新支持(方法級別驗證) 》。api

 

Bean Validation 1.1當前實現是Hibernate validator 5,且spring4才支持。接下來咱們從如下幾個方法講解Bean Validation 1.1,固然不必定是新特性:瀏覽器

  1.  集成Bean Validation 1.1到SpringMVC

  2.  分組驗證、分組順序及級聯驗證

  3.  消息中使用EL表達式

  4.  方法參數/返回值驗證

  5.  自定義驗證規則

  6.  類級別驗證器

  7.  腳本驗證器

  8.  cross-parameter,跨參數驗證

  9. 混合類級別驗證器和跨參數驗證器

  10. 組合多個驗證註解

  11. 本地化

由於大多數時候驗證都配合web框架使用,並且不少朋友都諮詢過如分組/跨參數驗證,因此本文介紹下這些,且是和SpringMVC框架集成的例子,其餘使用方式(好比集成到JPA中)能夠參考其官方文檔:

規範:http://beanvalidation.org/1.1/spec/

hibernate validator文檔:http://hibernate.org/validator/ 

 

 一、集成Bean Validation 1.1到SpringMVC

1.一、項目搭建

首先添加hibernate validator 5依賴:

<dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>5.0.2.Final</version>
        </dependency>

若是想在消息中使用EL表達式,請確保EL表達式版本是 2.2或以上,如使用Tomcat6,請到Tomcat7中拷貝相應的EL jar包到Tomcat6中。

<dependency>
            <groupId>javax.el</groupId>
            <artifactId>javax.el-api</artifactId>
            <version>2.2.4</version>
            <scope>provided</scope>
        </dependency>

請確保您使用的Web容器有相應版本的el jar包。

 

對於其餘POM依賴請下載附件中的項目參考。

 

1.二、Spring MVC配置文件(spring-mvc.xml):

<!-- 指定本身定義的validator -->
    <mvc:annotation-driven validator="validator"/>

    <!-- 如下 validator  ConversionService 在使用 mvc:annotation-driven 會 自動註冊-->
    <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
        <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
        <!-- 若是不加默認到 使用classpath下的 ValidationMessages.properties -->
        <property name="validationMessageSource" ref="messageSource"/>
    </bean>

    <!-- 國際化的消息資源文件(本系統中主要用於顯示/錯誤消息定製) -->
    <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <!-- 在web環境中必定要定位到classpath 不然默認到當前web應用下找  -->
                <value>classpath:messages</value>
                <value>classpath:org/hibernate/validator/ValidationMessages</value>
            </list>
        </property>
        <property name="useCodeAsDefaultMessage" value="false"/>
        <property name="defaultEncoding" value="UTF-8"/>
        <property name="cacheSeconds" value="60"/>
    </bean>

此處主要把bean validation的消息查找委託給spring的messageSource。

 

1.三、實體驗證註解:

public class User implements Serializable {
    @NotNull(message = "{user.id.null}")
    private Long id;

    @NotEmpty(message = "{user.name.null}")
    @Length(min = 5, max = 20, message = "{user.name.length.illegal}")
    @Pattern(regexp = "[a-zA-Z]{5,20}", message = "{user.name.illegal}")
    private String name;

    @NotNull(message = "{user.password.null}")
    private String password;
}

對於驗證規則能夠參考官方文檔,或者《第七章 註解式控制器的數據驗證、類型轉換及格式化》。

 

1.四、錯誤消息文件messages.properties:

user.id.null=用戶編號不能爲空
user.name.null=用戶名不能爲空
user.name.length.illegal=用戶名長度必須在5到20之間
user.name.illegal=用戶名必須是字母
user.password.null=密碼不能爲空

 

1.五、控制器

@Controller
public class UserController {

    @RequestMapping("/save")
    public String save(@Valid User user, BindingResult result) {
        if(result.hasErrors()) {
            return "error";
        }
        return "success";
    }
}

 

1.六、錯誤頁面:

<spring:hasBindErrors name="user">
    <c:if test="${errors.fieldErrorCount > 0}">
        字段錯誤:<br/>
        <c:forEach items="${errors.fieldErrors}" var="error">
            <spring:message var="message" code="${error.code}" arguments="${error.arguments}" text="${error.defaultMessage}"/>
            ${error.field}------${message}<br/>
        </c:forEach>
    </c:if>

    <c:if test="${errors.globalErrorCount > 0}">
        全局錯誤:<br/>
        <c:forEach items="${errors.globalErrors}" var="error">
            <spring:message var="message" code="${error.code}" arguments="${error.arguments}" text="${error.defaultMessage}"/>
            <c:if test="${not empty message}">
                ${message}<br/>
            </c:if>
        </c:forEach>
    </c:if>
</spring:hasBindErrors>

 

你們之後能夠根據這個作通用的錯誤消息顯示規則。好比我前端頁面使用validationEngine顯示錯誤消息,那麼我能夠定義一個tag來通用化錯誤消息的顯示:showFieldError.tag。  

 

1.七、測試

輸入如:http://localhost:9080/spring4/save?name=123 , 咱們獲得以下錯誤:

name------用戶名必須是字母
name------用戶名長度必須在5到20之間
password------密碼不能爲空
id------用戶編號不能爲空

 

基本的集成就完成了。

 

如上測試有幾個小問題:

一、錯誤消息順序,你們能夠看到name的錯誤消息順序不是按照書寫順序的,即不肯定;

二、我想顯示如:用戶名【zhangsan】必須在5到20之間;其中咱們想動態顯示:用戶名、min,max;而不是寫死了;

三、我想在修改的時候只驗證用戶名,其餘的不驗證怎麼辦。

接下來咱們挨着試試吧。

 

二、分組驗證及分組順序

若是咱們想在新增的狀況驗證id和name,而修改的狀況驗證name和password,怎麼辦? 那麼就須要分組了。

首先定義分組接口:

public interface First {
}

public interface Second {
}

分組接口就是兩個普通的接口,用於標識,相似於java.io.Serializable。

 

接着咱們使用分組接口標識實體:

public class User implements Serializable {

    @NotNull(message = "{user.id.null}", groups = {First.class})
    private Long id;

    @Length(min = 5, max = 20, message = "{user.name.length.illegal}", groups = {Second.class})
    @Pattern(regexp = "[a-zA-Z]{5,20}", message = "{user.name.illegal}", groups = {Second.class})
    private String name;

    @NotNull(message = "{user.password.null}", groups = {First.class, Second.class})
    private String password;
}

 

驗證時使用如:

@RequestMapping("/save")
    public String save(@Validated({Second.class}) User user, BindingResult result) {
        if(result.hasErrors()) {
            return "error";
        }
        return "success";
    }

即經過@Validate註解標識要驗證的分組;若是要驗證兩個的話,能夠這樣@Validated({First.class, Second.class})。

 

接下來咱們來看看經過分組來指定順序;還記得以前的錯誤消息嗎? user.name會顯示兩個錯誤消息,並且順序不肯定;若是咱們先驗證一個消息;若是不經過再驗證另外一個怎麼辦?能夠經過@GroupSequence指定分組驗證順序:

 

@GroupSequence({First.class, Second.class, User.class})
public class User implements Serializable {
    private Long id;

    @Length(min = 5, max = 20, message = "{user.name.length.illegal}", groups = {First.class})
    @Pattern(regexp = "[a-zA-Z]{5,20}", message = "{user.name.illegal}", groups = {Second.class})
    private String name;
    
    private String password;
}

經過@GroupSequence指定驗證順序:先驗證First分組,若是有錯誤當即返回而不會驗證Second分組,接着若是First分組驗證經過了,那麼纔去驗證Second分組,最後指定User.class表示那些沒有分組的在最後。這樣咱們就能夠實現按順序驗證分組了。

 

另外一個比較常見的就是級聯驗證:

如:

public class User {

    @Valid 
    @ConvertGroup(from=First.class, to=Second.class)
    private Organization o;

}

 一、級聯驗證只要在相應的字段上加@Valid便可,會進行級聯驗證;@ConvertGroup的做用是當驗證o的分組是First時,那麼驗證o的分組是Second,即分組驗證的轉換。

 

三、消息中使用EL表達式

假設咱們須要顯示如:用戶名[NAME]長度必須在[MIN]到[MAX]之間,此處你們能夠看到,咱們不想把一些數據寫死,如NAME、MIN、MAX;此時咱們可使用EL表達式。

 

如:

@Length(min = 5, max = 20, message = "{user.name.length.illegal}", groups = {First.class})

錯誤消息:

user.name.length.illegal=用戶名長度必須在{min}到{max}之間

 

其中咱們可使用{驗證註解的屬性}獲得這些值;如{min}獲得@Length中的min值;其餘的也是相似的。

 

到此,咱們仍是沒法獲得出錯的那個輸入值,如name=zhangsan。此時就須要EL表達式的支持,首先肯定引入EL jar包且版本正確。而後使用如:

user.name.length.illegal=用戶名[${validatedValue}]長度必須在5到20之間

使用如EL表達式:${validatedValue}獲得輸入的值,如zhangsan。固然咱們還可使用如${min > 1 ? '大於1' : '小於等於1'},及在EL表達式中也能拿到如@Length的min等數據。

 

另外咱們還能夠拿到一個java.util.Formatter類型的formatter變量進行格式化:

${formatter.format("%04d", min)}

 

四、方法參數/返回值驗證

這個能夠參考《Spring3.1 對Bean Validation規範的新支持(方法級別驗證) 》,概念是相似的,具體能夠參考Bean Validation 文檔。

 

五、自定義驗證規則

有時候默認的規則可能還不夠,有時候還須要自定義規則,好比屏蔽關鍵詞驗證是很是常見的一個功能,好比在發帖時帖子中不容許出現admin等關鍵詞。

 

一、定義驗證註解

package com.sishuok.spring4.validator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
/**
 * <p>User: Zhang Kaitao
 * <p>Date: 13-12-15
 * <p>Version: 1.0
 */

@Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
@Retention(RUNTIME)
//指定驗證器
@Constraint(validatedBy = ForbiddenValidator.class)
@Documented
public @interface Forbidden {

    //默認錯誤消息
    String message() default "{forbidden.word}";

    //分組
    Class<?>[] groups() default { };

    //負載
    Class<? extends Payload>[] payload() default { };

    //指定多個時使用
    @Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
    @Retention(RUNTIME)
    @Documented
    @interface List {
        Forbidden[] value();
    }
}

 

二、 定義驗證器

package com.sishuok.spring4.validator;

import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorContextImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.util.StringUtils;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.io.Serializable;

/**
 * <p>User: Zhang Kaitao
 * <p>Date: 13-12-15
 * <p>Version: 1.0
 */
public class ForbiddenValidator implements ConstraintValidator<Forbidden, String> {

    private String[] forbiddenWords = {"admin"};

    @Override
    public void initialize(Forbidden constraintAnnotation) {
        //初始化,獲得註解數據
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if(StringUtils.isEmpty(value)) {
            return true;
        }

        for(String word : forbiddenWords) {
            if(value.contains(word)) {
                return false;//驗證失敗
            }
        }
        return true;
    }
}

 驗證器中可使用spring的依賴注入,如注入:@Autowired  private ApplicationContext ctx; 

 

三、使用

public class User implements Serializable {
    @Forbidden()
    private String name;
}

 

四、當咱們在提交name中含有admin的時候會輸出錯誤消息:

forbidden.word=您輸入的數據中有非法關鍵詞

 

問題來了,哪一個詞是非法的呢?bean validation 和 hibernate validator都沒有提供相應的api提供這個數據,怎麼辦呢?經過跟蹤代碼,發現一種不是特別好的方法:咱們能夠覆蓋org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl實現(即複製一份代碼放到咱們的src中),而後覆蓋buildAnnotationParameterMap方法;

private Map<String, Object> buildAnnotationParameterMap(Annotation annotation) {
        ……
        //將Collections.unmodifiableMap( parameters );替換爲以下語句
        return parameters;
    }

 即容許這個數據能夠修改;而後在ForbiddenValidator中:

for(String word : forbiddenWords) {
            if(value.contains(word)) {
                ((ConstraintValidatorContextImpl)context).getConstraintDescriptor().getAttributes().put("word", word);
                return false;//驗證失敗
            }
        }

經過((ConstraintValidatorContextImpl)context).getConstraintDescriptor().getAttributes().put("word", word);添加本身的屬性;放到attributes中的數據能夠經過${} 獲取。而後消息就能夠變成:

forbidden.word=您輸入的數據中有非法關鍵詞【{word}】

這種方式不是很友好,可是能夠解決咱們的問題。

 

典型的如密碼、確認密碼的場景,很是經常使用;若是沒有這個功能咱們須要本身寫代碼來完成;並且常常重複本身。接下來看看bean validation 1.1如何實現的。

 

六、類級別驗證器

6.一、定義驗證註解

package com.sishuok.spring4.validator;

import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.constraints.NotNull;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
/**
 * <p>User: Zhang Kaitao
 * <p>Date: 13-12-15
 * <p>Version: 1.0
 */

@Target({ TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
//指定驗證器
@Constraint(validatedBy = CheckPasswordValidator.class)
@Documented
public @interface CheckPassword {

    //默認錯誤消息
    String message() default "";

    //分組
    Class<?>[] groups() default { };

    //負載
    Class<? extends Payload>[] payload() default { };

    //指定多個時使用
    @Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
    @Retention(RUNTIME)
    @Documented
    @interface List {
        CheckPassword[] value();
    }
}

6.二、 定義驗證器

package com.sishuok.spring4.validator;

import com.sishuok.spring4.entity.User;
import org.springframework.util.StringUtils;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

/**
 * <p>User: Zhang Kaitao
 * <p>Date: 13-12-15
 * <p>Version: 1.0
 */
public class CheckPasswordValidator implements ConstraintValidator<CheckPassword, User> {

    @Override
    public void initialize(CheckPassword constraintAnnotation) {
    }

    @Override
    public boolean isValid(User user, ConstraintValidatorContext context) {
        if(user == null) {
            return true;
        }

        //沒有填密碼
        if(!StringUtils.hasText(user.getPassword())) {
            context.disableDefaultConstraintViolation();
            context.buildConstraintViolationWithTemplate("{password.null}")
                    .addPropertyNode("password")
                    .addConstraintViolation();
            return false;
        }

        if(!StringUtils.hasText(user.getConfirmation())) {
            context.disableDefaultConstraintViolation();
            context.buildConstraintViolationWithTemplate("{password.confirmation.null}")
                    .addPropertyNode("confirmation")
                    .addConstraintViolation();
            return false;
        }

        //兩次密碼不同
        if (!user.getPassword().trim().equals(user.getConfirmation().trim())) {
            context.disableDefaultConstraintViolation();
            context.buildConstraintViolationWithTemplate("{password.confirmation.error}")
                    .addPropertyNode("confirmation")
                    .addConstraintViolation();
            return false;
        }
        return true;
    }
}

其中咱們經過disableDefaultConstraintViolation禁用默認的約束;而後經過buildConstraintViolationWithTemplate(消息模板)/addPropertyNode(所屬屬性)/addConstraintViolation定義咱們本身的約束。

 

6.三、使用

@CheckPassword()
public class User implements Serializable {
}

 放到類頭上便可。

 

七、經過腳本驗證

@ScriptAssert(script = "_this.password==_this.confirmation", lang = "javascript", alias = "_this", message = "{password.confirmation.error}")
public class User implements Serializable {
}

經過腳本驗證是很是簡單並且強大的,lang指定腳本語言(請參考javax.script.ScriptEngineManager JSR-223),alias是在腳本驗證中User對象的名字,可是你們會發現一個問題:錯誤消息怎麼顯示呢? 在springmvc 中會添加到全局錯誤消息中,這確定不是咱們想要的,咱們改造下吧。

 

7.一、定義驗證註解

package com.sishuok.spring4.validator;

import org.hibernate.validator.internal.constraintvalidators.ScriptAssertValidator;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;

import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target({ TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = {PropertyScriptAssertValidator.class})
@Documented
public @interface PropertyScriptAssert {

    String message() default "{org.hibernate.validator.constraints.ScriptAssert.message}";

    Class<?>[] groups() default { };

    Class<? extends Payload>[] payload() default { };

    String lang();

    String script();

    String alias() default "_this";

    String property();

    @Target({ TYPE })
    @Retention(RUNTIME)
    @Documented
    public @interface List {
        PropertyScriptAssert[] value();
    }
}

和ScriptAssert沒什麼區別,只是多了個property用來指定出錯後給實體的哪一個屬性。

 

7.二、驗證器

package com.sishuok.spring4.validator;

import javax.script.ScriptException;
import javax.validation.ConstraintDeclarationException;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

import com.sishuok.spring4.validator.PropertyScriptAssert;
import org.hibernate.validator.constraints.ScriptAssert;
import org.hibernate.validator.internal.util.Contracts;
import org.hibernate.validator.internal.util.logging.Log;
import org.hibernate.validator.internal.util.logging.LoggerFactory;
import org.hibernate.validator.internal.util.scriptengine.ScriptEvaluator;
import org.hibernate.validator.internal.util.scriptengine.ScriptEvaluatorFactory;

import static org.hibernate.validator.internal.util.logging.Messages.MESSAGES;

public class PropertyScriptAssertValidator implements ConstraintValidator<PropertyScriptAssert, Object> {

    private static final Log log = LoggerFactory.make();

    private String script;
    private String languageName;
    private String alias;
    private String property;
    private String message;

    public void initialize(PropertyScriptAssert constraintAnnotation) {
        validateParameters( constraintAnnotation );

        this.script = constraintAnnotation.script();
        this.languageName = constraintAnnotation.lang();
        this.alias = constraintAnnotation.alias();
        this.property = constraintAnnotation.property();
        this.message = constraintAnnotation.message();
    }

    public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {

        Object evaluationResult;
        ScriptEvaluator scriptEvaluator;

        try {
            ScriptEvaluatorFactory evaluatorFactory = ScriptEvaluatorFactory.getInstance();
            scriptEvaluator = evaluatorFactory.getScriptEvaluatorByLanguageName( languageName );
        }
        catch ( ScriptException e ) {
            throw new ConstraintDeclarationException( e );
        }

        try {
            evaluationResult = scriptEvaluator.evaluate( script, value, alias );
        }
        catch ( ScriptException e ) {
            throw log.getErrorDuringScriptExecutionException( script, e );
        }

        if ( evaluationResult == null ) {
            throw log.getScriptMustReturnTrueOrFalseException( script );
        }
        if ( !( evaluationResult instanceof Boolean ) ) {
            throw log.getScriptMustReturnTrueOrFalseException(
                    script,
                    evaluationResult,
                    evaluationResult.getClass().getCanonicalName()
            );
        }

        if(Boolean.FALSE.equals(evaluationResult)) {
            constraintValidatorContext.disableDefaultConstraintViolation();
            constraintValidatorContext
                    .buildConstraintViolationWithTemplate(message)
                    .addPropertyNode(property)
                    .addConstraintViolation();
        }

        return Boolean.TRUE.equals( evaluationResult );
    }

    private void validateParameters(PropertyScriptAssert constraintAnnotation) {
        Contracts.assertNotEmpty( constraintAnnotation.script(), MESSAGES.parameterMustNotBeEmpty( "script" ) );
        Contracts.assertNotEmpty( constraintAnnotation.lang(), MESSAGES.parameterMustNotBeEmpty( "lang" ) );
        Contracts.assertNotEmpty( constraintAnnotation.alias(), MESSAGES.parameterMustNotBeEmpty( "alias" ) );
        Contracts.assertNotEmpty( constraintAnnotation.property(), MESSAGES.parameterMustNotBeEmpty( "property" ) );
        Contracts.assertNotEmpty( constraintAnnotation.message(), MESSAGES.parameterMustNotBeEmpty( "message" ) );
    }
}

和以前的類級別驗證器相似,就很少解釋了,其餘代碼所有拷貝自org.hibernate.validator.internal.constraintvalidators.ScriptAssertValidator。

 

7.三、使用

@PropertyScriptAssert(property = "confirmation", script = "_this.password==_this.confirmation", lang = "javascript", alias = "_this", message = "{password.confirmation.error}")

和以前的區別就是多了個property,用來指定出錯時給哪一個字段。 這個相對以前的類級別驗證器更通用一點。

 

八、cross-parameter,跨參數驗證

直接看示例;

 

8.一、首先註冊MethodValidationPostProcessor,起做用請參考《Spring3.1 對Bean Validation規範的新支持(方法級別驗證) 》 

<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor">
        <property name="validator" ref="validator"/>
    </bean>

 

8.二、Service 

@Validated
@Service
public class UserService {

    @CrossParameter
    public void changePassword(String password, String confirmation) {

    }
}

經過@Validated註解UserService表示該類中有須要進行方法參數/返回值驗證;   @CrossParameter註解方法表示要進行跨參數驗證;即驗證password和confirmation是否相等。

 

8.三、驗證註解 

package com.sishuok.spring4.validator;

//省略import

@Constraint(validatedBy = CrossParameterValidator.class)
@Target({ METHOD, CONSTRUCTOR, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Documented
public @interface CrossParameter {

    String message() default "{password.confirmation.error}";
    Class<?>[] groups() default { };
    Class<? extends Payload>[] payload() default { };

}

 

8.四、驗證器 

package com.sishuok.spring4.validator;

//省略import

@SupportedValidationTarget(ValidationTarget.PARAMETERS)
public class CrossParameterValidator implements ConstraintValidator<CrossParameter, Object[]> {

    @Override
    public void initialize(CrossParameter constraintAnnotation) {
    }

    @Override
    public boolean isValid(Object[] value, ConstraintValidatorContext context) {
        if(value == null || value.length != 2) {
            throw new IllegalArgumentException("must have two args");
        }
        if(value[0] == null || value[1] == null) {
            return true;
        }
        if(value[0].equals(value[1])) {
            return true;
        }
        return false;
    }
}

其中@SupportedValidationTarget(ValidationTarget.PARAMETERS)表示驗證參數; value將是參數列表。 

 

8.五、使用

@RequestMapping("/changePassword")
    public String changePassword(
            @RequestParam("password") String password,
            @RequestParam("confirmation") String confirmation, Model model) {
        try {
            userService.changePassword(password, confirmation);
        } catch (ConstraintViolationException e) {
            for(ConstraintViolation violation : e.getConstraintViolations()) {
                System.out.println(violation.getMessage());
            }
        }
        return "success";
    }

調用userService.changePassword方法,若是驗證失敗將拋出ConstraintViolationException異常,而後獲得ConstraintViolation,調用getMessage便可獲得錯誤消息;而後到前臺顯示便可。

 

從以上來看,不如以前的使用方便,須要本身對錯誤消息進行處理。 下一節咱們也寫個腳本方式的跨參數驗證器。

 

九、混合類級別驗證器和跨參數驗證器

9.一、驗證註解

package com.sishuok.spring4.validator;

//省略import

@Constraint(validatedBy = {
        CrossParameterScriptAssertClassValidator.class,
        CrossParameterScriptAssertParameterValidator.class
})
@Target({ TYPE, FIELD, PARAMETER, METHOD, CONSTRUCTOR, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Documented
public @interface CrossParameterScriptAssert {
    String message() default "error";
    Class<?>[] groups() default { };
    Class<? extends Payload>[] payload() default { };
    String script();
    String lang();
    String alias() default "_this";
    String property() default "";
    ConstraintTarget validationAppliesTo() default ConstraintTarget.IMPLICIT;
}

 

此處咱們經過@Constraint指定了兩個驗證器,一個類級別的,一個跨參數的。validationAppliesTo指定爲ConstraintTarget.IMPLICIT,表示隱式自動判斷。

 

9.二、驗證器

請下載源碼查看

 

9.三、使用

9.3.一、類級別使用

@CrossParameterScriptAssert(property = "confirmation", script = "_this.password==_this.confirmation", lang = "javascript", alias = "_this", message = "{password.confirmation.error}")

指定property便可,其餘和以前的同樣。

9.3.二、跨參數驗證

@CrossParameterScriptAssert(script = "args[0] == args[1]", lang = "javascript", alias = "args", message = "{password.confirmation.error}")
    public void changePassword(String password, String confirmation) {

    }

經過args[0]==args[1] 來判斷是否相等。

 

這樣,咱們的驗證註解就自動適應兩種驗證規則了。  

 

十、組合驗證註解 

有時候,可能有好幾個註解須要一塊兒使用,此時就可使用組合驗證註解

@Target({ FIELD})
@Retention(RUNTIME)
@Documented
@NotNull(message = "{user.name.null}")
@Length(min = 5, max = 20, message = "{user.name.length.illegal}")
@Pattern(regexp = "[a-zA-Z]{5,20}", message = "{user.name.length.illegal}")
@Constraint(validatedBy = { })
public @interface Composition {
    String message() default "";
    Class<?>[] groups() default { };
    Class<? extends Payload>[] payload() default { };
}

這樣咱們驗證時只須要:

@Composition()
    private String name;

簡潔多了。 

 

十一、本地化 

即根據不一樣的語言選擇不一樣的錯誤消息顯示。

一、本地化解析器

<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
        <property name="cookieName" value="locale"/>
        <property name="cookieMaxAge" value="-1"/>
        <property name="defaultLocale" value="zh_CN"/>
    </bean>

此處使用cookie存儲本地化信息,固然也能夠選擇其餘的,如Session存儲。

 

二、設置本地化信息的攔截器

<mvc:interceptors>
        <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
            <property name="paramName" value="language"/>
        </bean>
    </mvc:interceptors>

即請求參數中經過language設置語言。

 

三、消息文件

 

四、 瀏覽器輸入

http://localhost:9080/spring4/changePassword?password=1&confirmation=2&language=en_US

 

 

 

到此,咱們已經完成大部分Bean Validation的功能實驗了。對於如XML配置、編程式驗證API的使用等對於咱們使用SpringMVC這種web環境用處不大,因此就很少介紹了,有興趣能夠本身下載官方文檔學習。

相關文章
相關標籤/搜索