日韩久久久精品,亚洲精品久久久久久久久久久,亚洲欧美一区二区三区国产精品 ,一区二区福利

Spring4新特性——集成Bean Validation 1.1(JSR

系統(tǒng) 2138 0

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

Spring4新特性——核心容器的其他改進(jìn)

Spring4新特性——Web開發(fā)的增強(qiáng)

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

Spring4新特性——Groovy Bean定義DSL

Spring4新特性——更好的Java泛型操作API ?

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

Spring4新特性——注解、腳本、任務(wù)、MVC等其他特性改進(jìn) ?

?

在之前的《 跟我學(xué)SpringMVC 》中的《 第七章 注解式控制器的數(shù)據(jù)驗(yàn)證、類型轉(zhuǎn)換及格式化 》中已經(jīng)介紹過SpringMVC集成Bean Validation 1.0(JSR-303),目前Bean Validation最新版本是 Bean Validation?1.1 (JSR-349),新特性可以到官網(wǎng)查看,筆者最喜歡的兩個(gè)特性是:跨參數(shù)驗(yàn)證(比如密碼和確認(rèn)密碼的驗(yàn)證)和支持在消息中使用EL表達(dá)式,其他的還有如方法參數(shù)/返回值驗(yàn)證、CDI和依賴注入、分組轉(zhuǎn)換等。對(duì)于方法參數(shù)/返回值驗(yàn)證,大家可以參閱《 Spring3.1 對(duì)Bean Validation規(guī)范的新支持(方法級(jí)別驗(yàn)證) ?》。

?

Bean Validation 1.1當(dāng)前實(shí)現(xiàn)是Hibernate validator 5,且spring4才支持。接下來我們從以下幾個(gè)方法講解Bean Validation 1.1,當(dāng)然不一定是新特性:

  1. ?集成Bean Validation 1.1到SpringMVC
  2. ?分組驗(yàn)證、分組順序及級(jí)聯(lián)驗(yàn)證
  3. ?消息中使用EL表達(dá)式
  4. ?方法參數(shù)/返回值驗(yàn)證
  5. ?自定義驗(yàn)證規(guī)則
  6. ?類級(jí)別驗(yàn)證器
  7. ?腳本驗(yàn)證器
  8. ?cross-parameter,跨參數(shù)驗(yàn)證
  9. 混合類級(jí)別驗(yàn)證器和跨參數(shù)驗(yàn)證器
  10. 組合多個(gè)驗(yàn)證注解
  11. 本地化

因?yàn)榇蠖鄶?shù)時(shí)候驗(yàn)證都配合web框架使用,而且很多朋友都咨詢過如分組/跨參數(shù)驗(yàn)證,所以本文介紹下這些,且是和SpringMVC框架集成的例子,其他使用方式(比如集成到JPA中)可以參考其官方文檔:

規(guī)范: http://beanvalidation.org/1.1/spec/

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

?

?1、集成Bean Validation 1.1到SpringMVC

1.1、項(xiàng)目搭建

首先添加hibernate validator 5依賴:

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

如果想在消息中使用EL表達(dá)式,請確保EL表達(dá)式版本是?2.2或以上,如使用Tomcat6,請到Tomcat7中拷貝相應(yīng)的EL jar包到Tomcat6中。

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

請確保您使用的Web容器有相應(yīng)版本的el jar包。

?

對(duì)于其他POM依賴請下載附件中的項(xiàng)目參考。

?

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

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

    <!-- 以下 validator  ConversionService 在使用 mvc:annotation-driven 會(huì) 自動(dòng)注冊-->
    <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
        <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
        <!-- 如果不加默認(rèn)到 使用classpath下的 ValidationMessages.properties -->
        <property name="validationMessageSource" ref="messageSource"/>
    </bean>

    <!-- 國際化的消息資源文件(本系統(tǒng)中主要用于顯示/錯(cuò)誤消息定制) -->
    <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <!-- 在web環(huán)境中一定要定位到classpath 否則默認(rèn)到當(dāng)前web應(yīng)用下找  -->
                <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.3、實(shí)體驗(yàn)證注解:

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

對(duì)于驗(yàn)證規(guī)則可以參考官方文檔,或者《 第七章 注解式控制器的數(shù)據(jù)驗(yàn)證、類型轉(zhuǎn)換及格式化 》。

?

1.4、錯(cuò)誤消息文件messages.properties:

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

?

1.5、控制器

      @Controller
