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/

1、集成Bean Validation 1.1到SpringMVC

1.1、项目搭建

首先添加hibernate validator 5依赖:

  1. <dependency>
  2. <groupId>org.hibernate</groupId>
  3. <artifactId>hibernate-validator</artifactId>
  4. <version>5.0.2.Final</version>
  5. </dependency>

如果想在消息中使用EL表达式,请确保EL表达式版本是 2.2或以上,如使用Tomcat6,请到Tomcat7中拷贝相应的EL jar包到Tomcat6中。

  1. <dependency>
  2. <groupId>javax.el</groupId>
  3. <artifactId>javax.el-api</artifactId>
  4. <version>2.2.4</version>
  5. <scope>provided</scope>
  6. </dependency>

请确保您使用的Web容器有相应版本的el jar包。

对于其他POM依赖请下载附件中的项目参考。

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

  1. <!-- 指定自己定义的validator -->
  2. <mvc:annotation-driven validator="validator"/>
  3. <!-- 以下 validator  ConversionService 在使用 mvc:annotation-driven 会 自动注册-->
  4. <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
  5. <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
  6. <!-- 如果不加默认到 使用classpath下的 ValidationMessages.properties -->
  7. <property name="validationMessageSource" ref="messageSource"/>
  8. </bean>
  9. <!-- 国际化的消息资源文件(本系统中主要用于显示/错误消息定制) -->
  10. <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
  11. <property name="basenames">
  12. <list>
  13. <!-- 在web环境中一定要定位到classpath 否则默认到当前web应用下找  -->
  14. <value>classpath:messages</value>
  15. <value>classpath:org/hibernate/validator/ValidationMessages</value>
  16. </list>
  17. </property>
  18. <property name="useCodeAsDefaultMessage" value="false"/>
  19. <property name="defaultEncoding" value="UTF-8"/>
  20. <property name="cacheSeconds" value="60"/>
  21. </bean>

此处主要把bean validation的消息查找委托给spring的messageSource。

1.3、实体验证注解:

  1. public class User implements Serializable {
  2. @NotNull(message = "{user.id.null}")
  3. private Long id;
  4. @NotEmpty(message = "{user.name.null}")
  5. @Length(min = 5, max = 20, message = "{user.name.length.illegal}")
  6. @Pattern(regexp = "[a-zA-Z]{5,20}", message = "{user.name.illegal}")
  7. private String name;
  8. @NotNull(message = "{user.password.null}")
  9. private String password;
  10. }

对于验证规则可以参考官方文档,或者《第七章 注解式控制器的数据验证、类型转换及格式化》。

1.4、错误消息文件messages.properties:

  1. user.id.null=用户编号不能为空
  2. user.name.null=用户名不能为空
  3. user.name.length.illegal=用户名长度必须在5到20之间
  4. user.name.illegal=用户名必须是字母
  5. user.password.null=密码不能为空

1.5、控制器

  1. @Controller
  2. public class UserController {
  3. @RequestMapping("/save")
  4. public String save(@Valid User user, BindingResult result) {
  5. if(result.hasErrors()) {
  6. return "error";
  7. }
  8. return "success";
  9. }
  10. }

1.6、错误页面:

  1. <spring:hasBindErrors name="user">
  2. <c:if test="${errors.fieldErrorCount > 0}">
  3. 字段错误:<br/>
  4. <c:forEach items="${errors.fieldErrors}" var="error">
  5. <spring:message var="message" code="${error.code}" arguments="${error.arguments}" text="${error.defaultMessage}"/>
  6. ${error.field}------${message}<br/>
  7. </c:forEach>
  8. </c:if>
  9. <c:if test="${errors.globalErrorCount > 0}">
  10. 全局错误:<br/>
  11. <c:forEach items="${errors.globalErrors}" var="error">
  12. <spring:message var="message" code="${error.code}" arguments="${error.arguments}" text="${error.defaultMessage}"/>
  13. <c:if test="${not empty message}">
  14. ${message}<br/>
  15. </c:if>
  16. </c:forEach>
  17. </c:if>
  18. </spring:hasBindErrors>

大家以后可以根据这个做通用的错误消息显示规则。比如我前端页面使用validationEngine显示错误消息,那么我可以定义一个tag来通用化错误消息的显示:showFieldError.tag

1.7、测试

输入如:http://localhost:9080/spring4/save?name=123 , 我们得到如下错误:

  1. name------用户名必须是字母
  2. name------用户名长度必须在5到20之间
  3. password------密码不能为空
  4. id------用户编号不能为空

基本的集成就完成了。

如上测试有几个小问题:

1、错误消息顺序,大家可以看到name的错误消息顺序不是按照书写顺序的,即不确定;

2、我想显示如:用户名【zhangsan】必须在5到20之间;其中我们想动态显示:用户名、min,max;而不是写死了;

3、我想在修改的时候只验证用户名,其他的不验证怎么办。

接下来我们挨着试试吧。

2、分组验证及分组顺序

如果我们想在新增的情况验证id和name,而修改的情况验证name和password,怎么办? 那么就需要分组了。

首先定义分组接口:

  1. public interface First {
  2. }
  3. public interface Second {
  4. }

分组接口就是两个普通的接口,用于标识,类似于Java.io.Serializable。

接着我们使用分组接口标识实体:

  1. public class User implements Serializable {
  2. @NotNull(message = "{user.id.null}", groups = {First.class})
  3. private Long id;
  4. @Length(min = 5, max = 20, message = "{user.name.length.illegal}", groups = {Second.class})
  5. @Pattern(regexp = "[a-zA-Z]{5,20}", message = "{user.name.illegal}", groups = {Second.class})
  6. private String name;
  7. @NotNull(message = "{user.password.null}", groups = {First.class, Second.class})
  8. private String password;
  9. }

验证时使用如:

  1. @RequestMapping("/save")
  2. public String save(@Validated({Second.class}) User user, BindingResult result) {
  3. if(result.hasErrors()) {
  4. return "error";
  5. }
  6. return "success";
  7. }

即通过@Validate注解标识要验证的分组;如果要验证两个的话,可以这样@Validated({First.class, Second.class})。

接下来我们来看看通过分组来指定顺序;还记得之前的错误消息吗? user.name会显示两个错误消息,而且顺序不确定;如果我们先验证一个消息;如果不通过再验证另一个怎么办?可以通过@GroupSequence指定分组验证顺序:

  1. @GroupSequence({First.class, Second.class, User.class})
  2. public class User implements Serializable {
  3. private Long id;
  4. @Length(min = 5, max = 20, message = "{user.name.length.illegal}", groups = {First.class})
  5. @Pattern(regexp = "[a-zA-Z]{5,20}", message = "{user.name.illegal}", groups = {Second.class})
  6. private String name;
  7. private String password;
  8. }

通过@GroupSequence指定验证顺序:先验证First分组,如果有错误立即返回而不会验证Second分组,接着如果First分组验证通过了,那么才去验证Second分组,最后指定User.class表示那些没有分组的在最后。这样我们就可以实现按顺序验证分组了。

另一个比较常见的就是级联验证:

如:

  1. public class User {
  2. @Valid
  3. @ConvertGroup(from=First.class, to=Second.class)
  4. private Organization o;
  5. }

1、级联验证只要在相应的字段上加@Valid即可,会进行级联验证;@ConvertGroup的作用是当验证o的分组是First时,那么验证o的分组是Second,即分组验证的转换。

3、消息中使用EL表达式

假设我们需要显示如:用户名[NAME]长度必须在[MIN]到[MAX]之间,此处大家可以看到,我们不想把一些数据写死,如NAME、MIN、MAX;此时我们可以使用EL表达式。

如:

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

错误消息:

  1. user.name.length.illegal=用户名长度必须在{min}到{max}之间

其中我们可以使用{验证注解的属性}得到这些值;如{min}得到@Length中的min值;其他的也是类似的。