public class UserController {

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

?

1.6、錯(cuò)誤頁面:

      <spring:hasBindErrors name="user">
    <c:if test="${errors.fieldErrorCount > 0}">
        字段錯(cuò)誤:<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}">
        全局錯(cuò)誤:<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>

    

?

大家以后可以根據(jù)這個(gè)做通用的錯(cuò)誤消息顯示規(guī)則。比如我前端頁面使用validationEngine顯示錯(cuò)誤消息,那么我可以定義一個(gè)tag來通用化錯(cuò)誤消息的顯示: showFieldError.tag 。??

?

1.7、測試

輸入如: http://localhost:9080/spring4/save?name=123 ?, 我們得到如下錯(cuò)誤:

      name------用戶名必須是字母
name------用戶名長度必須在5到20之間
password------密碼不能為空
id------用戶編號(hào)不能為空
    

?

基本的集成就完成了。

?

如上測試有幾個(gè)小問題:

1、錯(cuò)誤消息順序,大家可以看到name的錯(cuò)誤消息順序不是按照書寫順序的,即不確定;

2、我想顯示如:用戶名【zhangsan】必須在5到20之間;其中我們想動(dòng)態(tài)顯示:用戶名、min,max;而不是寫死了;

3、我想在修改的時(shí)候只驗(yàn)證用戶名,其他的不驗(yàn)證怎么辦。

接下來我們挨著試試吧。

?

2、分組驗(yàn)證及分組順序

如果我們想在新增的情況驗(yàn)證id和name,而修改的情況驗(yàn)證name和password,怎么辦? 那么就需要分組了。

首先定義分組接口:

      public interface First {
}

public interface Second {
}
    

分組接口就是兩個(gè)普通的接口,用于標(biāo)識(shí),類似于java.io.Serializable。

?

接著我們使用分組接口標(biāo)識(shí)實(shí)體:

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

?

驗(yàn)證時(shí)使用如:

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

即通過@Validate注解標(biāo)識(shí)要驗(yàn)證的分組;如果要驗(yàn)證兩個(gè)的話,可以這樣@Validated({First.class, Second.class})。

?

接下來我們來看看通過分組來指定順序;還記得之前的錯(cuò)誤消息嗎? user.name會(huì)顯示兩個(gè)錯(cuò)誤消息,而且順序不確定;如果我們先驗(yàn)證一個(gè)消息;如果不通過再驗(yàn)證另一個(gè)怎么辦?可以通過@GroupSequence指定分組驗(yàn)證順序:

?

      @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指定驗(yàn)證順序:先驗(yàn)證First分組,如果有錯(cuò)誤立即返回而不會(huì)驗(yàn)證Second分組,接著如果First分組驗(yàn)證通過了,那么才去驗(yàn)證Second分組,最后指定User.class表示那些沒有分組的在最后。這樣我們就可以實(shí)現(xiàn)按順序驗(yàn)證分組了。

?

另一個(gè)比較常見的就是級(jí)聯(lián)驗(yàn)證:

如:

      public class User {

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

}
    

?1、級(jí)聯(lián)驗(yàn)證只要在相應(yīng)的字段上加@Valid即可,會(huì)進(jìn)行級(jí)聯(lián)驗(yàn)證;@ ConvertGroup的作用是當(dāng)驗(yàn)證o的分組是First時(shí),那么驗(yàn)證o的分組是Second,即分組驗(yàn)證的轉(zhuǎn)換。

?

3、消息中使用EL表達(dá)式

假設(shè)我們需要顯示如:用戶名[NAME]長度必須在[MIN]到[MAX]之間,此處大家可以看到,我們不想把一些數(shù)據(jù)寫死,如NAME、MIN、MAX;此時(shí)我們可以使用EL表達(dá)式。

?

如:

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

錯(cuò)誤消息:

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

?

其中我們可以使用{驗(yàn)證注解的屬性}得到這些值;如{min}得到@Length中的min值;其他的也是類似的。

?

到此,我們還是無法得到出錯(cuò)的那個(gè)輸入值,如name=zhangsan。此時(shí)就需要EL表達(dá)式的支持,首先確定引入EL jar包且版本正確。然后使用如:

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

使用如EL表達(dá)式:${validatedValue}得到輸入的值,如zhangsan。當(dāng)然我們還可以使用如${min > 1 ? '大于1' : '小于等于1'},及在EL表達(dá)式中也能拿到如@Length的min等數(shù)據(jù)。