到此,我们还是无法得到出错的那个输入值,如name=zhangsan。此时就需要EL表达式的支持,首先确定引入EL jar包且版本正确。然后使用如:

  1. user.name.length.illegal=用户名[${validatedValue}]长度必须在5到20之间

使用如EL表达式:${validatedValue}得到输入的值,如zhangsan。当然我们还可以使用如${min > 1 ? '大于1' : '小于等于1'},及在EL表达式中也能拿到如@Length的min等数据。

另外我们还可以拿到一个java.util.Formatter类型的formatter变量进行格式化:

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

4、方法参数/返回值验证

这个可以参考《Spring3.1 对Bean Validation规范的新支持(方法级别验证) 》,概念是类似的,具体可以参考Bean Validation 文档。

5、自定义验证规则

有时候默认的规则可能还不够,有时候还需要自定义规则,比如屏蔽关键词验证是非常常见的一个功能,比如在发帖时帖子中不允许出现admin等关键词。

1、定义验证注解

  1. package com.sishuok.spring4.validator;
  2. import javax.validation.Constraint;
  3. import javax.validation.Payload;
  4. import java.lang.annotation.Documented;
  5. import java.lang.annotation.Retention;
  6. import java.lang.annotation.Target;
  7. import static java.lang.annotation.ElementType.*;
  8. import static java.lang.annotation.RetentionPolicy.*;
  9. /**
  10. * <p>User: Zhang Kaitao
  11. * <p>Date: 13-12-15
  12. * <p>Version: 1.0
  13. */
  14. @Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
  15. @Retention(RUNTIME)
  16. //指定验证器
  17. @Constraint(validatedBy = ForbiddenValidator.class)
  18. @Documented
  19. public @interface Forbidden {
  20. //默认错误消息
  21. String message() default "{forbidden.word}";
  22. //分组
  23. Class<?>[] groups() default { };
  24. //负载
  25. Class<? extends Payload>[] payload() default { };
  26. //指定多个时使用
  27. @Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
  28. @Retention(RUNTIME)
  29. @Documented
  30. @interface List {
  31. Forbidden[] value();
  32. }
  33. }

2、 定义验证器

  1. package com.sishuok.spring4.validator;
  2. import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorContextImpl;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.context.ApplicationContext;
  5. import org.springframework.util.StringUtils;
  6. import javax.validation.ConstraintValidator;
  7. import javax.validation.ConstraintValidatorContext;
  8. import java.io.Serializable;
  9. /**
  10. * <p>User: Zhang Kaitao
  11. * <p>Date: 13-12-15
  12. * <p>Version: 1.0
  13. */
  14. public class ForbiddenValidator implements ConstraintValidator<Forbidden, String> {
  15. private String[] forbiddenWords = {"admin"};
  16. @Override
  17. public void initialize(Forbidden constraintAnnotation) {
  18. //初始化,得到注解数据
  19. }
  20. @Override
  21. public boolean isValid(String value, ConstraintValidatorContext context) {
  22. if(StringUtils.isEmpty(value)) {
  23. return true;
  24. }
  25. for(String word : forbiddenWords) {
  26. if(value.contains(word)) {
  27. return false;//验证失败
  28. }
  29. }
  30. return true;
  31. }
  32. }

验证器中可以使用spring的依赖注入,如注入:@Autowired  private ApplicationContext ctx;

3、使用

  1. public class User implements Serializable {
  2. @Forbidden()
  3. private String name;
  4. }

4、当我们在提交name中含有admin的时候会输出错误消息:

  1. forbidden.word=您输入的数据中有非法关键词

问题来了,哪个词是非法的呢?bean validation 和 hibernate validator都没有提供相应的api提供这个数据,怎么办呢?通过跟踪代码,发现一种不是特别好的方法:我们可以覆盖org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl实现(即复制一份代码放到我们的src中),然后覆盖buildAnnotationParameterMap方法;

  1. private Map<String, Object> buildAnnotationParameterMap(Annotation annotation) {
  2. ……
  3. //将Collections.unmodifiableMap( parameters );替换为如下语句
  4. return parameters;
  5. }

即允许这个数据可以修改;然后在ForbiddenValidator中:

  1. for(String word : forbiddenWords) {
  2. if(value.contains(word)) {
  3. ((ConstraintValidatorContextImpl)context).getConstraintDescriptor().getAttributes().put("word", word);
  4. return false;//验证失败
  5. }
  6. }

通过((ConstraintValidatorContextImpl)context).getConstraintDescriptor().getAttributes().put("word", word);添加自己的属性;放到attributes中的数据可以通过${} 获取。然后消息就可以变成:

  1. forbidden.word=您输入的数据中有非法关键词【{word}】

这种方式不是很友好,但是可以解决我们的问题。

典型的如密码、确认密码的场景,非常常用;如果没有这个功能我们需要自己写代码来完成;而且经常重复自己。接下来看看bean validation 1.1如何实现的。

6、类级别验证器

6.1、定义验证注解

  1. package com.sishuok.spring4.validator;
  2. import javax.validation.Constraint;
  3. import javax.validation.Payload;
  4. import javax.validation.constraints.NotNull;
  5. import java.lang.annotation.Documented;
  6. import java.lang.annotation.Retention;
  7. import java.lang.annotation.Target;
  8. import static java.lang.annotation.ElementType.*;
  9. import static java.lang.annotation.RetentionPolicy.*;
  10. /**
  11. * <p>User: Zhang Kaitao
  12. * <p>Date: 13-12-15
  13. * <p>Version: 1.0
  14. */
  15. @Target({ TYPE, ANNOTATION_TYPE})
  16. @Retention(RUNTIME)
  17. //指定验证器
  18. @Constraint(validatedBy = CheckPasswordValidator.class)
  19. @Documented
  20. public @interface CheckPassword {
  21. //默认错误消息
  22. String message() default "";
  23. //分组
  24. Class<?>[] groups() default { };
  25. //负载
  26. Class<? extends Payload>[] payload() default { };
  27. //指定多个时使用
  28. @Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
  29. @Retention(RUNTIME)
  30. @Documented
  31. @interface List {
  32. CheckPassword[] value();
  33. }
  34. }

6.2、 定义验证器

  1. package com.sishuok.spring4.validator;
  2. import com.sishuok.spring4.entity.User;
  3. import org.springframework.util.StringUtils;
  4. import javax.validation.ConstraintValidator;
  5. import javax.validation.ConstraintValidatorContext;
  6. /**
  7. * <p>User: Zhang Kaitao
  8. * <p>Date: 13-12-15
  9. * <p>Version: 1.0
  10. */
  11. public class CheckPasswordValidator implements ConstraintValidator<CheckPassword, User> {
  12. @Override
  13. public void initialize(CheckPassword constraintAnnotation) {
  14. }
  15. @Override
  16. public boolean isValid(User user, ConstraintValidatorContext context) {
  17. if(user == null) {
  18. return true;
  19. }
  20. //没有填密码
  21. if(!StringUtils.hasText(user.getPassword())) {
  22. context.disableDefaultConstraintViolation();
  23. context.buildConstraintViolationWithTemplate("{password.null}")
  24. .addPropertyNode("password")
  25. .addConstraintViolation();
  26. return false;
  27. }
  28. if(!StringUtils.hasText(user.getConfirmation())) {
  29. context.disableDefaultConstraintViolation();
  30. context.buildConstraintViolationWithTemplate("{password.confirmation.null}")
  31. .addPropertyNode("confirmation")
  32. .addConstraintViolation();
  33. return false;
  34. }
  35. //两次密码不一样
  36. if (!user.getPassword().trim().equals(user.getConfirmation().trim())) {
  37. context.disableDefaultConstraintViolation();
  38. context.buildConstraintViolationWithTemplate("{password.confirmation.error}")
  39. .addPropertyNode("confirmation")
  40. .addConstraintViolation();
  41. return false;
  42. }
  43. return true;
  44. }
  45. }

其中我们通过disableDefaultConstraintViolation禁用默认的约束;然后通过buildConstraintViolationWithTemplate(消息模板)/addPropertyNode(所属属性)/addConstraintViolation定义我们自己的约束。