?

另外我們還可以拿到一個(gè)java.util.Formatter類型的formatter變量進(jìn)行格式化:

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

?

4、方法參數(shù)/返回值驗(yàn)證

這個(gè)可以參考《 Spring3.1 對(duì)Bean Validation規(guī)范的新支持(方法級(jí)別驗(yàn)證) ?》,概念是類似的,具體可以參考Bean Validation 文檔。

?

5、自定義驗(yàn)證規(guī)則

有時(shí)候默認(rèn)的規(guī)則可能還不夠,有時(shí)候還需要自定義規(guī)則,比如屏蔽關(guān)鍵詞驗(yàn)證是非常常見的一個(gè)功能,比如在發(fā)帖時(shí)帖子中不允許出現(xiàn)admin等關(guān)鍵詞。

?

1、定義驗(yàn)證注解

      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)
//指定驗(yàn)證器
@Constraint(validatedBy = ForbiddenValidator.class)
@Documented
public @interface Forbidden {

    //默認(rèn)錯(cuò)誤消息
    String message() default "{forbidden.word}";

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

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

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

?

2、 定義驗(yàn)證器

      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) {
        //初始化,得到注解數(shù)據(jù)
    }

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

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

    

?驗(yàn)證器中可以使用spring的依賴注入,如注入:@Autowired? private ApplicationContext ctx;?

?

3、使用

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

    

?

4、當(dāng)我們在提交name中含有admin的時(shí)候會(huì)輸出錯(cuò)誤消息:

      forbidden.word=您輸入的數(shù)據(jù)中有非法關(guān)鍵詞
    

?

問題來了,哪個(gè)詞是非法的呢?bean validation 和 hibernate validator都沒有提供相應(yīng)的api提供這個(gè)數(shù)據(jù),怎么辦呢?通過跟蹤代碼,發(fā)現(xiàn)一種不是特別好的方法:我們可以覆蓋org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl實(shí)現(xiàn)(即復(fù)制一份代碼放到我們的src中),然后覆蓋buildAnnotationParameterMap方法;

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

?即允許這個(gè)數(shù)據(jù)可以修改;然后在ForbiddenValidator中:

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

通過((ConstraintValidatorContextImpl)context).getConstraintDescriptor().getAttributes().put("word", word);添加自己的屬性;放到attributes中的數(shù)據(jù)可以通過${}?獲取。然后消息就可以變成:

      forbidden.word=您輸入的數(shù)據(jù)中有非法關(guān)鍵詞【{word}】
    

這種方式不是很友好,但是可以解決我們的問題。

?

典型的如密碼、確認(rèn)密碼的場景,非常常用;如果沒有這個(gè)功能我們需要自己寫代碼來完成;而且經(jīng)常重復(fù)自己。接下來看看bean validation 1.1如何實(shí)現(xiàn)的。

?

6、類級(jí)別驗(yàn)證器

6.1、定義驗(yàn)證注解

      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)
//指定驗(yàn)證器
@Constraint(validatedBy = CheckPasswordValidator.class)
@Documented
public @interface CheckPassword {

    //默認(rèn)錯(cuò)誤消息
    String message() default "";

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

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

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

    

6.2、 定義驗(yàn)證器

      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禁用默認(rèn)的約束;然后通過buildConstraintViolationWithTemplate(消息模板)/addPropertyNode(所屬屬性)/addConstraintViolation定義我們自己的約束。

?

6.3、使用

      @CheckPassword()
public class User implements Serializable {
}
    

?放到類頭上即可。

?

7、通過腳本驗(yàn)證

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

通過腳本驗(yàn)證是非常簡單而且強(qiáng)大的,lang指定腳本語言(請參考javax.script.ScriptEngineManager JSR-223),alias是在腳本驗(yàn)證中User對(duì)象的名字,但是大家會(huì)發(fā)現(xiàn)一個(gè)問題:錯(cuò)誤消息怎么顯示呢? 在springmvc 中會(huì)添加到全局錯(cuò)誤消息中,這肯定不是我們想要的,我們改造下吧。

?

7.1、定義驗(yàn)證注解

      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沒什么區(qū)別,只是多了個(gè)property用來指定出錯(cuò)后給實(shí)體的哪個(gè)屬性。

?

7.2、驗(yàn)證器

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

    

和之前的類級(jí)別驗(yàn)證器類似,就不多解釋了,其他代碼全部拷貝自org.hibernate.validator.internal.constraintvalidators.ScriptAssertValidator。

?

7.3、使用

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

和之前的區(qū)別就是多了個(gè)property,用來指定出錯(cuò)時(shí)給哪個(gè)字段。 這個(gè)相對(duì)之前的類級(jí)別驗(yàn)證器更通用一點(diǎn)。

?

8、cross-parameter,跨參數(shù)驗(yàn)證

直接看示例;

?

8.1、首先注冊MethodValidationPostProcessor,起作用請參考《 Spring3.1 對(duì)Bean Validation規(guī)范的新支持(方法級(jí)別驗(yàn)證) ?》?

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

?

8.2、Service?