6.3、使用

  1. @CheckPassword()
  2. public class User implements Serializable {
  3. }

放到类头上即可。

7、通过脚本验证

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

通过脚本验证是非常简单而且强大的,lang指定脚本语言(请参考javax.script.ScriptEngineManager JSR-223),alias是在脚本验证中User对象的名字,但是大家会发现一个问题:错误消息怎么显示呢? 在springmvc 中会添加到全局错误消息中,这肯定不是我们想要的,我们改造下吧。

7.1、定义验证注解

  1. package com.sishuok.spring4.validator;
  2. import org.hibernate.validator.internal.constraintvalidators.ScriptAssertValidator;
  3. import java.lang.annotation.Documented;
  4. import java.lang.annotation.Retention;
  5. import java.lang.annotation.Target;
  6. import javax.validation.Constraint;
  7. import javax.validation.Payload;
  8. import static java.lang.annotation.ElementType.TYPE;
  9. import static java.lang.annotation.RetentionPolicy.RUNTIME;
  10. @Target({ TYPE })
  11. @Retention(RUNTIME)
  12. @Constraint(validatedBy = {PropertyScriptAssertValidator.class})
  13. @Documented
  14. public @interface PropertyScriptAssert {
  15. String message() default "{org.hibernate.validator.constraints.ScriptAssert.message}";
  16. Class<?>[] groups() default { };
  17. Class<? extends Payload>[] payload() default { };
  18. String lang();
  19. String script();
  20. String alias() default "_this";
  21. String property();
  22. @Target({ TYPE })
  23. @Retention(RUNTIME)
  24. @Documented
  25. public @interface List {
  26. PropertyScriptAssert[] value();
  27. }
  28. }

和ScriptAssert没什么区别,只是多了个property用来指定出错后给实体的哪个属性。

7.2、验证器

  1. package com.sishuok.spring4.validator;
  2. import javax.script.ScriptException;
  3. import javax.validation.ConstraintDeclarationException;
  4. import javax.validation.ConstraintValidator;
  5. import javax.validation.ConstraintValidatorContext;
  6. import com.sishuok.spring4.validator.PropertyScriptAssert;
  7. import org.hibernate.validator.constraints.ScriptAssert;
  8. import org.hibernate.validator.internal.util.Contracts;
  9. import org.hibernate.validator.internal.util.logging.Log;
  10. import org.hibernate.validator.internal.util.logging.LoggerFactory;
  11. import org.hibernate.validator.internal.util.scriptengine.ScriptEvaluator;
  12. import org.hibernate.validator.internal.util.scriptengine.ScriptEvaluatorFactory;
  13. import static org.hibernate.validator.internal.util.logging.Messages.MESSAGES;
  14. public class PropertyScriptAssertValidator implements ConstraintValidator<PropertyScriptAssert, Object> {
  15. private static final Log log = LoggerFactory.make();
  16. private String script;
  17. private String languageName;
  18. private String alias;
  19. private String property;
  20. private String message;
  21. public void initialize(PropertyScriptAssert constraintAnnotation) {
  22. validateParameters( constraintAnnotation );
  23. this.script = constraintAnnotation.script();
  24. this.languageName = constraintAnnotation.lang();
  25. this.alias = constraintAnnotation.alias();
  26. this.property = constraintAnnotation.property();
  27. this.message = constraintAnnotation.message();
  28. }
  29. public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
  30. Object evaluationResult;
  31. ScriptEvaluator scriptEvaluator;
  32. try {
  33. ScriptEvaluatorFactory evaluatorFactory = ScriptEvaluatorFactory.getInstance();
  34. scriptEvaluator = evaluatorFactory.getScriptEvaluatorByLanguageName( languageName );
  35. }
  36. catch ( ScriptException e ) {
  37. throw new ConstraintDeclarationException( e );
  38. }
  39. try {
  40. evaluationResult = scriptEvaluator.evaluate( script, value, alias );
  41. }
  42. catch ( ScriptException e ) {
  43. throw log.getErrorDuringScriptExecutionException( script, e );
  44. }
  45. if ( evaluationResult == null ) {
  46. throw log.getScriptMustReturnTrueOrFalseException( script );
  47. }
  48. if ( !( evaluationResult instanceof Boolean ) ) {
  49. throw log.getScriptMustReturnTrueOrFalseException(
  50. script,
  51. evaluationResult,
  52. evaluationResult.getClass().getCanonicalName()
  53. );
  54. }
  55. if(Boolean.FALSE.equals(evaluationResult)) {
  56. constraintValidatorContext.disableDefaultConstraintViolation();
  57. constraintValidatorContext
  58. .buildConstraintViolationWithTemplate(message)
  59. .addPropertyNode(property)
  60. .addConstraintViolation();
  61. }
  62. return Boolean.TRUE.equals( evaluationResult );
  63. }
  64. private void validateParameters(PropertyScriptAssert constraintAnnotation) {
  65. Contracts.assertNotEmpty( constraintAnnotation.script(), MESSAGES.parameterMustNotBeEmpty( "script" ) );
  66. Contracts.assertNotEmpty( constraintAnnotation.lang(), MESSAGES.parameterMustNotBeEmpty( "lang" ) );
  67. Contracts.assertNotEmpty( constraintAnnotation.alias(), MESSAGES.parameterMustNotBeEmpty( "alias" ) );
  68. Contracts.assertNotEmpty( constraintAnnotation.property(), MESSAGES.parameterMustNotBeEmpty( "property" ) );
  69. Contracts.assertNotEmpty( constraintAnnotation.message(), MESSAGES.parameterMustNotBeEmpty( "message" ) );
  70. }
  71. }

和之前的类级别验证器类似,就不多解释了,其他代码全部拷贝自org.hibernate.validator.internal.constraintvalidators.ScriptAssertValidator。

7.3、使用

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

和之前的区别就是多了个property,用来指定出错时给哪个字段。 这个相对之前的类级别验证器更通用一点。

8、cross-parameter,跨参数验证

直接看示例;

8.1、首先注册MethodValidationPostProcessor,起作用请参考《Spring3.1 对Bean Validation规范的新支持(方法级别验证) 》

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

8.2、Service

  1. @Validated
  2. @Service
  3. public class UserService {
  4. @CrossParameter
  5. public void changePassword(String password, String confirmation) {
  6. }
  7. }

通过@Validated注解UserService表示该类中有需要进行方法参数/返回值验证;   @CrossParameter注解方法表示要进行跨参数验证;即验证password和confirmation是否相等。

8.3、验证注解

  1. package com.sishuok.spring4.validator;
  2. //省略import
  3. @Constraint(validatedBy = CrossParameterValidator.class)
  4. @Target({ METHOD, CONSTRUCTOR, ANNOTATION_TYPE })
  5. @Retention(RUNTIME)
  6. @Documented
  7. public @interface CrossParameter {
  8. String message() default "{password.confirmation.error}";
  9. Class<?>[] groups() default { };
  10. Class<? extends Payload>[] payload() default { };
  11. }

8.4、验证器

  1. package com.sishuok.spring4.validator;
  2. //省略import
  3. @SupportedValidationTarget(ValidationTarget.PARAMETERS)
  4. public class CrossParameterValidator implements ConstraintValidator<CrossParameter, Object[]> {
  5. @Override
  6. public void initialize(CrossParameter constraintAnnotation) {
  7. }
  8. @Override
  9. public boolean isValid(Object[] value, ConstraintValidatorContext context) {
  10. if(value == null || value.length != 2) {
  11. throw new IllegalArgumentException("must have two args");
  12. }
  13. if(value[0] == null || value[1] == null) {
  14. return true;
  15. }
  16. if(value[0].equals(value[1])) {
  17. return true;
  18. }
  19. return false;
  20. }
  21. }

其中@SupportedValidationTarget(ValidationTarget.PARAMETERS)表示验证参数; value将是参数列表。