      @Validated
@Service
public class UserService {

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

    }
}
    

通過@Validated注解UserService表示該類中有需要進(jìn)行方法參數(shù)/返回值驗(yàn)證;???@CrossParameter注解方法表示要進(jìn)行跨參數(shù)驗(yàn)證;即驗(yàn)證password和confirmation是否相等。

?

8.3、驗(yàn)證注解?

      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.4、驗(yàn)證器?

      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)表示驗(yàn)證參數(shù); value將是參數(shù)列表。?

?

8.5、使用

          @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";
    }
    

調(diào)用userService.changePassword方法,如果驗(yàn)證失敗將拋出ConstraintViolationException異常,然后得到ConstraintViolation,調(diào)用getMessage即可得到錯(cuò)誤消息;然后到前臺(tái)顯示即可。

?

從以上來看,不如之前的使用方便,需要自己對(duì)錯(cuò)誤消息進(jìn)行處理。 下一節(jié)我們也寫個(gè)腳本方式的跨參數(shù)驗(yàn)證器。

?

9、混合類級(jí)別驗(yàn)證器和跨參數(shù)驗(yàn)證器

9.1、驗(yàn)證注解

      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指定了兩個(gè)驗(yàn)證器,一個(gè)類級(jí)別的,一個(gè)跨參數(shù)的。validationAppliesTo指定為ConstraintTarget.IMPLICIT,表示隱式自動(dòng)判斷。

?

9.2、驗(yàn)證器

請下載源碼查看

?

9.3、使用

9.3.1、類級(jí)別使用

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

指定property即可,其他和之前的一樣。

9.3.2、跨參數(shù)驗(yàn)證

          @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] 來判斷是否相等。

?

這樣,我們的驗(yàn)證注解就自動(dòng)適應(yīng)兩種驗(yàn)證規(guī)則了。??

?

10、組合驗(yàn)證注解?

有時(shí)候,可能有好幾個(gè)注解需要一起使用,此時(shí)就可以使用組合驗(yàn)證注解

      @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 { };
}
    

這樣我們驗(yàn)證時(shí)只需要:

          @Composition()
    private String name;

    

簡潔多了。?

?

11、本地化?

即根據(jù)不同的語言選擇不同的錯(cuò)誤消息顯示。

1、本地化解析器

          <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存儲(chǔ)本地化信息,當(dāng)然也可以選擇其他的,如Session存儲(chǔ)。

?

2、設(shè)置本地化信息的攔截器

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

即請求參數(shù)中通過language設(shè)置語言。

?

3、消息文件

?

4、 瀏覽器輸入

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

?

?

?

到此,我們已經(jīng)完成大部分Bean Validation的功能實(shí)驗(yàn)了。對(duì)于如XML配置、編程式驗(yàn)證API的使用等對(duì)于我們使用SpringMVC這種web環(huán)境用處不大,所以就不多介紹了,有興趣可以自己下載官方文檔學(xué)習(xí)。

?

?

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


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號(hào)聯(lián)系: 360901061

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對(duì)您有幫助就好】

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會(huì)非常 感謝您的哦!??!

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論
主站蜘蛛池模板: 马边| 佛学| 博乐市| 泽库县| 玉溪市| 乐陵市| 吴江市| 花垣县| 兰考县| 郯城县| 西丰县| 建德市| 麻栗坡县| 贺州市| 成武县| 北辰区| 星子县| 安国市| 牡丹江市| 盐津县| 阿克陶县| 辉南县| 莱芜市| 菏泽市| 神农架林区| 陆河县| 库车县| 房山区| 荆门市| 马龙县| 丰城市| 诸暨市| 长白| 弋阳县| 黄山市| 平罗县| 高密市| 洛扎县| 武义县| 思茅市| 阿荣旗|