8.5、使用

  1. @RequestMapping("/changePassword")
  2. public String changePassword(
  3. @RequestParam("password") String password,
  4. @RequestParam("confirmation") String confirmation, Model model) {
  5. try {
  6. userService.changePassword(password, confirmation);
  7. } catch (ConstraintViolationException e) {
  8. for(ConstraintViolation violation : e.getConstraintViolations()) {
  9. System.out.println(violation.getMessage());
  10. }
  11. }
  12. return "success";
  13. }

调用userService.changePassword方法,如果验证失败将抛出ConstraintViolationException异常,然后得到ConstraintViolation,调用getMessage即可得到错误消息;然后到前台显示即可。

从以上来看,不如之前的使用方便,需要自己对错误消息进行处理。 下一节我们也写个脚本方式的跨参数验证器。

9、混合类级别验证器和跨参数验证器

9.1、验证注解

  1. package com.sishuok.spring4.validator;
  2. //省略import
  3. @Constraint(validatedBy = {
  4. CrossParameterScriptAssertClassValidator.class,
  5. CrossParameterScriptAssertParameterValidator.class
  6. })
  7. @Target({ TYPE, FIELD, PARAMETER, METHOD, CONSTRUCTOR, ANNOTATION_TYPE })
  8. @Retention(RUNTIME)
  9. @Documented
  10. public @interface CrossParameterScriptAssert {
  11. String message() default "error";
  12. Class<?>[] groups() default { };
  13. Class<? extends Payload>[] payload() default { };
  14. String script();
  15. String lang();
  16. String alias() default "_this";
  17. String property() default "";
  18. ConstraintTarget validationAppliesTo() default ConstraintTarget.IMPLICIT;
  19. }

此处我们通过@Constraint指定了两个验证器,一个类级别的,一个跨参数的。validationAppliesTo指定为ConstraintTarget.IMPLICIT,表示隐式自动判断。

9.2、验证器

请下载源码查看

9.3、使用

9.3.1、类级别使用

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

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

9.3.2、跨参数验证

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

通过args[0]==args[1] 来判断是否相等。

这样,我们的验证注解就自动适应两种验证规则了。

10、组合验证注解

有时候,可能有好几个注解需要一起使用,此时就可以使用组合验证注解

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

这样我们验证时只需要:

  1. @Composition()
  2. private String name;

简洁多了。

11、本地化

即根据不同的语言选择不同的错误消息显示。

1、本地化解析器

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

此处使用cookie存储本地化信息,当然也可以选择其他的,如Session存储。

2、设置本地化信息的拦截器

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

即请求参数中通过language设置语言。

3、消息文件

4、 浏览器输入

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

到此,我们已经完成大部分Bean Validation的功能实验了。对于如XML配置、编程式验证API的使用等对于我们使用SpringMVC这种web环境用处不大,所以就不多介绍了,有兴趣可以自己下载官方文档学习。

javax validation--参数基础校验的更多相关文章

  1. javax.validation参数校验

    在实体字段加注解: /** * 机构名称 */ @ApiParam(name = "orgName", value = "机构名称") @Size(max = ...

  2. Java web服务端参数校验Javax.validation (springboot)

    一.基本使用 Javax.validation是spring集成自带的一个参数校验接口.可通过添加注解来设置校验条件. 下面以springboot项目为例进行说明.创建web项目后,不需要再添加其他的 ...

  3. SpringBoot - Bean validation 参数校验

    目录 前言 常见注解 参数校验的应用 依赖 简单的参数校验示例 级联校验 @Validated 与 @Valid 自定义校验注解 前言 后台开发中对参数的校验是不可缺少的一个环节,为了解决如何优雅的对 ...

  4. Validation(3)--全局参数异常校验捕获及返回XML解决

    @RestControllerAdvice原创直接上代码,后面再说怎么用1.这个是一个Form,用来接收参数的,一个简单的NotEmpty注解校验,merchantName这个参数是必传的: pack ...

  5. 使用Spring Validation优雅地校验参数

    写得好的没我写得全,写得全的没我写得好 引言 不知道大家平时的业务开发过程中 controller 层的参数校验都是怎么写的?是否也存在下面这样的直接判断? public String add(Use ...

  6. SpringBoot Validation参数校验 详解自定义注解规则和分组校验

    前言 Hibernate Validator 是 Bean Validation 的参考实现 .Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的 ...

  7. javax.validation.ConstraintViolationException---Hibernate后台实体校验

    javax.validation.ConstraintViolationException ... 71 moreCaused by: javax.validation.ConstraintViola ...

  8. spring boot validation参数校验

    对于任何一个应用而言在客户端做的数据有效性验证都不是安全有效的,这时候就要求我们在开发的时候在服务端也对数据的有效性进行验证. Spring Boot自身对数据在服务端的校验有一个比较好的支持,它能将 ...

  9. spring boot中使用javax.validation以及org.hibernate.validator校验入参

    这里springboot用的版本是:<version>2.1.1.RELEASE</version> 自带了hibernate.validator,所以不用添加额外依赖 1.创 ...

随机推荐

  1. 在Gridview 中 对日期格式的控制

    在数据库中保存日期格式的时候,我们需要在客户端的显示有自己的要求 这就需要对 datatime 类型的数据进行控制,使之显示为你需要的格式 数据库中 如果不对其进行控制,显示的格式为 当在前端页面上进 ...

  2. scrollview的优化

    针对一次加载很多格子的scrollview的优化,第一次只加载可视区域的格子,其他的用空物体占位,在每次滑动时检测需要实例化的格子,通过对象池重用第一次的格子.可以根据每行格子的数量只检测每行的第一个 ...

  3. 如何自己手动修改win10磁贴背景颜色?

    前言 当我们安装完应用后,可以选择将应用图标固定到"开始"屏幕,于是就会产生一个磁贴,有的应用会自带背景颜色,有的则是默认的主题色.其实这个只不过是应用本身没有没有去适配win10 ...

  4. 01-Windows Server 2012的配置与部署

    一. 背景 这里以阿里云Windows Server 2012系统的服务器为主,介绍服务器的配置以及.Net程序的发布顺序,在后续的项目管理文章中,会介绍<运维手册>的写法. 二. 步骤 ...

  5. linux 挂载新的硬盘

    linux 挂载新的硬盘 1.查看硬盘情况,物理盘和分区 fdisk -l 2.分区一个盘,sdb是个还没有分区的硬盘 fdisk /dev/sdb 输入 n p 1 w n 表示新建分区 p 表示分 ...

  6. 工信部要求应用商店上新 App 检查 IPv6,这里有一份 IPv6 快速部署指南

    7 月 25 日,工业和信息化部信息通信发展司组织召开部署推进 IPv6 网络就绪专项行动电视电话会议.会议指出,加快推进 IPv6 规模部署,构建高速率.广普及.全覆盖.智能化的下一代互联网,是互联 ...

  7. texlive2019安装

    TeX Live 是 TUG (TeX User Group) 发布并维护的的 TeX 系统,可以称得上是TeX的官方系统,官网为:https://www.tug.org/texlive/ 1.通过最 ...

  8. React 语法

    1.JavaScript XML JSX = JavaScript XML,是一个看起来很像 XML 的 JavaScript 语法扩展.JSX 不是模板,是JS语法本身,有更多的扩展.JSX 组件一 ...

  9. MVC的Views中使用递归生成Html【转】

    在开发过程中往往会有一个需求,就是将一个树状的数据结构在视图中表示出来.例如最传统的多级分类,系统中有一系列根分类,每个分类中又带有一些子分类,而我们的目标便是在页面上生成一个由ul和li嵌套组成的H ...

  10. 米尔电子i.MX8开发板评测

    基于 NXP 公司的i.MX8M 系列芯片的高性能开发平台 MYD-JX8MX开发板.是采用核心板(MYC-JX8MX)加底板(MYB-JX8MX)的形式,提供了 HDMI,LVDS(或 MIPI), ...